From 12fdfa1e88427f479f4e2a17ede607260da557a1 Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Sun, 16 Aug 2026 06:41:56 -0400 Subject: [PATCH 1/5] CLOSES #1181, CLOSES #1212, CLOSES #1214, CLOSES #1224, CLOSES #1230, CLOSES #1249, CLOSES #1271, CLOSES #1272, CLOSES #1273, CLOSES #1298, CLOSES #1305, CLOSES #1307, CLOSES #1318, CLOSES #1328, CLOSES #1330, CLOSES #1331, CLOSES #1333, CLOSES #1334, CLOSES #1335, CLOSES #1340, CLOSES #1345, CLOSES #1346, CLOSES #1360, CLOSES #1362 --- data/scripts/flavor/oaks_lab.lua | 40 +- data/scripts/oaks_lab.lua | 62 +- data/scripts/story2.lua | 26 +- data/scripts/story5.lua | 36 +- docs/gold-walkthrough/asm-walk/README.md | 1374 -------------- docs/gold-walkthrough/asm-walk/_TEMPLATE.md | 126 -- ...tion-00-chikorita-cyndaquil-or-totodile.md | 535 ------ ...on-01-cherrygrove-city-and-routes-29-31.md | 1081 ----------- ...ion-02-sprout-tower-and-violet-city-gym.md | 777 -------- ...section-03-ruins-of-alph-and-union-cave.md | 1280 ------------- ...ection-04-slowpoke-well-azalea-town-gym.md | 907 --------- ...n-05-ilex-forest-and-goldenrod-city-gym.md | 1309 ------------- ...n-06-bug-catching-contest-and-sudowoodo.md | 987 ---------- ...n-07-burned-tower-and-ecruteak-city-gym.md | 1227 ------------- ...livine-lighthouse-and-cianwood-city-gym.md | 1632 ----------------- .../asm-walk/section-09-olivine-city-gym.md | 681 ------- ...-10-the-lake-of-rage-and-shiny-gyarados.md | 1101 ----------- ...am-rocket-hideout-and-mahogany-town-gym.md | 851 --------- .../section-12-team-rocket-radio-tower.md | 1050 ----------- ...ion-13-ice-path-and-blackthorn-city-gym.md | 1255 ------------- .../section-14-ho-oh-gold-and-lugia-silver.md | 1090 ----------- .../section-15-mount-mortar-and-dark-cave.md | 762 -------- .../section-16-routes-45-46-26-and-27.md | 1078 ----------- .../asm-walk/section-17-victory-road.md | 599 ------ .../asm-walk/section-18-pok-mon-league.md | 883 --------- ...tion-19-s-s-aqua-and-vermilion-city-gym.md | 1093 ----------- .../asm-walk/section-20-saffron-city-gym.md | 680 ------- ...on-21-power-plant-and-cerulean-city-gym.md | 896 --------- ...ion-22-rock-tunnel-and-celadon-city-gym.md | 1277 ------------- ...on-23-routes-11-19-and-fuchsia-city-gym.md | 1508 --------------- .../section-24-snorlax-and-pewter-city-gym.md | 919 ---------- .../section-25-lugia-gold-and-ho-oh-silver.md | 987 ---------- .../section-26-mount-moon-and-routes-1-4.md | 893 --------- ...cinnabar-island-and-seafoam-islands-gym.md | 570 ------ ...n-28-routes-19-20-and-viridian-city-gym.md | 460 ----- .../asm-walk/section-29-routes-22-28.md | 785 -------- .../asm-walk/section-30-lapras.md | 611 ------ .../section-31-mount-silver-and-red.md | 715 -------- .../section-32-raikou-entei-and-suicune.md | 823 --------- ...ion-00-chikorita-cyndaquil-or-totodile.txt | 119 -- ...n-01-cherrygrove-city-and-routes-29-31.txt | 369 ---- ...on-02-sprout-tower-and-violet-city-gym.txt | 150 -- ...ection-03-ruins-of-alph-and-union-cave.txt | 312 ---- ...ction-04-slowpoke-well-azalea-town-gym.txt | 155 -- ...-05-ilex-forest-and-goldenrod-city-gym.txt | 395 ---- ...-06-bug-catching-contest-and-sudowoodo.txt | 292 --- ...-07-burned-tower-and-ecruteak-city-gym.txt | 361 ---- ...ivine-lighthouse-and-cianwood-city-gym.txt | 584 ------ .../section-09-olivine-city-gym.txt | 81 - ...10-the-lake-of-rage-and-shiny-gyarados.txt | 223 --- ...m-rocket-hideout-and-mahogany-town-gym.txt | 334 ---- .../section-12-team-rocket-radio-tower.txt | 324 ---- ...on-13-ice-path-and-blackthorn-city-gym.txt | 317 ---- ...section-14-ho-oh-gold-and-lugia-silver.txt | 193 -- .../section-15-mount-mortar-and-dark-cave.txt | 222 --- .../section-16-routes-45-46-26-and-27.txt | 394 ---- .../section-17-victory-road.txt | 99 - .../section-18-pok-mon-league.txt | 296 --- ...ion-19-s-s-aqua-and-vermilion-city-gym.txt | 246 --- .../section-20-saffron-city-gym.txt | 135 -- ...n-21-power-plant-and-cerulean-city-gym.txt | 304 --- ...on-22-rock-tunnel-and-celadon-city-gym.txt | 313 ---- ...n-23-routes-11-19-and-fuchsia-city-gym.txt | 468 ----- ...section-24-snorlax-and-pewter-city-gym.txt | 192 -- ...section-25-lugia-gold-and-ho-oh-silver.txt | 179 -- .../section-26-mount-moon-and-routes-1-4.txt | 232 --- ...innabar-island-and-seafoam-islands-gym.txt | 133 -- ...-28-routes-19-20-and-viridian-city-gym.txt | 134 -- .../section-29-routes-22-28.txt | 80 - docs/gold-walkthrough/section-30-lapras.txt | 72 - .../section-31-mount-silver-and-red.txt | 153 -- .../section-32-raikou-entei-and-suicune.txt | 61 - docs/rfcs/0003-multiplayer-session-layer.md | 114 -- docs/rfcs/0003-playthrough-storage.md | 147 -- docs/rfcs/0004-runtime-checkpoints.md | 138 -- docs/rfcs/0005-battle-runtime-checkpoints.md | 158 -- docs/rfcs/0006-platform-lifecycle-hooks.md | 107 -- ...006-title-playthrough-checkpoint-resume.md | 92 - .../0007-battle-menu-auxiliary-actions.md | 46 - docs/rfcs/0007-per-category-game-speed.md | 212 --- docs/rfcs/0008-device-power-info.md | 49 - docs/rfcs/0009-step-bridge-permission.md | 68 - docs/rfcs/0010-trainer-battle-party-scope.md | 92 - src/battle/BattleState.lua | 96 +- src/battle/gen2/AnimRunner.lua | 2 +- src/battle/gen2/Battle.lua | 25 +- src/battle/gen2/Prize.lua | 15 + src/core/Game2.lua | 11 +- src/core/Music.lua | 45 +- src/core/gen2/ItemEffects.lua | 73 +- src/core/gen2/Save.lua | 1 + src/import/LauncherSettings.lua | 10 + src/render/PaletteFX.lua | 37 +- src/render/Renderer.lua | 14 + src/render/TextBox.lua | 9 + src/script/gen2/CallAsm.lua | 5 +- src/ui/SummaryMenu.lua | 12 +- src/ui/TitleState.lua | 31 +- src/ui/gen2/BattleState.lua | 88 +- src/ui/gen2/OptionsMenu.lua | 11 + src/ui/gen2/PartyMenu.lua | 8 + src/world/OverworldController.lua | 39 +- src/world/gen2/StepEvents.lua | 2 +- src/world/gen2/World.lua | 49 +- .../title_obj_palette_bug1214_test.lua | 323 ++++ tests/engine/gen2_always_hit_bug1272.lua | 155 ++ tests/engine/gen2_pay_day_bug1212.lua | 268 +++ tests/engine/gen2_substitute_doll_bug1271.lua | 147 ++ .../engine/gen2_type_chart_merge_bug1268.lua | 215 +++ tests/engine/museum_money_box_bug1335.lua | 81 + .../oaks_lab_posters_email_bug1331_1333.lua | 83 + .../oaks_lab_starter_energetic_bug1334.lua | 68 + tests/engine/oaks_pc_flow.lua | 22 +- tests/engine/residual_latch_bug1181.lua | 174 ++ tests/engine/town_map_bookshelf_bug1330.lua | 68 + tests/gen2_callasm_test.lua | 24 + tests/gen2_field_items_test.lua | 112 ++ tests/gen2_menus_test.lua | 54 +- tests/parity_hpcolor_og_palette.lua | 253 +++ 119 files changed, 2555 insertions(+), 42186 deletions(-) delete mode 100644 docs/gold-walkthrough/asm-walk/README.md delete mode 100644 docs/gold-walkthrough/asm-walk/_TEMPLATE.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-00-chikorita-cyndaquil-or-totodile.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-01-cherrygrove-city-and-routes-29-31.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-02-sprout-tower-and-violet-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-03-ruins-of-alph-and-union-cave.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-04-slowpoke-well-azalea-town-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-05-ilex-forest-and-goldenrod-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-06-bug-catching-contest-and-sudowoodo.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-07-burned-tower-and-ecruteak-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-08-olivine-lighthouse-and-cianwood-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-09-olivine-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-10-the-lake-of-rage-and-shiny-gyarados.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-11-team-rocket-hideout-and-mahogany-town-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-12-team-rocket-radio-tower.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-13-ice-path-and-blackthorn-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-14-ho-oh-gold-and-lugia-silver.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-15-mount-mortar-and-dark-cave.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-16-routes-45-46-26-and-27.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-17-victory-road.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-18-pok-mon-league.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-19-s-s-aqua-and-vermilion-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-20-saffron-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-21-power-plant-and-cerulean-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-22-rock-tunnel-and-celadon-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-23-routes-11-19-and-fuchsia-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-24-snorlax-and-pewter-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-25-lugia-gold-and-ho-oh-silver.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-26-mount-moon-and-routes-1-4.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-27-cinnabar-island-and-seafoam-islands-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-28-routes-19-20-and-viridian-city-gym.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-29-routes-22-28.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-30-lapras.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-31-mount-silver-and-red.md delete mode 100644 docs/gold-walkthrough/asm-walk/section-32-raikou-entei-and-suicune.md delete mode 100644 docs/gold-walkthrough/section-00-chikorita-cyndaquil-or-totodile.txt delete mode 100644 docs/gold-walkthrough/section-01-cherrygrove-city-and-routes-29-31.txt delete mode 100644 docs/gold-walkthrough/section-02-sprout-tower-and-violet-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-03-ruins-of-alph-and-union-cave.txt delete mode 100644 docs/gold-walkthrough/section-04-slowpoke-well-azalea-town-gym.txt delete mode 100644 docs/gold-walkthrough/section-05-ilex-forest-and-goldenrod-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-06-bug-catching-contest-and-sudowoodo.txt delete mode 100644 docs/gold-walkthrough/section-07-burned-tower-and-ecruteak-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-08-olivine-lighthouse-and-cianwood-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-09-olivine-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-10-the-lake-of-rage-and-shiny-gyarados.txt delete mode 100644 docs/gold-walkthrough/section-11-team-rocket-hideout-and-mahogany-town-gym.txt delete mode 100644 docs/gold-walkthrough/section-12-team-rocket-radio-tower.txt delete mode 100644 docs/gold-walkthrough/section-13-ice-path-and-blackthorn-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-14-ho-oh-gold-and-lugia-silver.txt delete mode 100644 docs/gold-walkthrough/section-15-mount-mortar-and-dark-cave.txt delete mode 100644 docs/gold-walkthrough/section-16-routes-45-46-26-and-27.txt delete mode 100644 docs/gold-walkthrough/section-17-victory-road.txt delete mode 100644 docs/gold-walkthrough/section-18-pok-mon-league.txt delete mode 100644 docs/gold-walkthrough/section-19-s-s-aqua-and-vermilion-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-20-saffron-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-21-power-plant-and-cerulean-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-22-rock-tunnel-and-celadon-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-23-routes-11-19-and-fuchsia-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-24-snorlax-and-pewter-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-25-lugia-gold-and-ho-oh-silver.txt delete mode 100644 docs/gold-walkthrough/section-26-mount-moon-and-routes-1-4.txt delete mode 100644 docs/gold-walkthrough/section-27-cinnabar-island-and-seafoam-islands-gym.txt delete mode 100644 docs/gold-walkthrough/section-28-routes-19-20-and-viridian-city-gym.txt delete mode 100644 docs/gold-walkthrough/section-29-routes-22-28.txt delete mode 100644 docs/gold-walkthrough/section-30-lapras.txt delete mode 100644 docs/gold-walkthrough/section-31-mount-silver-and-red.txt delete mode 100644 docs/gold-walkthrough/section-32-raikou-entei-and-suicune.txt delete mode 100644 docs/rfcs/0003-multiplayer-session-layer.md delete mode 100644 docs/rfcs/0003-playthrough-storage.md delete mode 100644 docs/rfcs/0004-runtime-checkpoints.md delete mode 100644 docs/rfcs/0005-battle-runtime-checkpoints.md delete mode 100644 docs/rfcs/0006-platform-lifecycle-hooks.md delete mode 100644 docs/rfcs/0006-title-playthrough-checkpoint-resume.md delete mode 100644 docs/rfcs/0007-battle-menu-auxiliary-actions.md delete mode 100644 docs/rfcs/0007-per-category-game-speed.md delete mode 100644 docs/rfcs/0008-device-power-info.md delete mode 100644 docs/rfcs/0009-step-bridge-permission.md delete mode 100644 docs/rfcs/0010-trainer-battle-party-scope.md create mode 100644 tests/drivers/title_obj_palette_bug1214_test.lua create mode 100644 tests/engine/gen2_always_hit_bug1272.lua create mode 100644 tests/engine/gen2_pay_day_bug1212.lua create mode 100644 tests/engine/gen2_substitute_doll_bug1271.lua create mode 100644 tests/engine/gen2_type_chart_merge_bug1268.lua create mode 100644 tests/engine/museum_money_box_bug1335.lua create mode 100644 tests/engine/oaks_lab_posters_email_bug1331_1333.lua create mode 100644 tests/engine/oaks_lab_starter_energetic_bug1334.lua create mode 100644 tests/engine/residual_latch_bug1181.lua create mode 100644 tests/engine/town_map_bookshelf_bug1330.lua create mode 100644 tests/parity_hpcolor_og_palette.lua diff --git a/data/scripts/flavor/oaks_lab.lua b/data/scripts/flavor/oaks_lab.lua index b7d9ff0d..8fcb138a 100644 --- a/data/scripts/flavor/oaks_lab.lua +++ b/data/scripts/flavor/oaks_lab.lua @@ -1,13 +1,39 @@ --- Hand-ported flavor text for OaksLab (registry id OAKS_LAB). --- Source: pokered/scripts/OaksLab.asm. These five text_asm bodies are --- all simple "PrintText; jp TextScriptEnd" -- no flag branches, no --- YES/NO menu -- so a one-row talk script showing the real extracted --- text is a faithful port. (The rest of OaksLab.asm's TEXT_OAKSLAB_* --- constants -- OAK1, the three starter poke balls, RIVAL -- are already --- ported with full branching logic in data/scripts/oaks_lab.lua.) +-- Hand-ported OAKS_LAB flavor: the simple talk texts (scripts/OaksLab.asm; +-- OAK1, the starter balls and RIVAL live in data/scripts/oaks_lab.lua). + +local TextBox = require("src.render.TextBox") return { OAKS_LAB = { + -- data/events/hidden_events.asm:147 + onInteract = function(game, ow, fx, fy) + local t = game.data.text or {} + -- engine/events/hidden_events/oaks_lab_posters.asm:1 + if fy == 0 and fx == 4 then + game.stack:push(TextBox.new(game, + t._PushStartText or "Push START to\nopen the MENU!")) + return true + end + if fy == 0 and fx == 5 then + local owned = 0 + for _ in pairs(game.save.pokedex.owned or {}) do owned = owned + 1 end + game.stack:push(TextBox.new(game, + owned >= 2 + and (t._StrengthsAndWeaknessesText + or "All POKéMON types\nhave strong and\vweak points\vagainst others.") + or (t._SaveOptionText + or "The SAVE option is\non the MENU\vscreen."))) + return true + end + -- engine/events/hidden_events/oaks_lab_email.asm:1 + if fy == 1 and (fx == 0 or fx == 1) then + if ow.player.facing ~= "up" then return false end + game.stack:push(TextBox.new(game, + t._OakLabEmailText or "There's an e-mail\nmessage here!")) + return true + end + return false + end, talk = { -- OaksLabGirlText (scripts/OaksLab.asm) TEXT_OAKSLAB_GIRL = { diff --git a/data/scripts/oaks_lab.lua b/data/scripts/oaks_lab.lua index 55cb3316..bb0b058f 100644 --- a/data/scripts/oaks_lab.lua +++ b/data/scripts/oaks_lab.lua @@ -22,10 +22,10 @@ local function starterBall(askText, species, choseFlag, ownBall, rivalBallX, rivalBall) return { { "check_flag", "EVENT_GOT_STARTER" }, -- 1 - { "jump_if_true", 22 }, -- 2 + { "jump_if_true", 23 }, -- 2 -- no picking until Oak has walked you in (OaksLabScript gating) { "check_flag", "EVENT_FOLLOWED_OAK_INTO_LAB" }, -- 3 - { "jump_if_false", 25 }, -- 4 + { "jump_if_false", 26 }, -- 4 -- the Pokédex "new species" entry shows before the ask (predef -- StarterDex ahead of OaksLabYouWant...Text). StarterDex temporarily -- sets the owned bits so ShowPokedexData prints height/weight/text; @@ -34,44 +34,38 @@ local function starterBall(askText, species, choseFlag, ownBall, { species = species, forceOwned = true } }, -- 5 { "ask", askText }, -- 6 { "jump_if_false", "end" }, -- 7 - -- OaksLab.asm prints ReceivedMon then AddPartyMon (AskName lives - -- inside give_pokemon). Show the received text first so the - -- nickname prompt follows "you got X", matching Gen1. - -- The received text carries sound_get_key_item (OaksLab.asm - -- OaksLabReceivedMonText), so the jingle fires once the box has - -- typed out and holds it (same beat as the Yellow port's starter, #668). - { "text_sound", "Get_Key_Item" }, -- 8 - { "show_text", "_OaksLabReceivedMonText", { RAM = species } }, -- 9 - { "give_pokemon", species, 5 }, -- 10 - { "set_flag", "EVENT_GOT_STARTER" }, -- 11 - { "set_flag", choseFlag }, -- 12 - -- POKé BALLs are not handed out here in the original -- Oak gives - -- them later, at OaksLabOak1Text's .give_poke_balls beat once the - -- player has beaten the Route 22 rival (see TEXT_OAKSLAB_OAK1 below) - { "hide_object", "OAKS_LAB", ownBall }, -- 13 + -- scripts/OaksLab.asm:919 + { "show_text", "_OaksLabMonEnergeticText" }, -- 8 + -- OaksLab.asm: ReceivedMon (sound_get_key_item) then AddPartyMon; the + -- jingle fires once the box has typed and holds it (#668) + { "text_sound", "Get_Key_Item" }, -- 9 + { "show_text", "_OaksLabReceivedMonText", { RAM = species } }, -- 10 + { "give_pokemon", species, 5 }, -- 11 + { "set_flag", "EVENT_GOT_STARTER" }, -- 12 + { "set_flag", choseFlag }, -- 13 + -- POKé BALLs come later, at OaksLabOak1Text's .give_poke_balls beat + -- once the Route 22 rival is beaten (see TEXT_OAKSLAB_OAK1 below) + { "hide_object", "OAKS_LAB", ownBall }, -- 14 -- the rival walks to the countering ball (around the furniture) - { "move_npc_to", 1, rivalBallX, 4 }, -- 14 - { "face_object", 1, "up" }, -- 15 - { "show_text", "_OaksLabRivalIllTakeThisOneText" }, -- 16 - { "hide_object", "OAKS_LAB", rivalBall }, -- 17 - { "text_sound", "Get_Key_Item" }, -- 18 (sound_get_key_item) + { "move_npc_to", 1, rivalBallX, 4 }, -- 15 + { "face_object", 1, "up" }, -- 16 + { "show_text", "_OaksLabRivalIllTakeThisOneText" }, -- 17 + { "hide_object", "OAKS_LAB", rivalBall }, -- 18 + { "text_sound", "Get_Key_Item" }, -- 19 (sound_get_key_item) { "show_text", "_OaksLabRivalReceivedMonText", { RAM = rivalBall == "OAKSLAB_CHARMANDER_POKE_BALL" and "CHARMANDER" or rivalBall == "OAKSLAB_SQUIRTLE_POKE_BALL" and "SQUIRTLE" - or "BULBASAUR" } }, -- 19 - { "jump", "end" }, -- 20 - { "jump", "end" }, -- 21 (spacer) - -- a leftover ball after the player's pick: Oak turns to face the - -- player and reads the last-mon line instead of re-offering the - -- starter (scripts/OaksLab.asm OaksLabSelectedPokeBallScript -> - -- OaksLabLastMonScript; #601). The ROM's "#MON" ligature is spelled - -- out as Pokémon here. - { "face_object", 5, "down" }, -- 22 - { "show_text", "That's PROF.OAK's\nlast Pokémon!" }, -- 23 + or "BULBASAUR" } }, -- 20 + { "jump", "end" }, -- 21 + { "jump", "end" }, -- 22 (spacer) + -- leftover ball: Oak reads the last-mon line (scripts/OaksLab.asm + -- OaksLabSelectedPokeBallScript -> OaksLabLastMonScript, #601) + { "face_object", 5, "down" }, -- 23 + { "show_text", "That's PROF.OAK's\nlast Pokémon!" }, -- 24 -- OaksLabLastMonScript ends at TextScriptEnd; the port used to fall -- through into the pre-pick line below (#601 remnant, reported on #600) - { "jump", "end" }, -- 24 - { "show_text", "_OaksLabThoseArePokeBallsText" }, -- 25 + { "jump", "end" }, -- 25 + { "show_text", "_OaksLabThoseArePokeBallsText" }, -- 26 } end diff --git a/data/scripts/story2.lua b/data/scripts/story2.lua index b414b804..08b3dee0 100644 --- a/data/scripts/story2.lua +++ b/data/scripts/story2.lua @@ -697,35 +697,37 @@ M.MT_MOON_B2F = { }, } --- The ticket clerk (scripts/Museum1F.asm Museum1FScientist1Text): --- Y50, once. Declining at the rope shoves the player one tile SOUTH back off --- the exhibit rope they crossed heading north (#151); the museum floor has no --- ledges, so a plain scriptMove("down",1) is the correct primitive. +-- The ticket clerk (scripts/Museum1F.asm Museum1FScientist1Text): Y50, once. +-- Declining at the rope shoves the player one tile south (#151) local function museumClerk(game, ow, done, onDecline) local TextBox = require("src.render.TextBox") - local ChoiceBox = require("src.ui.ChoiceBox") + local t = game.data.text or {} if game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET then game.stack:push(TextBox.new(game, "Take your time,\nand enjoy it all!", done)) return end + -- scripts/Museum1F.asm:72 + local money = function() return game.save.money end game.stack:push(TextBox.new(game, - "It's ¥50 for a\nchild's ticket.\fWould you like to\ncome in?", function() - game.stack:push(ChoiceBox.new(game, function(yes) + t._Museum1FScientist1WouldYouLikeToComeInText + or "It's ¥50 for a\nchild's ticket.\fWould you like to\ncome in?", + nil, { money = money, choice = function(yes) if yes and game.save.money >= 50 then game.save.money = game.save.money - 50 game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET = true + -- scripts/Museum1F.asm:106 game.stack:push(TextBox.new(game, - "Right, ¥50!\nThank you!", done)) + t._Museum1FScientist1ThankYouText or "Right, ¥50!\nThank you!", done, + { money = money })) elseif yes then game.stack:push(TextBox.new(game, - "You don't have\nenough money.", onDecline or done)) + "You don't have\nenough money.", onDecline or done, { money = money })) else game.stack:push(TextBox.new(game, - "Come again!", onDecline or done)) + "Come again!", onDecline or done, { money = money })) end - end)) - end)) + end })) end M.MUSEUM_1F = { diff --git a/data/scripts/story5.lua b/data/scripts/story5.lua index 2c580f41..67002652 100644 --- a/data/scripts/story5.lua +++ b/data/scripts/story5.lua @@ -573,14 +573,15 @@ local function route22Scene(n, objIndex, objName, oppClass, baseParty, beatFlag, { "face_object", objIndex, rivalFacing }, -- 3 { "show_text", "_Route22RivalBeforeBattleText" .. n }, -- 4 { "rival_battle", oppClass, baseParty }, -- 5 - { "jump_if_false", 12 }, -- 6 + { "jump_if_false", 13 }, -- 6 { "set_flag", beatFlag }, -- 7 { "show_text", "_Route22Rival" .. n .. "DefeatedText" }, -- 8 { "show_text", "_Route22RivalAfterBattleText" .. n }, -- 9 { "play_music", "Music_MeetRival", { start = "rival", - tempo = n == 2 and 100 or nil } }, - { "walk_npc", objIndex, route22ExitDirs(n, py) }, -- 10 - { "hide_object", "ROUTE_22", objName }, -- 11 + tempo = n == 2 and 100 or nil } }, -- 10 + { "walk_npc", objIndex, route22ExitDirs(n, py) }, -- 11 + { "play_default_music" }, -- scripts/Route22.asm:230 + { "hide_object", "ROUTE_22", objName }, -- 13 } end @@ -627,13 +628,14 @@ local function ceruleanRivalScene(px, py) { "face_object", 1, "down" }, -- 3 { "show_text", "_CeruleanCityRivalPreBattleText" }, -- 4 { "rival_battle", "OPP_RIVAL1", 7 }, -- 5 - { "jump_if_false", 12 }, -- 6 + { "jump_if_false", 13 }, -- 6 { "set_flag", "EVENT_BEAT_CERULEAN_RIVAL" }, -- 7 { "show_text", "_CeruleanCityRivalDefeatedText" }, -- 8 { "show_text", "_CeruleanCityRivalIWentToBillsText" }, -- 9 - { "play_music", "Music_MeetRival", { start = "rival" } }, - { "walk_npc", 1, ceruleanRivalExitDirs(px) }, -- 10 - { "hide_object", "CERULEAN_CITY", "CERULEANCITY_RIVAL" }, -- 11 + { "play_music", "Music_MeetRival", { start = "rival" } }, -- 10 + { "walk_npc", 1, ceruleanRivalExitDirs(px) }, -- 11 + { "play_default_music" }, -- scripts/CeruleanCity.asm:230 + { "hide_object", "CERULEAN_CITY", "CERULEANCITY_RIVAL" }, -- 13 } end @@ -880,13 +882,14 @@ M.SILPH_CO_7F = { { "face_object", 9, "up" }, -- 4 { "show_text", "_SilphCo7FRivalWaitedHereText" }, -- 5 { "rival_battle", "OPP_RIVAL2", 7 }, -- 6 - { "jump_if_false", 13 }, -- 7 + { "jump_if_false", 14 }, -- 7 { "set_flag", "EVENT_BEAT_SILPH_CO_RIVAL" }, -- 8 { "show_text", "_SilphCo7FRivalDefeatedText" }, -- 9 { "show_text", "_SilphCo7FRivalGoodLuckToYouText" }, -- 10 - { "play_music", "Music_MeetRival", { start = "rival" } }, - { "move_npc_to", 9, 5, y + 1 }, -- 11 - { "hide_object", "SILPH_CO_7F", "SILPHCO7F_RIVAL" }, -- 12 + { "play_music", "Music_MeetRival", { start = "rival" } }, -- 11 + { "move_npc_to", 9, 5, y + 1 }, -- 12 + { "play_default_music" }, -- scripts/SilphCo7F.asm:261 + { "hide_object", "SILPH_CO_7F", "SILPHCO7F_RIVAL" }, -- 14 }, "down") end, } @@ -916,13 +919,14 @@ M.SS_ANNE_2F = { { "face_object", 2, onLeft and "down" or "right" }, -- 3 { "show_text", "_SSAnne2FRivalText" }, -- 4 { "rival_battle", "OPP_RIVAL2", 1 }, -- 5 - { "jump_if_false", 12 }, -- 6 + { "jump_if_false", 13 }, -- 6 { "set_flag", "EVENT_BEAT_SS_ANNE_RIVAL" }, -- 7 { "show_text", "_SSAnne2FRivalDefeatedText" }, -- 8 { "show_text", "_SSAnne2FRivalCutMasterText" }, -- 9 - { "play_music", "Music_MeetRival", { start = "rival" } }, - { "walk_npc", 2, ssAnne2FRivalExitDirs(onLeft) }, -- 10 - { "hide_object", "SS_ANNE_2F", "SSANNE2F_RIVAL" }, -- 11 + { "play_music", "Music_MeetRival", { start = "rival" } }, -- 10 + { "walk_npc", 2, ssAnne2FRivalExitDirs(onLeft) }, -- 11 + { "play_default_music" }, -- scripts/SSAnne2F.asm:175 + { "hide_object", "SS_ANNE_2F", "SSANNE2F_RIVAL" }, -- 13 }, onLeft and "up" or "left") end, } diff --git a/docs/gold-walkthrough/asm-walk/README.md b/docs/gold-walkthrough/asm-walk/README.md deleted file mode 100644 index 0c50b07d..00000000 --- a/docs/gold-walkthrough/asm-walk/README.md +++ /dev/null @@ -1,1374 +0,0 @@ -# ASM Walk index - -One document per walkthrough section, each mapping that section's beats onto the -pokegold disassembly: map constants and asm files, verbatim warp / coord / bg / -object event tables, script control flow with the `EVENT_*` and `ENGINE_*` flags -each label reads and writes, trainer party labels, wild encounter tables, the code -that enforces each progress gate, an ordered bot checklist, and a port-coverage -audit against `src/world/gen2/`, `src/script/gen2/`, `src/core/gen2/` and -`src/battle/gen2/`. - -Written against: - -- Walkthrough source: `docs/gold-walkthrough/section-NN-*.txt` -- Disassembly: `../pokegold` (paths in these docs are relative to that checkout root) -- Symbols: `../pokegold-symbols/pokegold.sym` - -Output shape is defined in [_TEMPLATE.md](_TEMPLATE.md). - -## Sections - -### 00. [chikorita cyndaquil or totodile](section-00-chikorita-cyndaquil-or-totodile.md) - -Covers the Pokemon Gold opening: new game, InitClock time set, Oak speech and player naming, the bedroom spawn at SPAWN_HOME, Mom's Pokegear cutscene, New Bark Town and the first rival encounter, and Elm's Lab through the starter choice and the aide's Potion. Documents every warp/coord/bg/object row for the four maps, the scene-id gates that block progress, the starter givepoke data, and where each beat lands (or does not land) in this repo's Gen 2 port. - -**Maps:** `MAP_PLAYERS_HOUSE_2F`, `MAP_PLAYERS_HOUSE_1F`, `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB` - -**Gates:** - -- SCENE_ELMSLAB_CANT_LEAVE (scene 1) coord events at ElmsLab (4,6)/(5,6) -> LabTryToLeaveScript pushes the player back up; released only by ElmDirectionsScript's setscene SCENE_ELMSLAB_AIDE_GIVES_POTION -- SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU (scene 0) coord events at NewBarkTown (1,8)/(1,9) -> NewBarkTown_TeacherStopsYouScene1/2 drag the player back east; released by ElmDirectionsScript's setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP -- EVENT_GOT_A_POKEMON_FROM_ELM (constants/event_flags.asm:35) is the section's terminal flag, set by ElmDirectionsScript after any starter is taken -- InitClock (engine/rtc/timeset.asm:4) must be answered plus confirmed twice before the world is created -- MeetMomScript's DST yesorno pair loops back to .SetDayOfWeek on a 'no' confirm - the Pokegear/ENGINE_POKEGEAR flags are not set until it exits -- Aide's POTION is scene-gated (SCENE_ELMSLAB_AIDE_GIVES_POTION at ElmsLab (4,8)/(5,8)), not flag-gated -- EVERSTONE requires EVENT_SHOWED_TOGEPI_TO_ELM (ElmGiveEverstoneScript, maps/ElmsLab.asm:338-347) - unreachable in this section despite the walkthrough listing it - -**Unresolved (6):** - -- Walkthrough lists Everstone as a New Bark Town item; the only local Everstone is ElmGiveEverstoneScript gated on EVENT_SHOWED_TOGEPI_TO_ELM (hatched Togepi), many sections later -- Walkthrough says Oak greets you then asks the time; in engine/menus/intro_menu.asm:494 OakSpeech's first instruction is farcall InitClock, so the clock screen comes first -- Walkthrough treats the Pokegear as a received item; MeetMomScript only does setflag ENGINE_POKEGEAR / ENGINE_PHONE_CARD, nothing enters the bag -- 'the time will keep running even with the GBA off' is an RTC hardware claim with no disassembly counterpart -- data/maps/attributes.asm:121 gives NEW_BARK_TOWN a connection east to ROUTE_27 with no guarding coord event; whether the shoreline in maps/NewBarkTown.blk blocks it pre-Surf was not decoded -- Only (1,8) and (1,9) carry the teacher coord events; whether the west connection strip is reachable at any other y was not verified against collision data - -### 01. [cherrygrove city and routes 29 31](section-01-cherrygrove-city-and-routes-29-31.md) - -Maps the Cherrygrove/Routes 29-31 walkthrough stretch onto pokegold: every warp, coord event, bg event and object event row transcribed verbatim from maps/Route29.asm, CherrygroveCity.asm, Route30.asm, Route31.asm, Route46.asm, MrPokemonsHouse.asm and the surrounding houses/gates, plus map headers, wild tables, trainer parties and the EVENT_/ENGINE_ flags each script reads and writes. Adds a 30-step bot checklist, the enforcing code for every progress gate, and an honest port-coverage table that flags item balls as unimplemented and the MAP CARD flag as not reaching the Pokegear. - -**Milestones:** No badge earned in this section; ENGINE_MAP_CARD (Guide Gent tour, CherrygroveCityGuideGent); ENGINE_POKEDEX (MrPokemonsHouse_OakScript); MYSTERY_EGG obtained then delivered -> EVENT_GAVE_MYSTERY_EGG_TO_ELM; Rival battle 1 won (RIVAL1 id 1/2/3, level 5 starter); ENGINE_FLYPOINT_CHERRYGROVE + blackoutmod CHERRYGROVE_CITY - -**Maps:** `MAP_ROUTE_29`, `MAP_ROUTE_29_ROUTE_46_GATE`, `MAP_ROUTE_46`, `MAP_CHERRYGROVE_CITY`, `MAP_CHERRYGROVE_POKECENTER_1F`, `MAP_CHERRYGROVE_MART`, `MAP_GUIDE_GENTS_HOUSE`, `MAP_CHERRYGROVE_GYM_SPEECH_HOUSE`, `MAP_CHERRYGROVE_EVOLUTION_SPEECH_HOUSE`, `MAP_ROUTE_30`, `MAP_ROUTE_30_BERRY_HOUSE`, `MAP_MR_POKEMONS_HOUSE`, `MAP_ROUTE_31`, `MAP_ROUTE_31_VIOLET_GATE`, `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB`, `MAP_PLAYERS_HOUSE_1F`, `MAP_DARK_CAVE_VIOLET_ENTRANCE (mouth only)`, `MAP_VIOLET_CITY (next section, one line)` - -**Gates:** - -- New Bark west exit blocked pre-starter: NewBarkTown.asm coord events (1,8)/(1,9) under SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU; cleared by ElmsLab.asm:ElmDirectionsScript setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP -- Elm's Lab exit blocked pre-starter: LabTryToLeaveScript at coord events (4,6)/(5,6) under SCENE_ELMSLAB_CANT_LEAVE -- Route 30 west fork blocked by the three EVENT_ROUTE_30_BATTLE objects at (5,24)/(5,25)/(5,26); ElmAfterTheftScript sets that event (hiding them) and clears EVENT_ROUTE_30_YOUNGSTER_JOEY (spawning Joey) -- Cherrygrove east exit trip-wire: coord events (33,6)/(33,7) under SCENE_CHERRYGROVECITY_MEET_RIVAL, armed by MrPokemonsHouse_OakScript; BATTLETYPE_CANLOSE so a loss still advances -- Elm's Lab cop scene: coord events (4,5)/(5,5) under SCENE_ELMSLAB_MEET_OFFICER -> CopScript (special NameRival) -- POKE BALLs not sold until EVENT_GAVE_MYSTERY_EGG_TO_ELM: CherrygroveMartClerkScript picks MART_CHERRYGROVE vs MART_CHERRYGROVE_DEX -- Route 29 catch tutorial needs SCENE_ROUTE29_CATCH_TUTORIAL, set only by ElmAfterTheftScript -- Dark Cave unusable: engine/events/overworld.asm FlashFunction/.CheckUseFlash requires ENGINE_ZEPHYRBADGE plus DARKNESS_PALSET -- PINK BOW unobtainable here: Route29TuscanyCallback requires ENGINE_ZEPHYRBADGE and VAR_WEEKDAY == TUESDAY -- Route 31 MAPCALLBACK_NEWMAP fires SPECIALCALL_WORRIED unless EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST is set by MomScript - -**Unresolved (10):** - -- Walkthrough's Route 30 'Antidote' does not exist in maps/Route30.asm - the only ground item is a hidden POTION bg_event at (14,9) -- Walkthrough's Route 31 'Potion left of Dark Cave' is actually itemball ANTIDOTE at (29,5); no POTION object exists on Route 31 -- Youngster Mikey is L2 PIDGEY + L4 RATTATA in parties.asm, not two Rattata -- Hoppip listed for Route 29 is absent from def_grass_wildmons ROUTE_29 -- Zubat and Poliwag listed for Routes 30/31 are not in those grass tables (Poliwag is water/fish, Zubat is Dark Cave) -- Weedle/Kakuna listed for Routes 30-31 are inside the ELIF DEF(_SILVER) arm; a Gold run gets Caterpie/Metapod -- Violet City claims (Onix, TM31, PRZCure Berry) unverified - maps/VioletCity.asm belongs to the next section and was not opened -- Whether the three EVENT_ROUTE_30_BATTLE objects fully seal the west lane depends on maps/Route30.blk collision, which was not decoded -- Port gap: setflag ENGINE_MAP_CARD lands in save.engineFlags while src/ui/gen2/Pokegear.lua reads save.pokegearFlags; no bridge found by grep -- Port gap: OBJECTTYPE_ITEMBALL objects are extracted (obj.itemball) but no runtime code consumes them, so Route 29/31/46 ground items appear un-takeable - -### 02. [sprout tower and violet city gym](section-02-sprout-tower-and-violet-city-gym.md) - -Maps the Sprout Tower climb (three floors of sages, the elder/rival cutscene coord event at 3F (11,9), Sage Li and HM05 Flash) and the Violet City Gym run (Bird Keepers Abe and Rod, Falkner, ZEPHYRBADGE and TM31 Mud-Slap) onto pokegold, with verbatim warp/coord/bg/object tables, map headers, script control flow, trainer parties with verified prize money, and the Sprout Tower 2F/3F wild tables including the morn/day/nite split. Includes a 28-row bot checklist keyed to asm cell coordinates and a port-coverage audit that flags Poke Ball item pickup as the one genuinely missing runtime path. - -**Milestones:** ZEPHYRBADGE (ENGINE_ZEPHYRBADGE, set by VioletGymFalknerScript in maps/VioletGym.asm); HM05 Flash (EVENT_GOT_HM05_FLASH, verbosegiveitem HM_FLASH in SageLiScript, maps/SproutTower3F.asm); TM31 Mud-Slap (EVENT_GOT_TM31_MUD_SLAP, VioletGymFalknerScript.FightDone); Sprout Tower rival cutscene cleared (EVENT_RIVAL_SPROUT_TOWER, scene id -> SCENE_SPROUTTOWER3F_NOOP) - -**Maps:** `MAP_SPROUT_TOWER_1F`, `MAP_SPROUT_TOWER_2F`, `MAP_SPROUT_TOWER_3F`, `MAP_VIOLET_CITY`, `MAP_VIOLET_POKECENTER_1F`, `MAP_VIOLET_GYM` - -**Gates:** - -- Sage Li battle gates HM05 Flash: maps/SproutTower3F.asm:SageLiScript, checkevent EVENT_GOT_HM05_FLASH / iftrue .GotFlash; loadtrainer SAGE, LI then verbosegiveitem HM_FLASH (return NOT checked, a full bag loses the HM) -- Coord event at SPROUT_TOWER_3F (11,9) armed by SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER (=0, macro-generated in maps/SproutTower3F.asm:12) fires SproutTower3FRivalScene; it is a cutscene with no battle and self-disarms via setscene SCENE_SPROUTTOWER3F_NOOP -- Falkner one-shot: maps/VioletGym.asm:VioletGymFalknerScript, checkevent EVENT_BEAT_FALKNER / iftrue .FightDone -- Out-of-battle Flash requires BOTH ENGINE_ZEPHYRBADGE and wTimeOfDayPalset == DARKNESS_PALSET: engine/events/overworld.asm FlashFunction.CheckUseFlash (03:48f1); the .notadarkcave arm fails the move even with the badge -- ZEPHYRBADGE attack boost / obedience mask: engine/battle/core.asm:6566 -- Object visibility polarity: engine/overworld/map_objects_2.asm CheckObjectFlag masks an object when its event flag is SET; Script_disappear sets it (engine/overworld/scripting.asm:887). Applies to EVENT_RIVAL_SPROUT_TOWER and every item ball flag in this section -- Elm's aide in Violet Pokecenter stays hidden all section: EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER is set at new game (engine/events/std_scripts.asm:468) and only cleared by the SPECIALCALL_ASSISTANT phone call queued by beating Falkner (engine/phone/scripts/elm.asm:84) -- No field-move, key-item or NPC-tile blocker exists anywhere in this section; the only hard requirement is winning battles - -**Unresolved (8):** - -- Walkthrough's Sprout Tower item list says X Accuracy; the asm has only itemball X_DEFEND at SPROUT_TOWER_2F (3,1) (SproutTower2FXDefend). The prose 'X Defense' is right, the header list is wrong. -- Walkthrough claims the item right of the monk on 3F is first a Level 3 Rattata and only later an Escape Rope. Nothing supports this: the object at (14,1) is a plain OBJECTTYPE_ITEMBALL whose label SproutTower3FEscapeRope is a bare 'itemball ESCAPE_ROPE'. No wildbattle, no second object, no branch on EVENT_GOT_HM05_FLASH. Reads like a Gen 1 Rocket Hideout Voltorb import. -- Walkthrough lists Bellsprout as a Sprout Tower wild encounter. data/wild/johto_grass.asm has no SPROUT_TOWER_1F entry at all, and 2F/3F are Rattata (morn/day) and Gastly+Rattata (nite). Bellsprout only appears as sage party members. -- Sage Li's party order differs: walkthrough says Bellsprout 7 / Hoothoot 10 / Bellsprout 7; parties.asm SageGroup 'LI' is Bellsprout 7 / Bellsprout 7 / Hoothoot 10. The quoted 320G only works with the asm order (last party level x base 8 x 4). -- Gym trainer walk order (Abe then Rod) is the reverse of the object_const order in maps/VioletGym.asm (VIOLETGYM_YOUNGSTER1 = Rod, YOUNGSTER2 = Abe). Do not index by object const when following the prose. -- Walkthrough's 'you need the 1st badge to use Flash' is incomplete; the darkness-palset half of FlashFunction.CheckUseFlash is unmentioned. -- Per-Pokemon EXP figures quoted by the walkthrough (54, 108, 126, 217, ...) are computed at runtime from base stats, not stored as constants, so they were not pinned to a single asm line. All seven money figures WERE verified as base reward x last party level x 4. -- PORT GAP (not a walkthrough issue, but load-bearing): OBJECTTYPE_ITEMBALL pickup is unimplemented. src/import/RomExtractorGen2.lua:2969 writes obj.itemball and nothing in src/ reads it; World:interact() (src/world/gen2/World.lua:5257) has no itemball branch and CallAsm.lua:550 stubs TryReceiveItem out deliberately. Every Poke Ball in this section (Parlyz Heal, X Defend, Potion, Escape Rope, PP Up, Rare Candy) renders and masks correctly but cannot be picked up. - -### 03. [ruins of alph and union cave](section-03-ruins-of-alph-and-union-cave.md) - -Maps the walkthrough's Violet City egg pickup, Ruins of Alph (Kabuto puzzle, Inner Chamber Unown, Research Center #DEX upgrade), the full Route 32 trainer gauntlet, Union Cave 1F/B1F and Route 33 onto pokegold's map asm, with verbatim warp/coord/bg/object tables, map headers, trainer parties, wild tables and the exact flag checks each script performs. Includes a 39-step bot checklist, a gate table citing the enforcing code, and an honest port-coverage table for this repo's Gen 2 work. - -**Milestones:** No badge is earned in this section; Togepi EGG obtained (EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE); First Unown puzzle solved (EVENT_SOLVED_KABUTO_PUZZLE / ENGINE_UNLOCKED_UNOWNS_A_TO_K); UNOWN #DEX mode unlocked (ENGINE_UNOWN_DEX); MIRACLE_SEED, TM05 ROAR, OLD_ROD, TM39 SWIFT, POISON_BARB (Friday) - -**Maps:** `MAP_VIOLET_POKECENTER_1F`, `MAP_ROUTE_36_RUINS_OF_ALPH_GATE`, `MAP_RUINS_OF_ALPH_OUTSIDE`, `MAP_RUINS_OF_ALPH_KABUTO_CHAMBER`, `MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER`, `MAP_RUINS_OF_ALPH_INNER_CHAMBER`, `MAP_RUINS_OF_ALPH_RESEARCH_CENTER`, `MAP_ROUTE_32_RUINS_OF_ALPH_GATE`, `MAP_ROUTE_32`, `MAP_ROUTE_32_POKECENTER_1F`, `MAP_UNION_CAVE_1F`, `MAP_UNION_CAVE_B1F`, `MAP_UNION_CAVE_B2F`, `MAP_ROUTE_33` - -**Gates:** - -- Route 32 coord_event 18,8 SCENE_ROUTE32_COOLTRAINER_M_BLOCKS pushes the player back to Violet until VioletPokecenter1F's setmapscene ROUTE_32, SCENE_ROUTE32_OFFER_SLOWPOKETAIL fires on taking the Togepi EGG -- Elm's Aide only exists after maps/VioletGym.asm:38 specialphonecall SPECIALCALL_ASSISTANT lands and engine/phone/scripts/elm.asm:84 clears EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER -- MIRACLE_SEED needs both ENGINE_ZEPHYRBADGE and EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE (Route32CooltrainerMScript) -- Kabuto chamber floor holes closed by RuinsOfAlphKabutoChamberHiddenDoorsCallback until EVENT_SOLVED_KABUTO_PUZZLE -- Unown never spawn until any ENGINE_UNLOCKED_UNOWNS_* bit is set (engine/overworld/wildmons.asm:341-347); letters filtered by CheckUnownLetter (engine/battle/core.asm:6219) -- Unown #DEX scientist requires VAR_UNOWNCOUNT > 2 plus EVENT_MADE_UNOWN_APPEAR_IN_RUINS and ENGINE_UNOWN_DEX clear (RuinsOfAlphOutsideScientistCallback) -- Aerodactyl chamber needs SURF + ENGINE_FOGBADGE (engine/events/overworld.asm:490) -- Union Cave B1F boulder at 7,10 needs STRENGTH + ENGINE_PLAINBADGE (engine/events/overworld.asm:1033 TryStrengthOW) -- Union Cave Lapras and Route 32 Frieda both require VAR_WEEKDAY == FRIDAY; Lapras additionally once-ever via ENGINE_UNION_CAVE_LAPRAS -- FLASH is NOT required: all three Union Cave floors are PALETTE_NITE, not PALETTE_DARK - -**Unresolved (10):** - -- Walkthrough lists a Repel on Route 32; maps/Route32.asm has no REPEL anywhere. Likely conflated with data/items/mom_phone.asm MomItems_2 'momitem 4000, 270, MOM_ITEM, REPEL' -- Walkthrough lists Ekans on Routes 32 and 33; EKANS only appears in the ELIF DEF(_SILVER) branches of data/wild/johto_grass.asm. Gold has RATTATA in that slot -- Walkthrough claims Unown has 28 forms including ? and !; constants/pokemon_constants.asm:308 is NUM_UNOWN EQU 26 and forms stop at UNOWN_Z -- Walkthrough says 'talk to the two people inside' the Kabuto chamber; only one object_event exists (RUINSOFALPHKABUTOCHAMBER_RECEPTIONIST at 5,5) -- RuinsOfAlphOutside declares five object consts but only two object_event rows; FISHER/YOUNGSTER2/YOUNGSTER3 are dead -- Walkthrough's Union Cave species list omits SANDSHREW and ONIX, which are in the Gold table; and Route 32 Wooper is morn/nite only, absent from the day table -- Walkthrough's 'X Attack below Bill' and 'Potion to the far left' do not match the coordinates (Bill 15,15; X Attack 4,17; Potion 4,2) -- Could not locate a SPECIALCALL_* scheduler in this repo's port; without it EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER never clears and Route 32 stays permanently blocked -- Did not confirm src/battle/gen2/Encounter.lua reproduces the wUnlockedUnowns == 0 -> no encounter short-circuit from engine/overworld/wildmons.asm -- Route32Pokecenter1FFishingGuruScript sets EVENT_GOT_OLD_ROD without an iffalse bag-full guard after verbosegiveitem (asm behaviour, not a port bug) - -### 04. [slowpoke well azalea town gym](section-04-slowpoke-well-azalea-town-gym.md) - -Maps the Azalea Town / Slowpoke Well / Azalea Gym stretch onto pokegold: every warp, coord, bg and object row transcribed verbatim from maps/AzaleaTown.asm, maps/KurtsHouse.asm, maps/AzaleaPokecenter1F.asm, maps/SlowpokeWellB1F.asm, maps/SlowpokeWellB2F.asm and maps/AzaleaGym.asm, plus headers, dimensions, connections, all four Rocket grunts and six gym trainers resolved to data/trainers/parties.asm, the wild tables, and the exact EVENT_/ENGINE_ flag chain that Kurt1 and TrainerGruntM1.Script write. Adds derived collision grids decoded from the .blk files so a bot has real walkable routes and proof of the two NPC chokepoints and the Surf-only B2F. - -**Milestones:** HIVEBADGE (ENGINE_HIVEBADGE, set by AzaleaGymBugsyScript in maps/AzaleaGym.asm); EVENT_CLEARED_SLOWPOKE_WELL (set by TrainerGruntM1.Script in maps/SlowpokeWellB1F.asm); EVENT_BEAT_BUGSY; EVENT_GOT_TM49_FURY_CUTTER (TM49 Fury Cutter); EVENT_KURT_GAVE_YOU_LURE_BALL (free Lure Ball); ENGINE_FLYPOINT_AZALEA (AzaleaTownFlypointCallback) - -**Maps:** `MAP_AZALEA_TOWN`, `MAP_KURTS_HOUSE`, `MAP_AZALEA_POKECENTER_1F`, `MAP_SLOWPOKE_WELL_B1F`, `MAP_SLOWPOKE_WELL_B2F`, `MAP_AZALEA_GYM` - -**Gates:** - -- Rocket object const 2 at AzaleaTown 31,9 (EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET) is the only walkable cell reaching the Slowpoke Well ladder at 31,7; cleared by talking to Kurt once (Kurt1 first-visit arm setevent) -- Rocket object const 12 at AzaleaTown 10,16 (EVENT_SLOWPOKE_WELL_ROCKETS) is the only walkable cell adjacent to the Azalea Gym warp at 10,15; cleared by the four disappear calls in TrainerGruntM1.Script -- Field CUT requires ENGINE_HIVEBADGE - engine/events/overworld.asm CutFunction.CheckAble (03:47e1) and TryCutOW (03:5193) - so the west exit to Ilex Forest is badge-gated (HM01 itself is section 05) -- L30 obedience gated on the HIVEBADGE bit, engine/battle/core.asm:6566 -- SLOWPOKE_WELL_B2F warp at B1F 7,11 sits in a chamber whose only outside neighbour is water at 7,14: Surf-only (King's Rock, TM Rain Dance) -- Strength boulder at SlowpokeWellB1F 3,2 (jumpstd StrengthBoulderScript) - out of section, nothing required behind it - -**Unresolved (6):** - -- Walkthrough gives Bug Catcher Al 200G; asm formula (base 4 x L12 x 4, engine/battle/read_trainer_party.asm ComputeTrainerReward + the ld c,4 loop and two .DoubleReward calls at engine/battle/core.asm:2340-2361) yields 192G. All eight other money figures match exactly. -- Walkthrough calls Bugsy 'her'/'she'; AzaleaGymGuideText in maps/AzaleaGym.asm says 'his knowledge of bug #MON'. -- Walkthrough's Liz-the-Picnicker Moo Moo Milk phone call could not be located: LizPhoneCallerScript (engine/phone/scripts/trainers.asm:234-254) is generic greet/rematch/random only, and no MOOMOO string exists under engine/phone/. -- Walkthrough lists TM49 under 'Items found in Azalea Town'; it is verbosegiveitem TM_FURY_CUTTER inside AzaleaGymBugsyScript, i.e. in MAP_AZALEA_GYM. -- Walkthrough's 'up the stairs' / 'down the stairs' inside Slowpoke Well has no LADDER/STAIRCASE collision to match - the only two ladder cells on B1F are the two warps (17,15 and 7,11). The trainer order it describes does match the geometry. -- Surf-only B2F access and the claim that Josh/Al/the twins can be walked past in Azalea Gym are both derived from the .blk collision decode plus object sight fields, not asserted by any asm check (the retroactive setevent block in AzaleaGymBugsyScript.FightDone is the only direct evidence that skipping the gym trainers is an anticipated state). - -### 05. [ilex forest and goldenrod city gym](section-05-ilex-forest-and-goldenrod-city-gym.md) - -Maps the walkthrough's Azalea-rival-through-Whitney stretch onto pokegold: the Azalea Town rival trip-wire, the full ten-position Ilex Forest Farfetch'd herding branch table and the single cuttable block, Route 34 and the Day-Care, then the Goldenrod hub (underground, bike shop, radio tower quiz, dept store, game corner) and the Goldenrod Gym. Every warp, coord, bg and object row is transcribed verbatim from the map asm, with trainer parties, wild tables, badge/field-move gates and a 47-step bot checklist. - -**Milestones:** PLAINBADGE (ENGINE_PLAINBADGE) from Whitney in GOLDENROD_GYM; HM01 Cut (EVENT_GOT_HM01_CUT) from the Ilex Forest charcoal master; TM02 Headbutt (EVENT_GOT_TM02_HEADBUTT); TM12 Sweet Scent (EVENT_GOT_TM12_SWEET_SCENT); TM45 Attract (EVENT_GOT_TM45_ATTRACT); BICYCLE (EVENT_GOT_BICYCLE); COIN_CASE (EVENT_GOLDENROD_UNDERGROUND_COIN_CASE); Radio Card (ENGINE_RADIO_CARD); Rival battle 2 cleared (EVENT_RIVAL_AZALEA_TOWN); ENGINE_FLYPOINT_GOLDENROD / ENGINE_REACHED_GOLDENROD - -**Maps:** `MAP_AZALEA_TOWN`, `MAP_ILEX_FOREST_AZALEA_GATE`, `MAP_ILEX_FOREST`, `MAP_ROUTE_34_ILEX_FOREST_GATE`, `MAP_ROUTE_34`, `MAP_DAY_CARE`, `MAP_GOLDENROD_CITY`, `MAP_GOLDENROD_POKECENTER_1F`, `MAP_GOLDENROD_DEPT_STORE_1F`, `MAP_GOLDENROD_DEPT_STORE_2F`, `MAP_GOLDENROD_DEPT_STORE_3F`, `MAP_GOLDENROD_DEPT_STORE_4F`, `MAP_GOLDENROD_DEPT_STORE_5F`, `MAP_GOLDENROD_DEPT_STORE_6F`, `MAP_BILLS_FAMILYS_HOUSE`, `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`, `MAP_GOLDENROD_UNDERGROUND`, `MAP_GOLDENROD_BIKE_SHOP`, `MAP_GOLDENROD_MAGNET_TRAIN_STATION`, `MAP_RADIO_TOWER_1F`, `MAP_GOLDENROD_GAME_CORNER`, `MAP_GOLDENROD_FLOWER_SHOP`, `MAP_GOLDENROD_GYM` - -**Gates:** - -- Azalea rival coord events at (5,10)/(5,11) require SCENE_AZALEATOWN_RIVAL_BATTLE, armed by maps/SlowpokeWellB1F.asm:58 after clearing Slowpoke Well -- Ilex Forest Farfetch'd quest and apprentice only exist after EVENT_CLEARED_SLOWPOKE_WELL (SlowpokeWellB1F clears EVENT_ILEX_FOREST_FARFETCHD_1 / EVENT_ILEX_FOREST_APPRENTICE) -- Charcoal master (HM01 giver) only spawns after FarfetchdPosition9 fall-through runs `appear 13` + setevent EVENT_CHARCOAL_KILN_BOSS / EVENT_HERDED_FARFETCHD -- Ilex Forest cut tree: block $0f at block (4,12) = map (8-9,24-25); needs a party mon with CUT and ENGINE_HIVEBADGE (engine/events/overworld.asm CutFunction.CheckAble / TryCutOW) -- Whitney withholds PLAINBADGE while EVENT_MADE_WHITNEY_CRY is set; must walk the coord event at GOLDENROD_GYM (8,5) to run WhitneyCriesScript, then talk again -- Strength field move needs ENGINE_PLAINBADGE (engine/events/overworld.asm StrengthFunction.TryStrength) -- Squirtbottle at GOLDENROD_FLOWER_SHOP needs ENGINE_PLAINBADGE (FlowerShopTeacherScript) -- Route 34 beach trainers Irene/Jenn/Kate and SOFT_SAND need SURF + ENGINE_FOGBADGE -- Goldenrod Underground basement door blocked by changeblock 18,6,$3d until BASEMENT_KEY (BasementDoorScript) -- Bill / Eevee gated on EVENT_MET_BILL (set in Ecruteak); only the phone number is reachable now -- Game Corner prizes need COIN_CASE from the underground item ball at (7,25) -- Officer Keith on Route 34 only battles under checktime NITE -- Underground salon / bargain / bitter merchants gated on VAR_WEEKDAY (and MORN for the bargain merchant) - -**Unresolved (11):** - -- Bug Catcher Wayne (L8 Ledyba / L10 Paras) is not in pokegold at all - maps/IlexForest.asm has zero OBJECTTYPE_TRAINER rows; only BIKER DWAYNE on Route 8 matches the grep -- X Attack and Antidote claimed in Ilex Forest do not exist; the map holds only a REVIVE item ball at (20,32) and hidden ETHER (27,1), SUPER_POTION (17,7), FULL_HEAL (9,17) -- Walkthrough lists Weedle in Ilex Forest, but Weedle/Kakuna are the ELIF DEF(_SILVER) arm; Gold gets Caterpie/Metapod -- "Youngster Ian will want to trade phone numbers" contradicts TrainerYoungsterIan.Script, which has no askforphonenumber; only Todd and Gina register numbers on Route 34 -- Rival party order printed as Gastly/starter/Zubat; Rival1Group entries 4-6 are Gastly L12, Zubat L14, starter L16 -- "There's a PC in the corner of the Daycare" - maps/DayCare.asm has no PC bg event or object -- Dept Store 5F "TM02, TM33, TM41, TM48" is conditional: MART_GOLDENROD_5F_1 default is only TM41/TM48/TM33; TM02 needs EVENT_GOT_TM02_HEADBUTT and TM08 needs EVENT_GOT_TM08_ROCK_SMASH -- Whether the Azalea Town rival coord events at (5,10)/(5,11) can be walked around was not verified - requires decoding AzaleaTown.blk collision -- Concrete approach tiles for each Farfetch'd position (turning "do not face " into a walkable route) were not derived - needs IlexForest.blk collision -- data/wild/fish.asm has no ILEX_FOREST or ROUTE_34 rows; fishing comes from the header FISHGROUP_* and those group tables were not transcribed -- Money/EXP payouts quoted by the walkthrough are runtime-computed and were not verified against any table - -### 06. [bug catching contest and sudowoodo](section-06-bug-catching-contest-and-sudowoodo.md) - -Maps the Goldenrod-to-Violet stretch (Squirtbottle, Route 35, National Park and the Bug-Catching Contest, Routes 36/37 and Sudowoodo) onto pokegold, with verbatim warp/coord/bg/object tables, map headers, wild tables, the full contest rule set from engine/events/bug_contest/, and every trainer party resolved to its data/trainers/parties.asm line. Records the four real progress gates (PLAINBADGE for the Squirtbottle, SQUIRTBOTTLE for Sudowoodo, EVENT_FOUGHT_SUDOWOODO for TM08, weekday for the Contest) plus a Route 35 CUT tree located by parsing Route35.blk against data/collision/field_move_blocks.asm. - -**Milestones:** No badge earned in this section; SQUIRTBOTTLE key item (EVENT_GOT_SQUIRTBOTTLE); EVENT_FOUGHT_SUDOWOODO - opens Route 36 west<->east; TM08 ROCK SMASH (EVENT_GOT_TM08_ROCK_SMASH); Bug-Catching Contest run: SUN_STONE / EVERSTONE / GOLD_BERRY / BERRY, ENGINE_DAILY_BUG_CONTEST; TM04 ROLLOUT, TM28 DIG, QUICK CLAW, HARD STONE (Thursday), KENYA the SPEAROW + FLOWER MAIL - -**Maps:** `MAP_GOLDENROD_FLOWER_SHOP`, `MAP_ROUTE_35_GOLDENROD_GATE`, `MAP_ROUTE_35`, `MAP_ROUTE_35_NATIONAL_PARK_GATE`, `MAP_NATIONAL_PARK`, `MAP_NATIONAL_PARK_BUG_CONTEST`, `MAP_ROUTE_36_NATIONAL_PARK_GATE`, `MAP_ROUTE_36`, `MAP_ROUTE_37` - -**Gates:** - -- ENGINE_PLAINBADGE gates the SQUIRTBOTTLE - maps/GoldenrodFlowerShop.asm:FlowerShopTeacherScript (57:52d1) -- SQUIRTBOTTLE in the PACK gates Sudowoodo - maps/Route36.asm:SudowoodoScript (4b:61aa) checkitem SQUIRTBOTTLE; the object at cell (35,9) is a solid NPC until disappeared -- engine/events/squirtbottle.asm:_Squirtbottle (14:4763) .CheckCanUseSquirtbottle requires map ROUTE_36 and a faced object with SPRITEMOVEDATA_SUDOWOODO -- EVENT_FOUGHT_SUDOWOODO gates TM08 - maps/Route36.asm:Route36RockSmashGuyScript (4b:61f7) -- ENGINE_HIVEBADGE + CUT gates the east half of Route 35 (block $5b at block(8,3) = cells x16-17 y6-7 in maps/Route35.blk; engine/events/overworld.asm:133 CutFunction.CheckAble) -- VAR_WEEKDAY must be TUESDAY/THURSDAY/SATURDAY and ENGINE_DAILY_BUG_CONTEST clear for the Contest - Route35OfficerScriptContest (56:5e0c) / Route36OfficerScriptContest (56:67cc) -- Contest entry also needs a non-EGG, non-fainted lead and party-or-box room (VAR_PARTYCOUNT / VAR_BOXSPACE / CheckFirstMonIsEgg / ContestDropOffMons) -- VAR_PARTYCOUNT < PARTY_LENGTH gates Kenya - maps/Route35GoldenrodGate.asm:RandyScript (56:59ce) -- checktime NITE gates Officer Dirk - maps/Route35.asm:TrainerOfficerDirk -- VAR_WEEKDAY THURSDAY gates Arthur (Route36ArthurCallback 4b:619e); SUNDAY gates Sunny (Route37SunnyCallback) - -**Unresolved (11):** - -- Floria running off to fetch the Squirtbottle after being talked to on Route 36 - no such script exists in pokegold; FlowerShopFloriaScript and Route36LassScript are both text-only and the Squirtbottle is gated on ENGINE_PLAINBADGE alone. Looks like Crystal behaviour -- Contest species/levels: walkthrough says Nincada/Volbeat/Wurmple at lv28-31; data/wild/bug_contest_mons.asm has CATERPIE/WEEDLE/METAPOD/KAKUNA/BUTTERFREE/BEEDRILL/VENONAT/PARAS/SCYTHER/PINSIR at lv7-18 (VENOMOTH 30-40 sits on the unreachable -1 terminator row) -- "20 Sport Balls" - the item is PARK_BALL (constants/item_constants.asm:185), BUG_CONTEST_BALLS = 20 -- "you can win a Shiny Stone" - first place gives SUN_STONE (std_scripts.asm:346); no Shiny Stone exists in Gen 2 -- "a Scyther at full HP may be worth around 342 points" - the formula in ContestScore (04:7cbc) is confirmed but the specific number was not computed -- National Park species list includes Butterfree/Weedle/Kakuna/Beedrill/Paras/Venonat/Scyther/Pinsir; none are in def_grass_wildmons NATIONAL_PARK - they are Contest-only -- The "Routes 36 & 37" item/species lists merge two maps: the apricorns and Ledyba/Spinarak/Pidgeotto are all Route 37, which this section never enters -- "talk to Arnie for HER phone number" - ARNIE1 uses the male phone std scripts -- Liz's Route 32 rematch offer is a phone event on no map in this section; not resolved -- EVENT_FOUGHT_SUDOWOODO is set BEFORE the DRAW check and both branches disappear the tree, so fleeing or fainting it still clears the road and unlocks TM08 - contradicts "catch it or else". A mid-battle black-out was not checked -- maps/NationalParkBugContest.asm warps 2 and 4 both target destination warp 1 (lines 228, 230) rather than 2 - transcribed verbatim, not a typo on my part - -### 07. [burned tower and ecruteak city gym](section-07-burned-tower-and-ecruteak-city-gym.md) - -Maps the walkthrough's Route 37 -> Ecruteak City -> Goldenrod (Bill's Eevee) -> Dance Theater -> Burned Tower -> Ecruteak Gym stretch onto the pokegold disassembly, with verbatim warp/coord/bg/object tables, map headers and dimensions, script control-flow summaries with their EVENT_*/ENGINE_* flags, resolved trainer parties, and the Route 37 and Burned Tower wild tables. It adds derived collision maps for Burned Tower 1F and Ecruteak Gym that pin the two Rock Smash chokepoints and the exact invisible-floor zig-zag, plus a 31-step bot checklist and an honest port-coverage table. - -**Milestones:** FOGBADGE (ENGINE_FOGBADGE, set by EcruteakGymMortyScript after MORTY/MORTY1); HM03 SURF (EVENT_GOT_HM03_SURF, DanceTheaterSurfGuy after all five Kimono Girls); ITEMFINDER (EVENT_GOT_ITEMFINDER, EcruteakItemfinderGuy); TM30 SHADOW BALL (EVENT_GOT_TM30_SHADOW_BALL, EcruteakGymMortyScript.FightDone); EEVEE L20 (EVENT_GOT_EEVEE, BillScript givepoke); TIME CAPSULE (ENGINE_TIME_CAPSULE, Ecruteak Pokecenter Bill cutscene); ENGINE_FLYPOINT_ECRUTEAK (EcruteakCityFlypointCallback); EVENT_RELEASED_THE_BEASTS plus special InitRoamMons (BurnedTowerB1F ReleaseTheBeasts) - -**Maps:** `ROUTE_37`, `ECRUTEAK_CITY`, `ECRUTEAK_POKECENTER_1F`, `DANCE_THEATER`, `ECRUTEAK_ITEMFINDER_HOUSE`, `BILLS_FAMILYS_HOUSE`, `BURNED_TOWER_1F`, `BURNED_TOWER_B1F`, `ECRUTEAK_GYM`, `ECRUTEAK_TIN_TOWER_ENTRANCE` - -**Gates:** - -- EVENT_MET_BILL is set at new game and masks Bill in BILLS_FAMILYS_HOUSE; only the Ecruteak Pokecenter scene-0 cutscene (EcruteakPokcenter1FBillActivatesTimeCapsuleScript) clears it, hard-locking Eevee behind visiting Ecruteak first -- BillScript readvar VAR_PARTYCOUNT / ifequal PARTY_LENGTH -> .NoRoom: a free party slot is required for the Eevee givepoke -- DanceTheaterSurfGuy checks all five EVENT_BEAT_KIMONO_GIRL_{NAOKO,SAYO,ZUKI,KUNI,MIKI} before .GetSurf gives HM_SURF -- Field SURF needs ENGINE_FOGBADGE (engine/events/overworld.asm:340 SurfFunction.TrySurf CheckBadge; :490 TrySurfOW CheckEngineFlag) -- ROCK_SMASH (party move, no badge - AskRockSmashScript / callasm HasRockSmash) is required to smash BurnedTower1F rock at (4,3), the only route to the (10,7) centre pit and therefore to the B1F (9,5) ReleaseTheBeasts coord event -- Second BurnedTower1F rock at (16,13) gates the east loop: Firebreather Ned (16,8) and the BURN_HEAL ball (15,2) -- BURNED_TOWER_B1F has exactly one exit: the LADDER warp at (7,15); warps 1-5 sit on non-warp collisions and cannot be re-entered upward -- Ecruteak Gym invisible floor: 30 warp rows on COLL_PIT tiles all dumping to warp 3 at (4,14); only the exact zig-zag reaches Morty -- EVENT_RIVAL_BURNED_TOWER masks the Burned Tower rival and is set by maps/GoldenrodUndergroundSwitchRoomEntrances.asm:125, which also setmapscenes BURNED_TOWER_1F past the battle - Burned Tower must be done first -- Tin Tower stays sage-blocked until EcruteakGymMortyScript does setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP -- B1F Strength boulder at (17,4) needs STRENGTH plus PLAINBADGE (AskStrengthScript / TryStrengthOW); nothing required here is behind it -- Bike-shop 'keep the bicycle' call needs STATUSFLAGS2_BIKE_SHOP_CALL_F, PLAYER_BIKE, phone service, and wBikeStep >= HIGH(1024) (engine/overworld/events.asm:1267 DoBikeStep) - -**Unresolved (12):** - -- Walkthrough says the rival fight is after 'the spiral'; the asm runs BurnedTower1FRivalBattleScene as scene 0 via sdefer on map load, with the rival at (9,12) three cells from the (9,15) door -- Walkthrough says Rock Smash yields 'the HP Up at the northeastern part of the floor'; the NE 1F ball is BURN_HEAL at (15,2), the HP_UP is B1F (4,3) via the NW pits -- Rock Smash being mandatory to reach the beasts is a flood-fill result derived from BurnedTower1F.blk plus tower_collision.asm, not a line of asm; verify on emulator -- EcruteakGym (6,7) has a warp_event row but FLOOR collision (dead row); (6,4) is a PIT with no warp row (safe to stand on). Both derived from the .blk and look like one transposed source row -- Morty's second mon is L21 HAUNTER in MortyGroup, not the walkthrough's L23, so its 567 EXP figure is suspect -- DanceTheaterSurfGuy.GetSurf has no iffalse after verbosegiveitem HM_SURF, unlike every other give in the section - a failed give would still set EVENT_GOT_HM03_SURF -- Both Route 37 twins share EVENT_BEAT_TWINS_ANN_AND_ANNE; EVENT_BEAT_TWINS_ANN_AND_ANNE2 exists but is unreferenced, so only one battle ever happens -- Route 37 twins are declared SPRITE_WEIRD_TREE (consts ROUTE37_WEIRD_TREE1/2), which the new-game init variablesprites to SPRITE_SUDOWOODO; what actually renders was not verified -- BurnedTower1F item ball one-time flags are crossed: the BURN_HEAL ball carries EVENT_BURNED_TOWER_1F_X_SPEED and vice versa -- Ledge (HOP_*) traversal, which Burned Tower B1F is built on, could not be located in the port (no HOP_ matches under src/world/gen2/) -- EcruteakGymActivateRockets branches on VAR_BADGES == 6/7; unexercised on a linear run but a badge-skipping bot will trip it -- Walkthrough EXP and prize-money figures were not checked against engine/battle/ and are deliberately not reproduced - -### 08. [olivine lighthouse and cianwood city gym](section-08-olivine-lighthouse-and-cianwood-city-gym.md) - -Maps the Route 38 -> Route 39 -> Olivine City -> Olivine Lighthouse -> Route 40 -> Route 41 -> Cianwood City -> Cianwood Gym stretch onto pokegold, with verbatim warp/coord/bg/object tables for all 25 maps, full trainer party data, wild/fish/headbutt/rock-smash tables, and the flag chains behind Moomoo Farm, the Good Rod, HM04 Strength, the Jasmine -> pharmacist Secretpotion gate, Shuckie, the Storm Badge and HM02 Fly. Includes a 71-step bot checklist keyed to raw cell coordinates and an honest port-coverage table for this repo's Gen 2 engine. - -**Milestones:** Storm Badge (ENGINE_STORMBADGE) from CianwoodGymChuckScript, maps/CianwoodGym.asm - -**Maps:** `MAP_ROUTE_38_ECRUTEAK_GATE`, `MAP_ROUTE_38`, `MAP_ROUTE_39`, `MAP_ROUTE_39_BARN`, `MAP_ROUTE_39_FARMHOUSE`, `MAP_OLIVINE_CITY`, `MAP_OLIVINE_POKECENTER_1F`, `MAP_OLIVINE_GOOD_ROD_HOUSE`, `MAP_OLIVINE_CAFE`, `MAP_OLIVINE_MART`, `MAP_OLIVINE_LIGHTHOUSE_1F`, `MAP_OLIVINE_LIGHTHOUSE_2F`, `MAP_OLIVINE_LIGHTHOUSE_3F`, `MAP_OLIVINE_LIGHTHOUSE_4F`, `MAP_OLIVINE_LIGHTHOUSE_5F`, `MAP_OLIVINE_LIGHTHOUSE_6F`, `MAP_ROUTE_40`, `MAP_ROUTE_41`, `MAP_CIANWOOD_CITY`, `MAP_MANIAS_HOUSE`, `MAP_CIANWOOD_PHARMACY`, `MAP_CIANWOOD_PHOTO_STUDIO`, `MAP_CIANWOOD_POKECENTER_1F`, `MAP_CIANWOOD_LUGIA_SPEECH_HOUSE`, `MAP_CIANWOOD_GYM` - -**Gates:** - -- Surf to leave Olivine westward: engine/events/overworld.asm:490 TrySurfOW / :340 SurfFunction.TrySurf require ENGINE_FOGBADGE plus a party mon knowing SURF -- Cianwood Gym boulders (3,7)/(4,7)/(5,7): CianwoodGymBoulder -> jumpstd StrengthBoulderScript -> engine/events/overworld.asm:1038 TryStrengthOW requires ENGINE_PLAINBADGE plus STRENGTH; HM04 comes from OlivineCafeStrengthSailorScript in this section -- Secretpotion: maps/CianwoodPharmacy.asm CianwoodPharmacist checks EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS, which is only set by talking to Jasmine on OLIVINE_LIGHTHOUSE_6F -- HM02 Fly: maps/CianwoodCity.asm CianwoodCityChucksWife checks EVENT_BEAT_CHUCK -- Using Fly in the field: engine/events/overworld.asm:545 FlyFunction.TryFly requires ENGINE_STORMBADGE -- Moomoo berries: maps/Route39Barn.asm MoomooScript refuses berries until EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO; TM13 needs EVENT_HEALED_MOOMOO (7 berries) -- Shuckie: maps/ManiasHouse.asm special GiveShuckle fails on a full party -- Olivine rival cut-scene: coord_event (13,12)/(13,13) armed by SCENE_OLIVINECITY_RIVAL_ENCOUNTER = 0, disarmed by setscene SCENE_OLIVINECITY_NOOP -- Ordering trap: CianwoodGymChuckScript.FightDone sets EVENT_BEAT_BLACKBELT_YOSHI/LAO/NOB/LUNG when you take TM01, so the four blackbelts must be fought before Chuck -- Route 41 whirlpools need ENGINE_GLACIERBADGE (engine/events/overworld.asm:1077) but are NOT required to reach Cianwood - -**Unresolved (12):** - -- Beauty Olivia on Route 38 does not exist in pokegold: no OLIVIA in constants/trainer_constants.asm, no OLIVIA party, and maps/Route38.asm has only five trainer objects -- Voltorb listed as found in Olivine City: the map has no grass table and its water table (data/wild/johto_water.asm:239) is Tentacool/Tentacool/Tentacruel -- Shuckle listed for Route 41: Route 41 is absent from RockMonMaps, has TREEMON_SET_NONE, and its Route41Rock script is marked unreferenced; only Route 40 and Cianwood City carry TREEMON_SET_ROCK (10% Shuckle 15) -- Swimmer Charlie's Tentacool is level 19 in data/trainers/parties.asm, not 21 as the walkthrough says -- Bird Keeper Denis party order is 18 SPEAROW / 20 FEAROW / 18 SPEAROW, not Spearow/Spearow/Fearow -- Swimmer Kaylee (SWIMMERF 3) at ROUTE_41 (17,4) is never mentioned by the walkthrough -- Whether the lighthouse x=16/17 warp pairs are one-way drops or two-way passages cannot be settled from the warp tables (they are symmetric); it depends on TILESET_LIGHTHOUSE block collision and the .blk files, which were not decoded -- Whether Bird Keeper Theo on 3F can actually be avoided depends on the 3F .blk geometry, not the object table -- Route 41's left/right branch split is blk geometry only; no asm data pins the walkthrough's routing prose -- Phone-call beats (Arnie, Liz, Todd, Mom's doll) come from CheckPhoneCall plus data/phone/*.asm and are random; nothing in these maps schedules them -- The 3000G prize from Chuck is computed from data/trainers/attributes.asm, not written in maps/CianwoodGym.asm; arithmetic not verified -- TM23 listed under Olivine City items is Jasmine's gym reward in maps/OlivineGym.asm (section 09), not obtainable on MAP_OLIVINE_CITY - -### 09. [olivine city gym](section-09-olivine-city-gym.md) - -Maps the Olivine Gym stretch of the Gold walkthrough onto pokegold: the optional Goldenrod Underground haircut detour, the second Olivine Lighthouse climb that hands Jasmine the SECRETPOTION, and the Jasmine gym battle that yields MINERALBADGE and TM23 Iron Tail. Every cited map's warp/coord/bg/object tables are transcribed verbatim, the lighthouse ladder-vs-pit navigation graph is decoded from the .blk files against the tileset collision, and the 3500G payout and 571/1470 EXP claims are derived from the actual reward and base-exp data. - -**Milestones:** MINERALBADGE (ENGINE_MINERALBADGE, engine flag id 30); EVENT_BEAT_JASMINE (0x4c1); TM_IRON_TAIL / TM23 (EVENT_GOT_TM23_IRON_TAIL, 0x0d); EVENT_JASMINE_RETURNED_TO_GYM (0x20) - Amphy cured on Lighthouse 6F; 6th-badge trigger: GoldenrodRocketsScript clears EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER - -**Maps:** `MAP_GOLDENROD_CITY`, `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`, `MAP_GOLDENROD_UNDERGROUND`, `MAP_OLIVINE_CITY`, `MAP_OLIVINE_LIGHTHOUSE_1F`, `MAP_OLIVINE_LIGHTHOUSE_2F`, `MAP_OLIVINE_LIGHTHOUSE_3F`, `MAP_OLIVINE_LIGHTHOUSE_4F`, `MAP_OLIVINE_LIGHTHOUSE_5F`, `MAP_OLIVINE_LIGHTHOUSE_6F`, `MAP_OLIVINE_GYM` - -**Gates:** - -- EVENT_OLIVINE_GYM_JASMINE must be CLEAR for Jasmine to exist in the gym; set at new game by InitializeEventsScript (engine/events/std_scripts.asm:511), cleared only by OlivineLighthouseJasmine (maps/OlivineLighthouse6F.asm:69) -- SECRETPOTION key item required: checkitem SECRETPOTION at maps/OlivineLighthouse6F.asm:14, and the yesorno at line 29 must be answered Yes (iffalse .Refused aborts, setting nothing) -- ENGINE_STORMBADGE gates FLY at all (engine/events/overworld.asm:544-547, FlyFunction.TryFly -> CheckBadge), plus ENGINE_FLYPOINT_OLIVINE (id 70) / ENGINE_FLYPOINT_GOLDENROD (id 69) set by each town's MAPCALLBACK_NEWMAP -- Lighthouse 4F: the (13,3) ladder is a dead end - you must step into the pit at 4F (8,3)/(9,3) (block $28, PIT collision) and re-climb via 3F warp 3 at (9,5) -- Haircut is weekday-gated: GoldenrodUndergroundCheckDayOfWeekCallback + readvar VAR_WEEKDAY; the 500 older brother exists only TUE/THU/SAT, and ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT (id 89) allows one cut per day -- TM pocket must have room: verbosegiveitem TM_IRON_TAIL -> iffalse .NoRoomForIronTail leaves EVENT_GOT_TM23_IRON_TAIL unset (retryable by re-talking) - -**Unresolved (5):** - -- The exact walking path between ladders/pits on Lighthouse 3F/4F/5F was not flood-filled; the ladder and pit coordinates are decoded and verified, but the intermediate route in checklist steps 8-14 is geometric inference -- The walkthrough's 'second/middle shop' is ambiguous because GoldenrodUndergroundCheckDayOfWeekCallback hides different merchants each day; the unambiguous identifier is the 500 price (older brother at (7,14)), and the walkthrough never mentions the TUE/THU/SAT restriction -- The walkthrough implies the 500 cut is the better happiness buy; data/events/happiness_probabilities.asm + happiness_changes.asm show the younger brother's 300 cut has a strictly better jackpot row (+10 vs +5) -- The 571 / 1470 EXP figures reproduce from base-exp bytes via the standard base*level/7*3/2 formula, but the EXP-award routine itself was not opened, so the *3/2 trainer multiplier is arithmetic agreement rather than a code citation -- Jasmine's row in data/trainers/attributes.asm was located by its '; Jasmine' comment (the file has no per-class labels); the 3500G derivation depends on that comment matching constants/trainer_constants.asm ordering - -### 10. [the lake of rage and shiny gyarados](section-10-the-lake-of-rage-and-shiny-gyarados.md) - -Maps the Ecruteak -> Route 42 -> Mahogany Town -> Route 43 -> Lake of Rage stretch onto pokegold, with verbatim warp/coord/bg/object tables, the trainer party rows, the Gold/Silver-split wild tables, and the flag state that decides who is actually on each map during this section (Lance, the Lake of Rage trainers and the TM36 officer are all masked at this point). Ends on the Red Gyarados encounter (loadwildmon GYARADOS 30 + BATTLETYPE_FORCESHINY, unguarded giveitem RED_SCALE) and LakeOfRageLanceScript, which sets EVENT_DECIDED_TO_HELP_LANCE and arms SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS for the next section. - -**Milestones:** No badge in this section; ENGINE_FLYPOINT_MAHOGANY (MahoganyTownFlypointCallback); ENGINE_FLYPOINT_LAKE_OF_RAGE (LakeOfRageFlypointCallback); EVENT_LAKE_OF_RAGE_RED_GYARADOS (Red Gyarados caught or beaten); RED_SCALE obtained; EVENT_DECIDED_TO_HELP_LANCE (arms SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS); EVENT_GOT_TM10_HIDDEN_POWER; EVENT_GOT_BLACKBELT_FROM_WESLEY (Wednesdays only) - -**Maps:** `MAP_ECRUTEAK_CITY`, `MAP_ROUTE_42_ECRUTEAK_GATE`, `MAP_ROUTE_42`, `MAP_MAHOGANY_TOWN`, `MAP_MAHOGANY_MART_1F`, `MAP_MAHOGANY_RED_GYARADOS_SPEECH_HOUSE`, `MAP_MAHOGANY_POKECENTER_1F`, `MAP_ROUTE_43_MAHOGANY_GATE`, `MAP_ROUTE_43`, `MAP_ROUTE_43_GATE`, `MAP_LAKE_OF_RAGE`, `MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE`, `MAP_LAKE_OF_RAGE_MAGIKARP_HOUSE` - -**Gates:** - -- SURF across the two Route 42 lakes and onto the Lake of Rage water: engine/events/overworld.asm:322 SurfFunction .TrySurf checks ENGINE_FOGBADGE via CheckBadge (:50) -- CUT for the Route 42 apricorn trees and the Lake of Rage north-west/north-east item paths: engine/events/overworld.asm:117 CutFunction .CheckAble checks ENGINE_HIVEBADGE then CheckMapForSomethingToCut -- Mahogany east exit to Route 44 blocked unconditionally: maps/MahoganyTown.asm coord_event 19,8 and 19,9 on SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR plus the MAHOGANYTOWN_POKEFAN_M object at (19,8); only engine/events/std_scripts.asm:261 RadioTowerRocketsScript lifts it -- Mahogany Gym door (6,13) blocked by the MAHOGANYTOWN_FISHER object at (6,14) while EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM is clear; maps/TeamRocketBaseB2F.asm:303 sets it -- Route 43 gate toll: maps/Route43Gate.asm scene 0 sdefer Route43GateRocketTakeoverScript, re-armed by Route43CheckIfRocketsScene; takemoney YOUR_MONEY, 1000 runs in BOTH the rich and broke branches (soft gate, always passable) -- Lance not talkable until RedGyarados runs appear LAKEOFRAGE_LANCE (EVENT_LAKE_OF_RAGE_LANCE is set by InitializeEventsScript, engine/events/std_scripts.asm:516) -- Wesley / BLACKBELT_I gated on readvar VAR_WEEKDAY == WEDNESDAY in LakeOfRageWesleyCallback -- Lake of Rage trainers (Andre, Raymond, Aaron, Lois) and the Route43Gate TM36 officer masked by EVENT_LAKE_OF_RAGE_CIVILIANS, set at init and cleared only in maps/TeamRocketBaseB2F.asm:305 -- Mahogany Town Lass and Mahogany Mart Granny masked by EVENT_MAHOGANY_MART_OWNERS (set at init) -- Magikarp length contest and its ETHER reward gated behind EVENT_CLEARED_ROCKET_HIDEOUT in MagikarpLengthRaterScript -- RED_SCALE is handed over by an unguarded giveitem in RedGyarados: a full bag loses it silently - -**Unresolved (14):** - -- Walkthrough calls the Lake of Rage item ball TM43 Secret Power; in GS it is itemball TM_DETECT and the add_tm ladder puts DETECT at TM43 -- Walkthrough lists TM16 under 'Items found in Mahogany Town'; no TM16 appears in maps/MahoganyTown.asm (TM16 is ICY_WIND, Pryce's gym reward, a later section) -- Camper Spencer's second mon: walkthrough says L18 Sandslash, data/trainers/parties.asm:2817 says L17 -- Walkthrough places Wesley on Route 43's left path; he is object_event 4,4 on MAP_LAKE_OF_RAGE -- Walkthrough says the Route 43 Max Ether is 'to the right of Picnicker Tiffany'; ball is (12,32), Tiffany is (9,29) -- Which tiles are Cut trees / Surf entry points on Route 42 and Lake of Rage is block data (maps/Route42.blk, maps/LakeOfRage.blk) read by CheckMapForSomethingToCut, not any event table; the claim that Route 43's west grass path bypasses ROUTE_43_GATE is likewise unverified from asm -- Mom's phone call on Route 43 is a generic SPECIALCALL, not anything in maps/Route43.asm; which call fires was not pinned down -- Fisher Marvin's L15 Gyarados EXP is left '?' by the walkthrough and is a derived number, not asm data -- LakeOfRage_MapScripts' two scene scripts are commented '; unusable' and RedGyarados does setscene 0 on a map with no scene variable; a port modelling setscene as a per-map byte write should confirm this is harmless -- Route43Gate warp 4 (5,7) points at ROUTE_43 warp 3, the same as warp 3 (4,7) - transcribed as written -- RedGyarados' ifequal LOSE, .NotBeaten only skips the disappear, so giveitem RED_SCALE and appear LAKEOFRAGE_LANCE run even on a loss; reads like a cart quirk but not observed on hardware -- Port gap found while checking coverage: World.lua:4591 sets opts.battleType but World:startBattle (:4420) never reads it, so BATTLETYPE_FORCESHINY does not make the Red Gyarados shiny (only BATTLETYPE_FORCEITEM is handled, at :4576) -- Port gap: movement byte $4c (teleport_from, Lance's exit) has no case in src/script/gen2/Movement.lua decodeByte and falls through to nop -- Port gap: no tests/drivers/gold_* driver covers this stretch - -### 11. [team rocket hideout and mahogany town gym](section-11-team-rocket-hideout-and-mahogany-town-gym.md) - -Maps the Mahogany Town / Team Rocket Hideout / Mahogany Gym stretch onto pokegold: every warp, coord event, bg event and object event for the six maps, the full B1F trap and camera coordinate tables, the three-floor stairwell topology derived by flood-filling the .blk files, and the decoded Mahogany Gym ice floor with a verified slide-by-slide route to every trainer and to Pryce. Also records the trainer parties, item balls and hidden items, the flag/scene ordering that gates the two password doors and the Electrode room, and an honest port-coverage table flagging BGEVENT_IFNOTSET dispatch and ice sliding as missing. - -**Milestones:** GLACIERBADGE (ENGINE_GLACIERBADGE, badge 7, from PRYCE1 in MahoganyGymPryceScript); TM16 Icy Wind (EVENT_GOT_TM16_ICY_WIND); HM06 Whirlpool (EVENT_GOT_HM06_WHIRLPOOL, from RocketBaseElectrodeScript); EVENT_CLEARED_ROCKET_HIDEOUT + clearflag ENGINE_ROCKET_SIGNAL_ON_CH20; EVENT_LEARNED_SLOWPOKETAIL / EVENT_LEARNED_RATICATE_TAIL / EVENT_LEARNED_HAIL_GIOVANNI - -**Maps:** `MAP_MAHOGANY_TOWN`, `MAP_MAHOGANY_POKECENTER_1F`, `MAP_MAHOGANY_MART_1F`, `MAP_TEAM_ROCKET_BASE_B1F`, `MAP_TEAM_ROCKET_BASE_B2F`, `MAP_TEAM_ROCKET_BASE_B3F`, `MAP_MAHOGANY_GYM` - -**Gates:** - -- Hideout entrance: MahoganyMart1FStaircaseCallback needs EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART; the cutscene needs SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS, set by maps/LakeOfRage.asm in the previous section -- B3F Giovanni's office door (bg_event 10,9 / 11,9, TeamRocketBaseB3FLockedDoor) requires BOTH EVENT_LEARNED_SLOWPOKETAIL (GRUNTF_5 at 21,7, sight 0 so she must be talked to) and EVENT_LEARNED_RATICATE_TAIL (GRUNTM_28 at 5,15) -- B2F transmitter door (bg_event 14,12 / 15,12, TeamRocketBaseB2FLockedDoor) requires EVENT_LEARNED_HAIL_GIOVANNI from the Murkrow object at B3F 7,2 -- B2F ExecutiveF ambush coord events only exist on SCENE_TEAMROCKETBASEB2F_ROCKET_BOSS, reached only by tripping LanceHealsScript1/2 at 5,14 or 4,13 first -- Electrode room exit blocked by RocketBaseCantLeaveScript / RocketBaseLancesSideScript until all three EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_n are set -- Mahogany Gym door blocked by SPRITE_FISHER at 6,14 until EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM is set by RocketBaseElectrodeScript (object event flags MASK when set, per engine/overworld/map_objects_2.asm CheckObjectFlag) -- Whirlpool field use gated on ENGINE_GLACIERBADGE in engine/events/overworld.asm WhirlpoolFunction.TryWhirlpool (03:4da0) and TryWhirlpoolOW (03:4e41) -- Mahogany Gym floor is COLL_ICE ($23) throughout; crossing it requires the STEP_ICE forced-slide rule in engine/overworld/player_movement.asm - -**Unresolved (8):** - -- Walkthrough's floor labels are wrong after the first password: it calls the rival encounter and Giovanni's office 'Negative Floor 1' but both are on TEAM_ROCKET_BASE_B3F (left region, reached via B2F warp 2 at 3,2). Verified by flood-filling the .blk files - B2F and B3F are each split into disconnected rooms. -- Boarder Ronald's party order differs (walkthrough Seel/Seel/Dewgong, asm SEEL 24, DEWGONG 25, SEEL 24); same for GRUNTM_18 (walkthrough Rattata/Rattata/Zubat, asm RATTATA 17, ZUBAT 17, RATTATA 17). -- Walkthrough's hideout item list omits the B3F FULL_HEAL (1,12) and DIRE_HIT (3,12) item balls from its header block, and never mentions the two hidden items: REVIVE at B1F 3,11 and FULL_HEAL at B2F 26,7. -- Walkthrough implies two statues on B1F; there are five bg_event statues (24,1 / 6,1 / 24,5 / 8,15 / 22,15) with eight trigger cells, i.e. up to ten camera-grunt battles. -- EVENT_TEAM_ROCKET_BASE_POPULATION is read by all eight SecurityCamera* scripts and is the shared visibility flag of eleven base NPCs, but a full-tree grep finds nothing that ever sets it - those checkevent arms are dead and the base never empties. -- Lance's three right-hand Electrodes (22,5 / 22,7 / 22,9) are never battled; RocketElectrodeN disappears both members of a mirrored pair and the right-hand objects point at the no-op ObjectEvent script. -- Facing direction needed to read the secret switch at B1F 19,11 was inferred from collision (19,11 is WALL, 19,12 is FLOOR) rather than observed; the event is BGEVENT_READ so any adjacent facing should work. -- The Mahogany Gym slide table was produced by simulating the ice rule against the decoded MahoganyGym.blk with NPCs as blockers. It reproduces every direction string in the walkthrough but was not executed in an emulator. - -### 12. [team rocket radio tower](section-12-team-rocket-radio-tower.md) - -Maps the Team Rocket Radio Tower arc onto pokegold: the 7-badge trigger that spawns the Rockets, all five Radio Tower floors, the Basement Key trip through the Goldenrod Underground into the switch-room shutter puzzle and the Underground Warehouse, and the Card Key return to 3F/4F/5F. Transcribes every warp/coord/bg/object table verbatim, resolves all 27 trainer constants to their party data, derives the shutter puzzle from the wUndergroundSwitchPositions position table, and audits the port for what actually works. - -**Milestones:** No badge earned in this section; BASEMENT_KEY (FakeDirectorScript, RADIO_TOWER_5F); CARD_KEY (GoldenrodUndergroundWarehouseDirectorScript); EVENT_CLEARED_RADIO_TOWER + EVENT_TEAM_ROCKET_DISBANDED; RAINBOW_WING (Gold) / SILVER_WING (Silver) from RadioTower5FRocketBossScript; Rival battle 4 (RIVAL1_4_*) in the Goldenrod Underground - -**Maps:** `MAP_GOLDENROD_CITY`, `MAP_RADIO_TOWER_1F`, `MAP_RADIO_TOWER_2F`, `MAP_RADIO_TOWER_3F`, `MAP_RADIO_TOWER_4F`, `MAP_RADIO_TOWER_5F`, `MAP_GOLDENROD_UNDERGROUND`, `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`, `MAP_GOLDENROD_UNDERGROUND_WAREHOUSE`, `MAP_GOLDENROD_DEPT_STORE_B1F` - -**Gates:** - -- 7th badge: ActivateRockets -> engine/events/std_scripts.asm:255 RadioTowerRocketsScript (readvar VAR_BADGES, ifequal 7) sets ENGINE_ROCKETS_IN_RADIO_TOWER and clears EVENT_RADIO_TOWER_ROCKET_TAKEOVER -- RADIO_TOWER_2F stairs at (0,0): RADIOTOWER2F_BLACK_BELT1 at (0,1), masked by EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS (SET = hidden) -- RADIO_TOWER_3F east shutter: bg_event 14,2 BGEVENT_UP -> CardKeySlotScript, checkitem CARD_KEY, sets EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER; RadioTower3FCardKeyShutterCallback replays the changeblocks -- GOLDENROD_UNDERGROUND locked door at (18,6): BasementDoorScript, checkitem BASEMENT_KEY, sets EVENT_USED_BASEMENT_KEY -- Switch-room shutters: GoldenrodUndergroundSwitchRoomEntrances_UpdateDoors driven by wUndergroundSwitchPositions (01:d6a8); reset to 0 by MAPCALLBACK_NEWMAP on GOLDENROD_UNDERGROUND / GOLDENROD_UNDERGROUND_WAREHOUSE -- RADIO_TOWER_5F boss: coord_event 16,5 only on SCENE_RADIOTOWER5F_ROCKET_BOSS, set by FakeDirectorScript after EXECUTIVEM_3 -- EXECUTIVEM_2 at (14,1) physically guards RADIO_TOWER_4F warp 3 (12,0) to 5F -- GOLDENROD_DEPT_STORE_B1F crate layout: EVENT_RECEIVED_CARD_KEY plus EVENT_GOLDENROD_DEPT_STORE_B1F_LAYOUT_1/2/3, frozen until EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_BLOCKED_OFF is cleared -- No HM field move is required anywhere in this section - -**Unresolved (8):** - -- Walkthrough says the director gives a Clear Bell; the asm gives RAINBOW_WING (Gold) / SILVER_WING (Silver) via checkver. Clear Bell is a Crystal item. -- Walkthrough claims the shutters remember switch ORDER; wUndergroundSwitchPositions is a plain sum (1+2+3 = 3+2+1 = 6). The real difference is which doors the intermediate positions leave untouched (door 5 at 10,10). Derivation done by hand from the .PositionN arms and should be re-checked on hardware. -- Party orderings differ: walkthrough lists GRUNTF_4 as Ekans/Ekans/Gloom/Oddish (asm: 21 Ekans, 23 Oddish, 21 Ekans, 24 Gloom) and EXECUTIVEF_1 as Arbok/Murkrow/Vileplume (asm: Arbok, Vileplume, Murkrow). -- "the northwesternmost building" resolved by geometry to GoldenrodCity warp 14 at (9,5) plus the UndergroundSignNorth bg_event at (8,6); the walkthrough never names it. -- EXP and money figures in the walkthrough were not verified against the trainer class base-money table. -- EVENT_TEAM_ROCKET_DISBANDED is set only on the Gold branch of RadioTower5FRocketBossScript (line 130), not the Silver branch; its only consumer is the TIN_TOWER_1F sage object mask. Looks intentional but is flagged rather than asserted. -- verbosegiveitem BASEMENT_KEY and verbosegiveitem CARD_KEY have no iffalse bag-full guard. -- Whether (16,6) on RADIO_TOWER_3F is truly unreachable before the shutter opens was not checked against the .blk collision data. - -### 13. [ice path and blackthorn city gym](section-13-ice-path-and-blackthorn-city-gym.md) - -Maps the Route 44 -> Ice Path -> Blackthorn City -> Blackthorn Gym -> Dragon's Den stretch onto the pokegold disassembly, with verbatim warp/coord/bg/object tables for all sixteen maps, both Strength stone tables, every EVENT_*/ENGINE_* flag the route touches, all thirteen trainer parties and the wild/fish tables. It pins down that the Rising Badge is granted in DragonsDenB1F rather than the gym, that the Den crossing needs Whirlpool rather than Waterfall, and that the port's only structural gap is ice-tile sliding. - -**Milestones:** RISINGBADGE (ENGINE_RISINGBADGE, set in maps/DragonsDenB1F.asm DragonsDenB1FDragonFangScript, NOT in the gym); EVENT_BEAT_CLAIR (gym battle won, badge withheld); EVENT_GOT_HM07_WATERFALL (HM07 Waterfall, ICE_PATH_1F (31,7)); EVENT_GOT_TM24_DRAGONBREATH (TM24 Dragonbreath); ENGINE_FLYPOINT_BLACKTHORN (Fly point, BlackthornCityFlypointCallback) - -**Maps:** `MAP_ROUTE_44`, `MAP_ICE_PATH_1F`, `MAP_ICE_PATH_B1F`, `MAP_ICE_PATH_B2F_MAHOGANY_SIDE`, `MAP_ICE_PATH_B3F`, `MAP_ICE_PATH_B2F_BLACKTHORN_SIDE`, `MAP_BLACKTHORN_CITY`, `MAP_BLACKTHORN_POKECENTER_1F`, `MAP_BLACKTHORN_MART`, `MAP_BLACKTHORN_EMYS_HOUSE`, `MAP_MOVE_DELETERS_HOUSE`, `MAP_BLACKTHORN_DRAGON_SPEECH_HOUSE`, `MAP_BLACKTHORN_GYM_1F`, `MAP_BLACKTHORN_GYM_2F`, `MAP_DRAGONS_DEN_1F`, `MAP_DRAGONS_DEN_B1F` - -**Gates:** - -- Blackthorn Gym door (18,11) blocked by BLACKTHORNCITY_SUPER_NERD1 on (18,12) until EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM is set by maps/RadioTower5F.asm:111-112 (previous section) -- Dragon's Den entrance (20,1) blocked by BLACKTHORNCITY_GRAMPS1 on (20,2) until maps/BlackthornGym1F.asm:54-55 runs after EVENT_BEAT_CLAIR -- Ice Path B1F requires HM04 STRENGTH (ENGINE_PLAINBADGE, engine/events/overworld.asm:941 StrengthFunction.TryStrength) plus the four-boulder stone table in IcePathB1FSetUpStoneTableCallback.StoneTable (boulder N onto warp N+2) -- Ice Path B3F Nevermeltice gated by the SMASHABLE_ROCK at (6,6) -> jumpstd SmashRockScript; ROCK SMASH needs the move only, no badge -- Dragon's Den B1F needs SURF (ENGINE_FOGBADGE) and WHIRLPOOL (ENGINE_GLACIERBADGE, engine/events/overworld.asm:1077); single $07 whirlpool block at maps/DragonsDenB1F.blk block (5,10) = walk cells x10-11 y20-21 -- WATERFALL unusable for the whole section: engine/events/overworld.asm:618 checks ENGINE_RISINGBADGE, which this section awards last -- Dragon Fang needs a free bag slot: DragonsDenB1FDragonFangScript giveitem/.BagFullDragonFang aborts the entire badge scene silently -- Master Ball in ElmsLab gated on checkflag ENGINE_RISINGBADGE (maps/ElmsLab.asm:60); specialphonecall SPECIALCALL_MASTERBALL queued in the Den - -**Unresolved (10):** - -- Route 44 'Max Repel' east of the trainer: the asm ball at (30,8) is Route44MaxRevive -> itemball MAX_REVIVE; no Max Repel object exists on the map -- Psychic Phil's Kadabra is db 26, KADABRA in data/trainers/parties.asm PSYCHIC_T (8); walkthrough says Level 24 -- 'Protein' in the Ice Path: no PROTEIN in any of the five Ice Path map files; only IRON (B1F (5,35)) and hidden CARBOS (B2F Mahogany (0,17)) -- Clair's party order: asm ClairGroup is Dragonair, Dragonair, Dragonair, Kingdra; walkthrough puts Kingdra third -- All ice-maze direction strings ('up, left, up, right...') and all boulder push counts are .blk block geometry plus CheckIceTile, not map-asm data; not verified -- Walkthrough says Dragon's Den needs Surf and Waterfall; the asm gate is Surf and Whirlpool, and Waterfall is not even usable until the badge this trip awards -- Money rewards (832G, 1000G, 1200G, ...) are computed at runtime from base money x level; no static table located, none verified -- Route 44 and Ice Path item lists in the walkthrough omit MAX_REVIVE, hidden ELIXER, B2F Mahogany MAX_POTION, hidden MAX_POTION, hidden CARBOS and hidden ICE_HEAL -- Which 1F<->2F warp pair the walkthrough means by 'use the warps to return to the entrance' in Blackthorn Gym is ambiguous -- File is section-13 but its own heading reads '---- 19 > Ice Path and Blackthorn City Gym ----' - -### 14. [ho oh gold and lugia silver](section-14-ho-oh-gold-and-lugia-silver.md) - -Maps the walkthrough's Gold (Tin Tower / Ho-Oh) and Silver (Whirl Islands / Lugia) legendary runs onto pokegold, transcribing every warp, coord, bg and object event row for the Ecruteak Tin Tower entrance corridor, Tin Tower 1F-9F and Roof, and all eight Whirl Islands maps plus Route 41's four island doors. It pins the two one-shot legendary callbacks (RAINBOW_WING / SILVER_WING in bag plus EVENT_FOUGHT_* clear), the checkver level split (L40 in your own version, L70 in the other), every item ball and hidden item with its EVENT_* latch, the Rattata/Gastly and Krabby/Zubat/Seel wild tables, and the real badge checks behind Flash, Surf, Whirlpool, Strength and Waterfall. - -**Milestones:** No badge awarded in this section; EVENT_FOUGHT_HO_OH (Gold, TinTowerRoof:TinTowerHoOh, 5b:6913) - one-shot level 40 Ho-Oh holding SACRED_ASH; EVENT_FOUGHT_LUGIA (Silver, WhirlIslandLugiaChamber:Lugia, 47:41a0) - one-shot level 40 Lugia - -**Maps:** `MAP_ECRUTEAK_TIN_TOWER_ENTRANCE`, `MAP_ECRUTEAK_TIN_TOWER_BACK_ENTRANCE`, `MAP_TIN_TOWER_1F`, `MAP_TIN_TOWER_2F`, `MAP_TIN_TOWER_3F`, `MAP_TIN_TOWER_4F`, `MAP_TIN_TOWER_5F`, `MAP_TIN_TOWER_6F`, `MAP_TIN_TOWER_7F`, `MAP_TIN_TOWER_8F`, `MAP_TIN_TOWER_9F`, `MAP_TIN_TOWER_ROOF`, `MAP_ROUTE_41`, `MAP_WHIRL_ISLAND_NE`, `MAP_WHIRL_ISLAND_NW`, `MAP_WHIRL_ISLAND_SW`, `MAP_WHIRL_ISLAND_SE`, `MAP_WHIRL_ISLAND_CAVE`, `MAP_WHIRL_ISLAND_B1F`, `MAP_WHIRL_ISLAND_B2F`, `MAP_WHIRL_ISLAND_LUGIA_CHAMBER` - -**Gates:** - -- ENGINE_FOGBADGE - Ecruteak Gym runs `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP` (maps/EcruteakGym.asm:34), which disarms the two sage-blocking coord events at (4,7)/(5,7); the sage's own script only changes its text -- EVENT_TEAM_ROCKET_DISBANDED - TINTOWER1F_SAGE stands on warp 3 at (10,2) (maps/TinTower1F.asm:56) and is only masked once the flag is set at maps/RadioTower5F.asm:130, one opcode after verbosegiveitem RAINBOW_WING -- RAINBOW_WING in bag - TinTowerRoofHoOhCallback (checkitem) refuses to appear Ho-Oh otherwise -- SILVER_WING in bag - WhirlIslandLugiaChamberLugiaCallback (checkitem) refuses to appear Lugia otherwise -- ENGINE_FOGBADGE + SURF - engine/events/overworld.asm:322 SurfFunction, required to reach Route 41 at all -- ENGINE_GLACIERBADGE + WHIRLPOOL - engine/events/overworld.asm:1077 / :1171, the whirlpools ringing the Whirl Islands (badge 7, not 8) -- ENGINE_ZEPHYRBADGE + FLASH - engine/events/overworld.asm:271 FlashFunction, every Whirl Islands map is PALETTE_DARK (data/maps/maps.asm:136-143) -- ENGINE_PLAINBADGE + STRENGTH - WhirlIslandB1FBoulder at (23,26) jumpstd StrengthBoulderScript -- ENGINE_RISINGBADGE + WATERFALL - only to climb back UP the B2F waterfall; descending is free via COLL_WATERFALL forcing DOWN in engine/overworld/player_movement.asm .water_table -- EVENT_FOUGHT_HO_OH / EVENT_FOUGHT_LUGIA are set BEFORE startbattle, so fleeing or blacking out burns the encounter permanently - save first - -**Unresolved (9):** - -- Walkthrough says Whirlpool needs eight badges; the asm checks ENGINE_GLACIERBADGE (badge 7, Pryce) in both WhirlpoolFunction.TryWhirlpool and TryWhirlpoolOW, and nothing reads VAR_BADGES -- Walkthrough gives Ho-Oh 'Safeguard, Ancient Power, Punishment, Sacred Fire'; HoOhEvosAttacks makes a level 40 Ho-Oh know Sacred Fire, Safeguard, Gust, Recover. AncientPower is level 88 and Punishment does not exist as a Gen 2 move constant -- Walkthrough's Tin Tower items do not match: it lists a PP Up (asm has SUPER_POTION at TinTower4F (17,14)), a Max Potion and Full Heal on the bridge floor (TIN_TOWER_6F has no objects and no bg events at all), and an HP Up (8F has NUGGET and FULL_RESTORE). No PP_UP or HP_UP appears in any TinTower*.asm -- Walkthrough lists 'Max Revive x2' for the Whirl Islands; the asm has exactly one, on B2F at (6,4). It also omits B1F's Full Restore, Carbos and Nugget, the three B1F hidden items, and B2F's Full Restore and Max Elixer -- Every hop/ledge/bridge step sequence is block-layout geometry in the .blk files (TinTower4F.blk..TinTower9F.blk, WhirlIslandB1F.blk, WhirlIslandB2F.blk); I did not decode block data, so those routes are unverified. Warp coordinates are the verified part -- 'The whirlpool just northwest of Swimmer Kara' could not be pinned to an asm row: whirlpool tiles are COLL_WHIRLPOOL entries inside maps/Route41.blk with no coordinate list in maps/Route41.asm. Kara's position (44,28) is verified -- 'The Ho-Oh in-game music' is not a legendary battle theme: engine/battle/start_battle.asm has no legendary case, so the fight uses MUSIC_JOHTO_WILD_BATTLE. The distinctive track is the map's MUSIC_TIN_TOWER, and Whirl Islands play MUSIC_UNION_CAVE -- The '~2% catch rate' figure is unverified (I derived the rate formula inputs, not the shake check): base catch rate 3, Ultra Ball x2, sleep/freeze +10, and burn/poison/paralysis worth nothing due to the reproduced PokeBallEffect bug -- Port gaps found: no forced-tile movement (COLL_WATERFALL / COLL_CURRENT_*) anywhere in src/world/gen2, so the B2F waterfall descent to Lugia's chamber has no implementation; and src/battle/gen2/Catching.lua sets every Apricorn ball multiplier to a flat 1 with no caller supplying conditions, so the Heavy Ball advantage on Lugia is not reproduced. No tests/drivers/gold_* driver exercises either legendary - -### 15. [mount mortar and dark cave](section-15-mount-mortar-and-dark-cave.md) - -Maps the walkthrough's optional Mt. Mortar and Dark Cave detour onto pokegold, transcribing every warp/coord/bg/object row for the four Mt. Mortar floors and both Dark Cave halves, plus the Kiyo Tyrogue script, the Blackglasses pharmacist script, wild tables and the Flash/Surf/Waterfall/Strength/Rock Smash gates. No badge is won here; the section's only lasting rewards are the free level 10 Tyrogue and the Blackglasses, and nothing in it blocks any later section. - -**Milestones:** No badge in this section; EVENT_BEAT_BLACKBELT_KIYO (Blackbelt Kiyo, BLACKBELT_T/KIYO, L34 Hitmonlee + L34 Hitmonchan); EVENT_GOT_TYROGUE_FROM_KIYO (givepoke TYROGUE, 10 - the only Tyrogue in the game); EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE (verbosegiveitem BLACKGLASSES) - -**Maps:** `MAP_MOUNT_MORTAR_1F_OUTSIDE`, `MAP_MOUNT_MORTAR_1F_INSIDE`, `MAP_MOUNT_MORTAR_2F_INSIDE`, `MAP_MOUNT_MORTAR_B1F`, `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE`, `MAP_DARK_CAVE_VIOLET_ENTRANCE`, `MAP_MAHOGANY_TOWN`, `MAP_ROUTE_42`, `MAP_BLACKTHORN_CITY`, `MAP_ROUTE_45`, `MAP_ROUTE_31`, `MAP_ROUTE_46` - -**Gates:** - -- Darkness: all six maps are PALETTE_DARK (data/maps/maps.asm:127-130,148-149); ReplaceTimeOfDayPals .NeedsFlash (23:43e9) forces DARKNESS_PALSET unless STATUSFLAGS_FLASH_F is set. FlashFunction.CheckUseFlash (03:48f1) needs ENGINE_ZEPHYRBADGE. ResetFlashIfOutOfCave (00:2f1d) clears the bit on any ROUTE/TOWN map, so Flash must be re-cast after every exit and every Fly. -- SURF (ENGINE_FOGBADGE, SurfFunction 03:493b / TrySurfOW 03:4a06): Mt. Mortar middle entrance (ROUTE_42 warp 4 at 28,9), the 1F Outside lake, and both Dark Cave water crossings. -- WATERFALL (ENGINE_RISINGBADGE, WaterfallFunction.TryWaterfall 03:4af6 / TryWaterfallOW 03:4b5f): the only route to MOUNT_MORTAR_1F_OUTSIDE warp 4 at (17,5) -> 2F. CheckMapCanWaterfall requires facing UP with wTileUp a waterfall tile. -- STRENGTH (ENGINE_PLAINBADGE, AskStrengthScript 03:4d4e / TryStrengthOW 03:4d7b): boulders at MOUNT_MORTAR_1F_INSIDE (21,43) and MOUNT_MORTAR_B1F (9,10). BIKEFLAGS_STRENGTH_ACTIVE_F is cleared on every map load, so Strength must be re-activated per floor. -- ROCK SMASH (no badge check; HasRockSmash 03:4f7f): the four SPRITEMOVEDATA_SMASHABLE_ROCK objects in DARK_CAVE_VIOLET_ENTRANCE at (16,14), (27,6), (7,14), (36,31). -- Party count: MountMortarB1FKiyoScript does readvar VAR_PARTYCOUNT / ifequal PARTY_LENGTH, .NoRoom - a full party defers the Tyrogue (recoverable, EVENT_BEAT_BLACKBELT_KIYO stays set). -- FLY (ENGINE_STORMBADGE, engine/events/overworld.asm:545) plus ENGINE_FLYPOINT_MAHOGANY / ENGINE_FLYPOINT_BLACKTHORN for the two hops the walkthrough takes. -- Nothing in this section gates any later section - both caves are fully optional. - -**Unresolved (12):** - -- Walkthrough's Mt. Mortar item list is the HGSS list: Carbos, PP Up, Max Ether and Iron do not exist on any MountMortar*.asm map. TM40 is the right number but the move is TM_DEFENSE_CURL (constants/item_constants.asm:261), not Aerial Ace. -- 'grab the free Iron on the cliff' on 1F Inside has no matching object; nearest are MAX_REVIVE (16,10) and hidden MAX_REPEL (31,9). -- 'surf clockwise around and reach a Hyper Potion and a Max Ether' - no MAX_ETHER in Mt. Mortar. B1F pairs HYPER_POTION (31,17) with FULL_HEAL (4,16); 2F has ELIXER (8,9). Which the author meant is undetermined. -- Kiyo's 816G prize and 1012/1020 EXP figures not verified - money comes from data/trainers/attributes.asm, which was not opened. Party itself is verified. -- 'two items ... Hyper Potion and Escape Rope' in the giant room: MOUNT_MORTAR_1F_INSIDE actually holds three balls plus a hidden MAX_REPEL, though MAX_REVIVE and the hidden item are only reachable from the 2F side. -- 'west entrance and east entrance lead to each other' - all three Route 42 doors land on MOUNT_MORTAR_1F_OUTSIDE; the link runs via MOUNT_MORTAR_1F_INSIDE warp pairs 5/6 and 8/9. The .blk files were not decoded, so only the warp graph is verified, not the walkable path. -- Dark Cave Pokemon list omits DUNSPARCE (slot 7 of DARK_CAVE_VIOLET_ENTRANCE). -- Dark Cave item list places Full Heal (36,22) and Hyper Potion (35,9) on a lobe this route does not pass, omits the hidden ELIXER at (26,3), and omits the POTION (6,8) named in its own prose. -- The rock-smash TREEMON_SET_ROCK table at DARK_CAVE_VIOLET_ENTRANCE (90% Krabby L15, 10% Shuckle L15) is real and unmentioned by the walkthrough. -- MOUNT_MORTAR_1F_OUTSIDE object 2 is itemball GUARD_SPEC but its flag is EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE - verbatim disassembly naming mismatch, not a transcription error. -- Mahogany Town and Blackthorn City interiors were not opened; only their maps.asm header rows and spawn_points.asm entries. -- Port gaps found while writing: OBJECTTYPE_ITEMBALL pickup has no arm in World:interact (all 15 Poke Balls here are unobtainable in the port), ROCK SMASH has no field-move path (CallAsm.lua:526 stubs RockMonEncounter), and ESCAPE_ROPE/DIG are absent from World:useFieldItem. - -### 16. [routes 45 46 26 and 27](section-16-routes-45-46-26-and-27.md) - -Maps the post-eighth-badge stretch from Blackthorn down Routes 45 and 46, back through New Bark Town for Elm's Master Ball and Everstone, then east across Route 27 and Tohjo Falls to Route 26 and the Victory Road gate badge check. Transcribes every warp/coord/bg/object row verbatim, resolves all 20 trainers to their parties.asm entries, records the wild tables with the Gold/Silver splits, and pins each field-move and script gate to its enforcing label. - -**Milestones:** No badge earned in this section; MASTER BALL from Prof. Elm (gated on ENGINE_RISINGBADGE); EVERSTONE from Prof. Elm (gated on EVENT_SHOWED_TOGEPI_TO_ELM); TM37 SANDSTORM (lead-mon happiness >= 150); TM22 SOLARBEAM (Route 27 whirlpool island); MOON STONE (Tohjo Falls); Victory Road Gate eight-badge check passed -> SCENE_VICTORYROADGATE_NOOP - -**Maps:** `MAP_ROUTE_45`, `MAP_ROUTE_46`, `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB`, `MAP_ROUTE_27`, `MAP_TOHJO_FALLS`, `MAP_ROUTE_27_SANDSTORM_HOUSE`, `MAP_ROUTE_26`, `MAP_ROUTE_26_HEAL_HOUSE`, `MAP_DAY_OF_WEEK_SIBLINGS_HOUSE`, `MAP_VICTORY_ROAD_GATE`, `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE`, `MAP_DARK_CAVE_VIOLET_ENTRANCE`, `MAP_ROUTE_29_ROUTE_46_GATE` - -**Gates:** - -- SURF + ENGINE_FOGBADGE to cross New Bark Town -> Route 27 and the Route 27 east water (engine/events/overworld.asm TrySurfOW) -- WATERFALL + ENGINE_RISINGBADGE for Tohjo Falls (engine/events/overworld.asm TryWaterfallOW + CheckMapCanWaterfall) -- WHIRLPOOL + ENGINE_GLACIERBADGE for the Route 27 island with TM22 and Bird Keeper Jose (engine/events/overworld.asm TryWhirlpoolOW) -- Master Ball: maps/ElmsLab.asm ElmCheckMasterBall -> checkflag ENGINE_RISINGBADGE, plus a free bag slot for verbosegiveitem -- Everstone: maps/ElmsLab.asm ElmCheckEverstone -> Togepi/Togetic must be IN THE PARTY (special FindPartyMonThatSpeciesYourTrainerID), not in the PC -- TM37 SANDSTORM: maps/Route27SandstormHouse.asm SandstormHouseWoman -> special GetFirstPokemonHappiness / ifgreater 150 - 1 (first non-egg party member only) -- Victory Road Gate: maps/VictoryRoadGate.asm _VictoryRoadGateBadgeCheckScript -> readvar VAR_BADGES / ifgreater NUM_JOHTO_BADGES - 1; soft fail pushes the player one step DOWN -- SCENE_ROUTE27_FIRST_STEP_INTO_KANTO coord events at (18,10) and (19,10) are unskippable on first entry - -**Unresolved (10):** - -- Camper Quentin (27 Fearow / 30 Primeape / 30 Tauros) does not exist anywhere in pokegold; no camper object on Route 45. The unmentioned trainer there is Hiker Michael. -- The 'Nugget' the walkthrough puts on Route 45 does not exist; the only hidden item is hiddenitem PP_UP, EVENT_ROUTE_45_HIDDEN_PP_UP at (13, 80). -- Route 27 Rare Candy is at cell (53, 12), well east of both Tohjo Falls entrances, but the walkthrough places it before entering the falls. Water continuity from the landing point was not verified against Route27.blk. -- Cooltrainer Gaven's second mon is KINGLER in data/trainers/parties.asm, not Krabby. -- Cooltrainer Reena's asm party order is STARMIE / NIDOQUEEN / STARMIE, not STARMIE / STARMIE / NIDOQUEEN. -- Hiker Erik's asm party order is MACHOP / GRAVELER / MACHOP, not Machop / Machop / Graveler. -- Walkthrough wild lists are a Gold/Silver merge: Route 46 also has Jigglypuff, Tohjo Falls also has Zubat/Golbat/Raticate/Goldeen, and Arbok is Silver-only on Routes 26/27 where Gold has Sandslash and Dodrio. -- The Tohjo Falls MOON STONE item ball at (2, 6) is never mentioned by the walkthrough. -- 'Head down the other waterfall' has no engine equivalent; descent is a COLL_CURRENT_DOWN ($3b) tile. Whether the port's World:waterfallStep handles the descend direction was not verified. -- Port gap, not a walkthrough gap: OBJECTTYPE_ITEMBALL objects are extracted (obj.itemball) but nothing in src/world/ consumes them, so all nine item balls in this section are unobtainable in the port today. - -### 17. [victory road](section-17-victory-road.md) - -Documents Gold/Silver Victory Road, which is a single 10x36-block map (20x72 cells) whose "floors" are self-warps, plus the VictoryRoadGate badge check that guards it: full warp / coord / bg / object tables, the rival ambush scripts and their three starter-dependent RIVAL1 parties, all seven items, the Kanto-table wild data, and a decoded per-cell collision map that pins every ladder, pit and one-way ledge. Port coverage is data-driven through RomExtractorGen2 and the Gen 2 VM, but Poke Ball pickup and ledge hops are missing in the port and no driver walks this map. - -**Milestones:** No badge in this section; Final RIVAL1 battle won (EVENT_RIVAL_VICTORY_ROAD set, wVictoryRoadSceneID -> SCENE_VICTORYROAD_NOOP); TM26 Earthquake obtained (EVENT_VICTORY_ROAD_TM_EARTHQUAKE); North exit onto Route 23 reached; Route23FlypointCallback sets ENGINE_FLYPOINT_INDIGO_PLATEAU - -**Maps:** `MAP_VICTORY_ROAD`, `MAP_VICTORY_ROAD_GATE`, `MAP_ROUTE_23 (exit only, next section)` - -**Gates:** - -- 8 Johto badges: VictoryRoadGateBadgeCheckScript / _VictoryRoadGateBadgeCheckScript in maps/VictoryRoadGate.asm reads VAR_BADGES (engine/overworld/variables.asm:80 .CountBadges, CountSetBits over 2 bytes) and requires ifgreater NUM_JOHTO_BADGES - 1; failure applies one step DOWN. Coord event (10,11) is a mandatory chokepoint (row 11 walkable only at x=8, occupied by the officer, and x=10) -- Rival ambush: coord events (12,8) and (13,8) gated on SCENE_VICTORYROAD_RIVAL_BATTLE cover the entire corridor to the exit warp at (13,5) -- TM26 Earthquake at (3,28) is geometry-gated: the y=26..30 pocket has exactly one entrance, the COLL_PIT at (0,11) (warp 8, one-way; warp 9 at (0,27) sits on COLL_FLOOR and never fires) -- Full Restore at (18,29) is geometry-gated: the shelf is entered only via warp 7 (17,19) -> (17,33), and exited only by the one-way HOP_DOWN ledge at y=34, x=16..19 -- Reaching ladder (17,19) on the cart requires the one-way HOP_RIGHT at (8,20)/(8,21) -- No field move gate at all: no cut tree, water, boulder or whirlpool tiles, and the header palette is PALETTE_NITE not PALETTE_DARK, so Flash is not required -- VictoryRoadGate black belts at (7,5) and (12,5) physically plug the only east-west corridor (gate row 5) until EVENT_OPENED_MT_SILVER / EVENT_FOUGHT_SNORLAX (later sections) - -**Unresolved (10):** - -- FAQ says Level 34 Magneton; asm has 35 in RIVAL1 (13) and (14), 34 only in RIVAL1 (15) (the Feraligatr set) -- FAQ lists the rival's starter fourth; asm order is Sneasel, Golbat, Magneton, Haunter, Kadabra, starter last -- FAQ's 2280G prize and per-mon EXP were not verified (trainer class attribute / base money table not opened) -- FAQ wild list omits Onix (L34 and L36) and Gold's Ursaring (L33), which Silver replaces with Donphan -- FAQ item list omits the hidden Max Potion at (3,29) and conflates the itemball Full Heal (15,48) with the hidden Full Heal (3,65) -- FAQ's "stairway" has no COLL_STAIRCASE anywhere in the decoded map; only ladders, one pit and five ledges exist -- FAQ's "head back up the ladder" after TM26 has no ladder in that pocket; the two exits are HOP_RIGHT at (8,28)/(8,29) and HOP_DOWN at (2,30)/(3,30) -- X Special ball at (7,38) sits on a COLL_HOP_LEFT tile walled on both sides and above; reachable by walking north from (7,42) because ledge tiles are LAND_TILE, but worth confirming in an emulator -- Gate black belts as physical blockers is inferred from the decoded gate collision plus NPC solidity, not stated in any script -- Port object masking polarity (hidden when the event flag is SET, per CheckObjectFlag) was not tested against src/world/gen2/Npc.lua - -### 18. [pok mon league](section-18-pok-mon-league.md) - -Maps the Pokémon League stretch (Victory Road exit -> Route 23 -> Indigo Plateau Pokecenter -> Will/Koga/Bruno/Karen/Lance rooms -> Hall of Fame -> credits) onto pokegold, with verbatim warp/coord/bg/object tables, map headers, the door-lock changeblock mechanics, every EVENT_/ENGINE_/SCENE_ flag the run touches, and the five Elite Four parties with class items, DVs and the real GS prize money. Also records the port's coverage (data-driven maps/scripts/battles are in, the post-credits SPAWN_NEW_BARK respawn is coded but has no caller, and no Gold driver walks the League) and flags that the walkthrough's parties, held items, move sets and money figures are HeartGold/SoulSilver values that contradict the asm. - -**Milestones:** No badge awarded in this section; EVENT_BEAT_ELITE_4_WILL; EVENT_BEAT_ELITE_4_KOGA; EVENT_BEAT_ELITE_4_BRUNO; EVENT_BEAT_ELITE_4_KAREN; EVENT_BEAT_CHAMPION_LANCE; EVENT_BEAT_ELITE_FOUR (durable League-cleared flag, set in HallOfFameEnterScript); STATUSFLAGS_HALL_OF_FAME_F (Hall of Fame induction; unlocks Kanto Pokegear map); ENGINE_FLYPOINT_INDIGO_PLATEAU (fly point, also gates the Kanto Fly map) - -**Maps:** `MAP_ROUTE_23`, `MAP_INDIGO_PLATEAU_POKECENTER_1F`, `MAP_WILLS_ROOM`, `MAP_KOGAS_ROOM`, `MAP_BRUNOS_ROOM`, `MAP_KARENS_ROOM`, `MAP_LANCES_ROOM`, `MAP_HALL_OF_FAME` - -**Gates:** - -- 8 Johto badges at maps/VictoryRoadGate.asm:_VictoryRoadGateBadgeCheckScript (readvar VAR_BADGES / ifgreater NUM_JOHTO_BADGES - 1) - precondition set in the previous section -- Each Elite Four room seals its south door on entry: DoorLocksBehindYouScript changeblock 4,14,$2a (Lance: 4,22,$34) + EVENT_*_ROOM_ENTRANCE_CLOSED, re-applied by MAPCALLBACK_TILES - no retreat to the lobby to heal -- Each room's north door needs that member beaten: Script_Battle changeblock 4,2,$16 + EVENT_*_ROOM_EXIT_OPEN -- Lance is only reachable via the coord events at (4,5)/(5,5) gated on SCENE_LANCESROOM_APPROACH_LANCE, which only the entry lock script sets -- IndigoPlateauPokecenter1FPrepareElite4Callback (MAPCALLBACK_NEWMAP) clears all five EVENT_BEAT_ELITE_4_* and re-arms every door-lock scene - re-entering the lobby restarts the gauntlet -- Losing is a full blackout: every fight uses winlosstext , 0 with no BATTLETYPE_CANLOSE -- No HM field move is required anywhere in this section -- Hazard, not a gate: PlateauRivalBattle1/2 at (16,4)/(17,4) fire only with EVENT_BEAT_RIVAL_IN_MT_MOON set, ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT clear, and VAR_WEEKDAY in {MONDAY, WEDNESDAY} - -**Unresolved (13):** - -- Walkthrough says 'Route 32' for the map between Victory Road and the Plateau; it is MAP_ROUTE_23 (constants/map_constants.asm:322) -- Elite Four send-out orders differ: asm Will is Xatu/Jynx/Exeggutor/Slowbro/Xatu, Koga is Ariados/Venomoth/Forretress/Muk/Crobat, Bruno is Hitmontop/Hitmonlee/Hitmonchan/Onix/Machamp - the FAQ lists HGSS orders -- Prize money: FAQ quotes 4200/4400/4600/4700/5000G (100 x level, HGSS). GS pays base reward 25 x last mon level = 1050/1100/1150/1175/1250 (ComputeTrainerReward, engine/battle/read_trainer_party.asm:300) -- Held/AI items: FAQ claims Koga and Lance carry three Full Restores, Karen one, plus Sitrus Berries on Houndoom and a Dragonite. GS parties are TRAINERTYPE_MOVES (no held-item field) and the class rows are Will/Bruno MAX_POTION, Karen FULL_HEAL+MAX_POTION (no Full Restore), Koga/Champion FULL_HEAL+FULL_RESTORE. Sitrus Berry does not exist in Gen 2 -- FAQ's 'Dragon Rush' on Lance's Dragonites is a Gen 4 move; GS movesets are Thunder Wave/Twister/Thunder-or-Blizzard/Hyper Beam and Fire Blast/Safeguard/Outrage/Hyper Beam -- Per-Pokemon EXP figures in the FAQ were not checked against data/pokemon/base_stats/ and are presumed HGSS -- 'Routes 46 and 47' - ROUTE_47 does not exist in pokegold (constants/map_constants.asm has ROUTE_45 and ROUTE_46 only) -- Move Deleter claim: maps/MoveDeletersHouse.asm exists and is reached from maps/BlackthornCity.asm, but its script was not read -- The Indigo Plateau 'Abra' object is declared SPRITE_JYNX (maps/IndigoPlateauPokecenter1F.asm:324) with script AbraScript and cry ABRA; no SPRITE_ABRA exists and no variablesprite rewrites it in std_scripts.asm's init block -- EVENT_LANCES_ROOM_EXIT_OPEN is read by LancesRoomDoorsCallback and cleared by the lobby callback but never set by any script; the champion's door is opened by a bare changeblock 4,0,$0b that does not survive a reload -- LancesRoomLanceScript re-sets EVENT_LANCES_ROOM_ENTRANCE_CLOSED after the battle, where EVENT_LANCES_ROOM_EXIT_OPEN was apparently intended -- PlateauRivalPostBattle's setscene writes 0 over 0 (the map declares one scene), so the rival coord events stay armed; only the engine flag and weekday check actually gate the fight -- Port gap: HallOfFame.consumePostGameSpawn (src/core/gen2/HallOfFame.lua:254) is unit-tested but has no caller in src/, so the post-credits SPAWN_NEW_BARK respawn does not happen in the port - -### 19. [s s aqua and vermilion city gym](section-19-s-s-aqua-and-vermilion-city-gym.md) - -Maps the walkthrough's post-Hall-of-Fame stretch (Elm's S.S. Ticket, the Fast Ship S.S. Aqua crossing from Olivine to Vermilion, and the Vermilion City gym) onto the pokegold disassembly, with verbatim warp/coord/bg/object tables for all fourteen maps involved, the script control flow and EVENT_*/ENGINE_* flags each beat reads and writes, resolved trainer parties, and the wild/fishing tables at both ports. Also records the exact cut-tree block and collision quad in Vermilion City, which contradicts the walkthrough's claim that the tree blocks the gym. - -**Milestones:** THUNDERBADGE (ENGINE_THUNDERBADGE) from LT_SURGE/LT_SURGE1 in VermilionGymSurgeScript; S_S_TICKET from ElmGiveTicketScript (EVENT_GOT_SS_TICKET_FROM_ELM); METAL_COAT from SSAquaMetalCoatAndDocking (EVENT_GOT_METAL_COAT_FROM_GRANDPA_ON_SS_AQUA); RARE_CANDY from PokemonFanClubChairmanScript (EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT); ENGINE_FLYPOINT_VERMILION via VermilionPortFlypointCallback / VermilionCityFlypointCallback; First Kanto crossing completed: EVENT_FAST_SHIP_FIRST_TIME set by VermilionPortLeaveShipScript - -**Maps:** `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB`, `MAP_OLIVINE_CITY`, `MAP_OLIVINE_PORT_PASSAGE`, `MAP_OLIVINE_PORT`, `MAP_FAST_SHIP_1F`, `MAP_FAST_SHIP_CABINS_NNW_NNE_NE`, `MAP_FAST_SHIP_CABINS_SW_SSW_NW`, `MAP_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN`, `MAP_FAST_SHIP_B1F`, `MAP_VERMILION_PORT`, `MAP_VERMILION_PORT_PASSAGE`, `MAP_VERMILION_CITY`, `MAP_POKEMON_FAN_CLUB`, `MAP_VERMILION_GYM` - -**Gates:** - -- EVENT_BEAT_ELITE_FOUR required before ProfElmScript reaches ElmGiveTicketScript (set by maps/HallOfFame.asm:33) -- checkitem S_S_TICKET in OlivinePortWalkUpToShipScript / OlivinePortSailorAfterHOFScript gates boarding -- EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME must be clear for the boarding sailor to exist (HallOfFame.asm:38); before that OlivinePortSailorBeforeHOFScript refuses entry -- VAR_WEEKDAY gate on repeat trips: Monday/Friday from Olivine, Wednesday/Sunday from Vermilion; bypassed on the maiden voyage via EVENT_FAST_SHIP_FIRST_TIME -- FastShipB1F sailor object at (30,6)/(31,6) plus coord events (30,7)/(31,7) under SCENE_FASTSHIPB1F_SAILOR_BLOCKS; cleared by FastShipLazySailorScript setmapscene SCENE_FASTSHIPB1F_NOOP -- EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR set at new game hides the lazy sailor until FastShipB1FSailorScript clears it -- Ship will not dock on the maiden voyage until SSAquaMetalCoatAndDocking sets EVENT_FAST_SHIP_HAS_ARRIVED / EVENT_FAST_SHIP_FOUND_GIRL; FastShip1FSailor1Script checks EVENT_FAST_SHIP_HAS_ARRIVED and FastShipBed.CanArrive needs FOUND_GIRL or FIRST_TIME -- PokemonFanClubChairmanScript yesorno: answering No skips the RARE_CANDY entirely -- ENGINE_THUNDERBADGE checkflag makes VermilionGymSurgeScript one-shot -- ENGINE_HIVEBADGE + CUT required for the Vermilion City cut tree (engine/events/overworld.asm:133/:169) - optional, only exposes the hidden FULL_HEAL -- special SnorlaxAwake blocks VermilionCity warp 10 to DIGLETTS_CAVE (out of scope for this section) - -**Unresolved (10):** - -- Walkthrough says a tree blocks the Vermilion Gym; the only cut tree is TILESET_KANTO block $35 at block (6,9), CUT_TREE quadrant cell (13,18), while the gym door is cell (10,19) with FLOOR tiles south of it - the tree gates nothing but the hidden FULL_HEAL at (12,19) -- "Meal Coat" is a typo for METAL_COAT -- Lt. Surge party order differs: asm is Raichu/Electrode/Magneton/Electrode/Electabuzz, walkthrough lists Raichu/Magneton/Electabuzz/Electrode/Electrode -- Juggler Fritz party order differs: asm is Mr. Mime/Magmar/Machoke, walkthrough says Mr. Mime/Machoke/Magmar -- "Lt. Surge has a Full Restore" could not be confirmed - his party entry is TRAINERTYPE_MOVES and carries no held item; AI item lists in data/trainers/attributes.asm were not opened -- Prize money (1056G, 4600G, etc.) and EXP values are runtime calculations, not table literals; not reproduced -- EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT_BUT_BAG_WAS_FULL is read by PokemonFanClubChairmanScript but never set anywhere found by grep -- "New Pokemon music for Kanto" - the Vermilion maps use MUSIC_VERMILION_CITY; not checked against the audio engine -- FastShipB1F prose route order ("head left, then down") was not re-walked tile by tile -- Water/fishing encounter handling at the ports was not verified against src/battle/gen2/Encounter.lua in this pass - -### 20. [saffron city gym](section-20-saffron-city-gym.md) - -Maps the walkthrough's Route 6 -> Saffron City -> Saffron Gym stretch onto the pokegold disassembly, with verbatim warp/bg/object tables for all eleven maps, the full 30-pad Saffron Gym teleporter graph resolved to landing coordinates, and the scripts, flags, items and trainer parties each beat depends on. Ends with a 23-step bot checklist, the gate list (Underground Path NPC body, Magnet Train power event, gym maze), and an honest port-coverage audit against src/world/gen2, src/script/gen2 and src/battle/gen2. - -**Milestones:** MARSHBADGE (ENGINE_MARSHBADGE, constants/engine_flags.asm:52, set by SaffronGymSabrinaScript at maps/SaffronGym.asm:35); ENGINE_FLYPOINT_SAFFRON unlocked by SaffronCityFlypointCallback (MAPCALLBACK_NEWMAP, maps/SaffronCity.asm:17); EVENT_BEAT_SABRINA plus the four gym-trainer flags force-set by the Sabrina script - -**Maps:** `MAP_ROUTE_6`, `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE`, `MAP_ROUTE_6_SAFFRON_GATE`, `MAP_SAFFRON_CITY`, `MAP_MR_PSYCHICS_HOUSE`, `MAP_SILPH_CO_1F`, `MAP_SAFFRON_MAGNET_TRAIN_STATION`, `MAP_SAFFRON_MART`, `MAP_FIGHTING_DOJO`, `MAP_SAFFRON_GYM`, `MAP_SAFFRON_POKECENTER_1F` - -**Gates:** - -- Underground Path shut: ROUTE6_POKEFAN_M object body at Route 6 (17,4) sits on the door at (17,3); masked only when EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH is set by maps/PowerPlant.asm:164 (CheckObjectFlag, engine/overworld/map_objects_2.asm:32) -- Magnet Train dead: SaffronMagnetTrainStationOfficerScript checks EVENT_RESTORED_POWER_TO_KANTO then checkitem PASS (maps/SaffronMagnetTrainStation.asm:19,30); both false in this section -- Silph Co upper floors permanently blocked: SILPHCO1F_OFFICER at (13,1) with event flag -1 and no stairs warp in SilphCo1F's def_warp_events -- Saffron Gym maze: 30 teleport-panel warps (maps/SaffronGym.asm:296-326); the only pad reaching Sabrina is (1,5) -> (11,9), the only exit is (11,9) -> (1,5). No badge or flag check anywhere in the gym -- MARSHBADGE re-fight guard: checkflag ENGINE_MARSHBADGE / iftrue .FightDone at maps/SaffronGym.asm:17 -- No HM field move is required anywhere in this section - -**Unresolved (7):** - -- Psychic Jared's third Pokemon is L35 EXEGGCUTE in data/trainers/parties.asm:2573, not L32 as the walkthrough claims (asm contradicts walkthrough) -- Per-Pokemon EXP figures (765, 1237, 1149, 735, 672, 720, 1264, 1941, 1339, 1912) were not checked against data/pokemon/base_stats/ -- 'PokeMart in the upper-right corner' - the mart door is warp 3 at (25,11) on a 40x36-cell map, north-of-centre rather than the corner -- 'Silph Co, the multi-story building above the Pokemon Center' - Silph warp is (18,21), Pokecenter (9,29), so north-east not above; SilphCo1F has no stairs warp and no upper floor exists in Gen 2 -- 'You now have 10 badges' - ENGINE_MARSHBADGE is bit 5 of wKantoBadges, the 6th Kanto badge in flag order; the count of 10 depends on the FAQ's chapter order, which nothing in the asm enforces -- The vague mid-maze heal route ('left/right whichever way works till you find Franklin') was not reduced to a pad sequence; the post-heal route it gives does resolve and is recorded -- The 'too weak to move your own boulders' taunt has no counterpart in Route6PokefanMText, which only mentions the Power Plant - -### 21. [power plant and cerulean city gym](section-21-power-plant-and-cerulean-city-gym.md) - -Maps the Kanto Machine Part chain and the Cerulean Gym onto pokegold: Route 5 (Cleanse Tag house) north to Cerulean, east across Route 9 to Route 10 North, Surf to the Power Plant, then the grunt cutscene in Cerulean Gym, the Route 24 Rocket battle, Route 25's six-pack trainers plus Kevin, Bill's grandpa, and finally Misty for CASCADEBADGE. Every warp/coord/bg/object table is transcribed verbatim, with the EVENT_TRAINERS_IN_CERULEAN_GYM / EVENT_CERULEAN_GYM_ROCKET / EVENT_ROUTE_24_ROCKET / EVENT_ROUTE_25_MISTY_BOYFRIEND state machine traced through InitializeEventsScript, PowerPlantManager, CeruleanGymGruntRunsOutScript and Route25MistyDate1Script. - -**Milestones:** CASCADEBADGE (ENGINE_CASCADEBADGE, set by CeruleanGymMistyScript in maps/CeruleanGym.asm); EVENT_BEAT_MISTY; EVENT_MET_MANAGER_AT_POWER_PLANT (starts the Machine Part chain); EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM; EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM (hidden MACHINE_PART obtained at CeruleanGym 3,8); EVENT_CLEARED_NUGGET_BRIDGE (Kevin's Nugget on Route 25); EVENT_GOT_CLEANSE_TAG - -**Maps:** `MAP_ROUTE_5`, `MAP_ROUTE_5_SAFFRON_GATE`, `MAP_ROUTE_5_CLEANSE_TAG_HOUSE`, `MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE`, `MAP_CERULEAN_CITY`, `MAP_CERULEAN_GYM`, `MAP_ROUTE_9`, `MAP_ROUTE_10_NORTH`, `MAP_POWER_PLANT`, `MAP_ROUTE_24`, `MAP_ROUTE_25`, `MAP_BILLS_HOUSE` - -**Gates:** - -- CUT at the Route 9 west entrance and the Route 25 tree above Super Nerd Pat: engine/events/overworld.asm:117 CutFunction .CheckAble checks ENGINE_HIVEBADGE then CheckCutCollision (engine/overworld/tile_events.asm:76) -- SURF to reach the Power Plant door at ROUTE_10_NORTH warp 2 (3,9): engine/events/overworld.asm:322 SurfFunction .TrySurf checks ENGINE_FOGBADGE; overworld A-press path TrySurfOW at :469 -- FLY back to Cerulean twice: engine/events/overworld.asm:529 FlyFunction checks ENGINE_STORMBADGE at :545; destination needs ENGINE_FLYPOINT_CERULEAN set by CeruleanCityFlypointCallback -- Cerulean Gym is empty until Route 25: all gym object rows carry EVENT_TRAINERS_IN_CERULEAN_GYM, set by InitializeEventsScript (engine/events/std_scripts.asm:550), cleared only by Route25MistyDate1Script/Route25MistyDate2Script (maps/Route25.asm:48/:74) -- Cerulean Gym Rocket grunt does not exist until PowerPlantManager clears EVENT_CERULEAN_GYM_ROCKET and does setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT (maps/PowerPlant.asm:146-148) -- Route 24 grunt does not exist until CeruleanGymGruntRunsOutScript clears EVENT_ROUTE_24_ROCKET (maps/CeruleanGym.asm:47) -- Route 25 Misty date coord events at (42,6)/(42,7) are inert until setmapscene ROUTE_25, SCENE_ROUTE25_MISTYS_DATE (maps/CeruleanGym.asm:50) and EVENT_ROUTE_25_MISTY_BOYFRIEND is cleared -- Hidden MACHINE_PART at CeruleanGym bg_event 3,8 yields nothing until PowerPlantManager clears EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM (maps/PowerPlant.asm:147) -- Route 5 Underground Path door (17,15) blocked by the Pokefan standing on 17,16; only cleared post-section by setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH in PowerPlantManager .FoundMachinePart (maps/PowerPlant.asm:164) -- Bill's grandpa hands over one stone per map load: EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1 forces .JustShowedSomething until the player leaves and re-enters - -**Unresolved (9):** - -- Cut tree coordinates on Route 9 and Route 25 are block data in maps/Route9.blk / maps/Route25.blk, not events; no coordinate exists in the asm text to cite -- The walkthrough omits Kevin's battle entirely - TrainerCooltrainermKevin gives the NUGGET then fights with L38 RHYHORN / L35 CHARMELEON / L35 WARTORTLE -- Walkthrough wild lists are abridged: Route 24 also has VENONAT/SUNKERN/ODDISH/VENOMOTH, Route 25 also has PIDGEY/PIDGEOTTO/VENONAT/WEEPINBELL/ODDISH/VENOMOTH, and Route 5's table is not listed at all -- The exact surf path across the ROUTE_9 / ROUTE_10_NORTH connection to the Power Plant door was not traced (block data) -- Per-mon EXP numbers in the walkthrough have no asm source; money values were all reproduced as base reward x last-mon level x 4 and check out -- '11th gym badge' is walkthrough prose - the asm has only the ENGINE_CASCADEBADGE bit, no ordinal -- Route 5 'old Day Care Center' is ROUTE_5_CLEANSE_TAG_HOUSE, whose sign reads 'House for Sale... Nobody lives here'; nothing in Gen 2 calls it a Day Care -- maps/Route5.asm bg_event 10,11 (HouseForSaleSign) shares its tile with warp_event 10,11 into the Cleanse Tag house - bg-event vs warp priority worth checking in the port -- maps/PowerPlant.asm names the fifth NPC POWERPLANT_GYM_GUIDE3 but its script is PowerPlantGymGuide4Script; there is no PowerPlantGymGuide3Script - -### 22. [rock tunnel and celadon city gym](section-22-rock-tunnel-and-celadon-city-gym.md) - -Maps the Rock Tunnel / Celadon Gym stretch of the Gold walkthrough onto pokegold: verbatim warp, coord, bg and object tables for every map from the Power Plant reward through Erika's gym, plus the machine-part -> EXPN card -> LOST_ITEM -> PASS flag chain, all trainer parties, the Rock Tunnel and Route 7/8 wild tables, and the exact field-move badge checks. Ends with a 45-step bot checklist, a port-coverage matrix against src/world/gen2 and src/script/gen2, and ten walkthrough claims that the asm contradicts or does not support. - -**Milestones:** RAINBOWBADGE (ENGINE_RAINBOWBADGE, set by CeladonGymErikaScript in maps/CeladonGym.asm) - -**Maps:** `MAP_POWER_PLANT`, `MAP_ROUTE_10_NORTH`, `MAP_ROCK_TUNNEL_1F`, `MAP_ROCK_TUNNEL_B1F`, `MAP_ROUTE_10_SOUTH`, `MAP_LAVENDER_TOWN`, `MAP_LAV_RADIO_TOWER_1F`, `MAP_SOUL_HOUSE`, `MAP_ROUTE_8`, `MAP_ROUTE_8_SAFFRON_GATE`, `MAP_SAFFRON_CITY`, `MAP_SAFFRON_MAGNET_TRAIN_STATION`, `MAP_COPYCATS_HOUSE_1F`, `MAP_COPYCATS_HOUSE_2F`, `MAP_VERMILION_CITY`, `MAP_POKEMON_FAN_CLUB`, `MAP_ROUTE_7_SAFFRON_GATE`, `MAP_ROUTE_7`, `MAP_CELADON_CITY`, `MAP_CELADON_MANSION_1F`, `MAP_CELADON_MANSION_2F`, `MAP_CELADON_MANSION_3F`, `MAP_CELADON_MANSION_ROOF`, `MAP_CELADON_MANSION_ROOF_HOUSE`, `MAP_CELADON_DEPT_STORE_1F`, `MAP_CELADON_DEPT_STORE_2F`, `MAP_CELADON_DEPT_STORE_3F`, `MAP_CELADON_DEPT_STORE_4F`, `MAP_CELADON_DEPT_STORE_5F`, `MAP_CELADON_DEPT_STORE_6F`, `MAP_CELADON_GAME_CORNER`, `MAP_CELADON_GAME_CORNER_PRIZE_ROOM`, `MAP_CELADON_CAFE`, `MAP_CELADON_GYM` - -**Gates:** - -- Rock Tunnel darkness: PALETTE_DARK in data/maps/maps.asm:156-157 + FlashFunction.CheckUseFlash (engine/events/overworld.asm:271) requires ENGINE_ZEPHYRBADGE and wTimeOfDayPalset == DARKNESS_PALSET -- Surf to the Power Plant / across Route 10 water: SurfFunction badge check ENGINE_FOGBADGE (engine/events/overworld.asm:340) -- Cut generally: CutFunction.CheckAble ENGINE_HIVEBADGE (engine/events/overworld.asm:133) + CheckCutCollision against data/collision/field_move_blocks.asm CutTreeBlockPointers.kanto -- TM07 Zap Cannon: PowerPlantManager requires EVENT_RETURNED_MACHINE_PART or MACHINE_PART in bag; sets EVENT_GOT_TM07_ZAP_CANNON and EVENT_RESTORED_POWER_TO_KANTO -- EXPN CARD: LavRadioTower1FGentlemanScript requires EVENT_RETURNED_MACHINE_PART, sets ENGINE_EXPN_CARD -- LOST_ITEM from PokemonFanClubClefairyGuyScript requires EVENT_RETURNED_MACHINE_PART AND EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM (set by talking to Copycat first) -- PASS from Copycat.ReturnLostItem requires LOST_ITEM in bag; sets EVENT_GOT_PASS_FROM_COPYCAT -- Magnet Train: SaffronMagnetTrainStationOfficerScript requires EVENT_RESTORED_POWER_TO_KANTO then checkitem PASS -- TM03 Curse: CeladonMansionRoofHousePharmacistScript requires checktime NITE -- Game Corner prizes: checkitem COIN_CASE, checkcoins price, and VAR_PARTYCOUNT != PARTY_LENGTH for the mon counter -- Beating Erika retroactively sets EVENT_BEAT_LASS_MICHELLE / TANYA / JULIA / TWINS_JO_AND_ZOE, so gym trainers cannot be fought afterwards -- TM19 Giga Drain is handed out in the same conversation as the badge; a full pack silently skips it (EVENT_GOT_TM19_GIGA_DRAIN stays clear, retry by talking again) - -**Unresolved (10):** - -- Walkthrough says Celadon Gym needs HM01 Cut; maps/CeladonCity.blk has exactly one kanto cut-tree block ($60 at block (14,17) = cell (28,34)), nowhere near the gym door (block $12 at (5,14), approached from walkable $79 at (5,15)). No Cut gate found. -- Walkthrough's Route 7 wild list (Pidgeotto/Vulpix/Meowth) does not match data/wild/kanto_grass.asm def_grass_wildmons ROUTE_7 IF DEF(_GOLD) (Rattata/Spearow/Growlithe/Raticate/Murkrow/Houndour); Meowth+Vulpix are the _SILVER branch. -- Walkthrough omits four Rock Tunnel items present in asm: ELIXER ball at 1F (4,18), hidden X_ACCURACY at 1F (24,4), hidden X_DEFEND at 1F (21,15), hidden MAX_POTION at B1F (4,14). -- Walkthrough says Erika carries three Full Restores; data/trainers/attributes.asm:125 gives the Erika class 'db HYPER_POTION, NO_ITEM'. -- Erika's party order in parties.asm:331 is Tangela/Jumpluff/Victreebel/Bellossom, not the walkthrough's Tangela/Victreebel/Bellossom/Jumpluff. -- Bellossom's Synthesis healing fraction (walkthrough claims half HP per turn) is weather-dependent in engine/battle/effect_commands.asm and was not pinned down. -- Walkthrough describes a 'Rail Pass' then a separate 'Magnet Train Pass'; the asm has one item, PASS, given once by Copycat.GivePass. -- 'The Saffron City guard will check your Pokedex as ID' - Route7SaffronGuardScript has no checkitem, no coord event and no blocking movement; the gate is passable regardless. -- maps/CeladonGameCorner.asm object/bg event rows (individual slot machines) were not transcribed; only its two warps. -- Quoted EXP and prize-money figures (e.g. 1447 EXP, 1120G) were not verified - they are computed at runtime, not stored in a table. - -### 23. [routes 11 19 and fuchsia city gym](section-23-routes-11-19-and-fuchsia-city-gym.md) - -Maps the Kanto south-east loop (Route 12 south from Lavender, the Route 11 spur, Routes 13-15 into Fuchsia, the Route 16/17/18 Cycling Road, and Fuchsia Gym) onto pokegold, with every def_warp_events / def_coord_events / def_bg_events / def_object_events row transcribed verbatim plus map headers, dimensions and connections. Adds cut-tree walk cells derived by scanning each .blk against CutTreeBlockPointers, full trainer party data, the wild/fishing tables, the two BICYCLE gate coord events, and the Janine gym script's flag cascade. - -**Milestones:** ENGINE_SOULBADGE (Kanto badge 5) from FuchsiaGymJanineScript in maps/FuchsiaGym.asm; EVENT_GOT_TM06_TOXIC -> TM_TOXIC (item $c5) from Janine; EVENT_GOT_SUPER_ROD -> SUPER_ROD from Route12SuperRodHouseFishingGuruScript; ENGINE_FLYPOINT_FUCHSIA set by FuchsiaCityFlypointCallback (MAPCALLBACK_NEWMAP) - -**Maps:** `MAP_ROUTE_12`, `MAP_ROUTE_11`, `MAP_ROUTE_12_SUPER_ROD_HOUSE`, `MAP_ROUTE_13`, `MAP_ROUTE_14`, `MAP_ROUTE_15`, `MAP_ROUTE_15_FUCHSIA_GATE`, `MAP_FUCHSIA_CITY`, `MAP_FUCHSIA_POKECENTER_1F`, `MAP_ROUTE_16`, `MAP_ROUTE_16_GATE`, `MAP_ROUTE_16_FUCHSIA_SPEECH_HOUSE`, `MAP_ROUTE_17`, `MAP_ROUTE_17_ROUTE_18_GATE`, `MAP_ROUTE_18`, `MAP_FUCHSIA_GYM` - -**Gates:** - -- CUT (ENGINE_HIVEBADGE) required at walk cells Route12 (7,44)/(7,49), Route13 (44,4), Route14 (5,8)/(11,14)/(3,24), Route16 (15,4), FuchsiaCity (16,11)/(18,19) - enforced by CutFunction.CheckAble (engine/events/overworld.asm:117) and TryCutOW (:1741) -- BICYCLE in bag required at Route16Gate coord events (5,4)/(5,5), scene 0 - Route16GateBicycleCheck (maps/Route16Gate.asm:16) -- BICYCLE in bag required at Route17Route18Gate coord events (5,4)/(5,5), scene 0 - Route17Route18GateBicycleCheck -- ENGINE_ALWAYS_ON_BIKE + ENGINE_DOWNHILL forced on all of Route 17 (Route17AlwaysOnBikeCallback): no dismount, no Surf, idle frames auto-step DOWN -- ENGINE_ALWAYS_ON_BIKE conditionally set on Route 16 unless VAR_YCOORD < 5 or VAR_XCOORD > 13 (Route16AlwaysOnBikeCallback, MAPCALLBACK_NEWMAP only) -- Vermilion Snorlax (EVENT_VERMILION_CITY_SNORLAX, maps/VermilionCity.asm:41) blocks the Route 11 west connection until special SnorlaxAwake returns true - out of scope for this section -- Safari Zone permanently shut: FuchsiaCity warp 7 at (18,3) is commented ; inaccessible and no script or flag ever opens it -- ENGINE_SOULBADGE gates re-fighting Janine (FuchsiaGymJanineScript checkflag), and EVENT_GOT_TM06_TOXIC gates the TM06 handout separately from the badge - -**Unresolved (9):** - -- Route 19 is in the section title but the walkthrough never enters MAP_ROUTE_19 or MAP_ROUTE_19_FUCHSIA_GATE -- Walkthrough says 'cut the tree on your way west' on Route 15, but maps/Route15.blk contains none of the five TILESET_KANTO cut-tree block ids; the nearest real tree is the Route 14 one at cell (3,24) -- Walkthrough includes a 'Strategy VERSUS Espeon' paragraph for Janine; Janine (1) in data/trainers/parties.asm has no Espeon (Crobat, Weezing, Weezing, Ariados, Venomoth) -- Seven trainers' party ORDER in the guide differs from ROM order (Herman, Johnny, Carter, Boris, Charles, Linda, Janine); species and levels always match -- Guide omits Route 12 NUGGET (5,51) and hidden ELIXER (14,13), Route 13 hidden CALCIUM (30,13), Route 17 hidden MAX_ETHER (12,5) and MAX_ELIXER (8,77) -- Guide's Route 17 biker order (Riley, Glenn, Joel, Charles) contradicts the map's y-order (Riley 17, Joel 32, Glenn 53, Charles 80); these are sight-line trainers so y-order is what a bot will hit -- Port: the four-fold prize-money loop (engine/battle/core.asm:2341 'ld c, 4') was not located in src/battle/gen2/Prize.lua during this pass -- Fuchsia City cut trees at cells (16,11) and (18,19) are unmentioned by the guide and no reward was traced behind them -- FuchsiaCity warp 7 at (18,3) is only documented as inaccessible by an asm comment; the block collision itself was not decoded - -### 24. [snorlax and pewter city gym](section-24-snorlax-and-pewter-city-gym.md) - -Maps the FAQ's "Snorlax and Pewter City Gym" chapter onto pokegold: the blocked Route 19 boulder callback, the Poke Flute radio wake-up and forced-Leftovers Snorlax battle in Vermilion City, the three-pocket ladder chain through Diglett's Cave, Route 2's items/cut trees/Bug Catchers, and Pewter City through Brock's BOULDERBADGE. Every warp, coord, bg and object row is transcribed verbatim from the map asm, with the enforcing code cited for each gate and an honest implemented/partial/missing pass over the port's src/world/gen2, src/script/gen2 and src/battle/gen2. - -**Milestones:** BOULDERBADGE (ENGINE_BOULDERBADGE, set by maps/PewterGym.asm:PewterGymBrockScript); EVENT_BEAT_BROCK; EVENT_FOUGHT_SNORLAX (L50 SNORLAX with LEFTOVERS); EVENT_GOT_SILVER_WING (Gold) / EVENT_GOT_RAINBOW_WING + EVENT_TEAM_ROCKET_DISBANDED (Silver); EVENT_GOT_NUGGET_FROM_GUY; ENGINE_FLYPOINT_PEWTER - -**Maps:** `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_ROUTE_20`, `MAP_VERMILION_CITY`, `MAP_VERMILION_POKECENTER_1F`, `MAP_DIGLETTS_CAVE`, `MAP_ROUTE_2`, `MAP_ROUTE_2_NUGGET_HOUSE`, `MAP_ROUTE_2_GATE`, `MAP_PEWTER_CITY`, `MAP_PEWTER_POKECENTER_1F`, `MAP_PEWTER_GYM` - -**Gates:** - -- Route 19 boulders: Route19ClearRocksCallback paints six $7a blocks at (6,6)(8,6)(10,6)(12,8)(4,8)(10,10) while EVENT_CINNABAR_ROCKS_CLEARED is clear; that event is set ONLY by Route20ClearRocksCallback (maps/Route20.asm:13, MAPCALLBACK_NEWMAP), so nothing in this section can open it -- SNORLAX at (34,8) physically covers DIGLETT'S CAVE warp 10 at (34,7); removed by `disappear` in VermilionSnorlax, which runs only after startbattle -- SnorlaxAwake (engine/events/specials.asm:358) needs wMapMusic == MUSIC_POKE_FLUTE_CHANNEL AND the player on (33,8)/(34,10)/(35,10)/(36,8)/(36,9) -- POKe FLUTE radio channel (knob 78 / 20.0, engine/pokegear/pokegear.asm:1487) needs not-Johto plus POKEGEAR_EXPN_CARD_F -- Route 2 cut trees: COLL_CUT_TREE via CheckCutCollision, gated on HM01 CUT + ENGINE_HIVEBADGE (engine/events/overworld.asm:133) -- Diglett's Cave is three disconnected pockets joined only by warp pairs 2<->5 and 6<->4 -- PewterGymBrockScript `checkflag ENGINE_BOULDERBADGE / iftrue .FightDone` makes Brock one-shot -- Pewter Museum has no warp row at all - permanently closed - -**Unresolved (9):** - -- Brock's party order: FAQ says Graveler/Omastar/Rhyhorn/Onix/Kabutops, BrockGroup (data/trainers/parties.asm:270) is Graveler/Rhyhorn/Omastar/Onix/Kabutops -- Every FAQ prize-money figure is exactly 4x the asm's ComputeTrainerReward (base reward x last mon level): Brock 25x42=1050 vs FAQ 4200, Rob 128 vs 512, Doug 136 vs 544, Ed 120 vs 480, Jerry 185 vs 740; the EXP figures likewise are not GS values (probably HGSS) -- FAQ says the Max Potion is 'in Viridian Forest' - no such map exists in GS; it is ROUTE2_POKE_BALL2 at (2,23) on ROUTE_2 -- FAQ's 'lady' in the Pewter Pokemon Center is `Chris`, SPRITE_POKEFAN_M at (7,2) -- Which two of Route 2's five CUT_TREE blocks the FAQ means is inferred from the .blk dump plus item/warp positions, not labelled in asm -- Diglett's Cave three-pocket connectivity was derived by hand from DiglettsCave.blk block ids vs kanto_collision.asm; interior tile collision not fully traced -- FAQ's 'the route is cleared to Diglett's Cave and through Route 8' - nothing in Route11/Route8 asm is gated on the Snorlax; EVENT_FOUGHT_SNORLAX only additionally controls a Black Belt object in maps/VictoryRoadGate.asm:119 -- 'Keep around those four Ultra Balls' is strategy; no ball-count check exists -- Port: the equivalent of BikeFunction.CheckEnvironment (CAVE/GATE permission) was not located in src/world/gen2/Bike.lua, so biking Diglett's Cave in the port is unverified - -### 25. [lugia gold and ho oh silver](section-25-lugia-gold-and-ho-oh-silver.md) - -Section 25 covers the post-Elite-Four hunt for the version-opposite mascot: the Gold branch surfs Route 41 into the Whirl Islands down to Lugia's chamber, and the Silver branch walks Ecruteak's Tin Tower gatehouse up nine floors of warp/jump mazes to Ho-Oh on the roof. The document transcribes every warp/coord/bg/object table for all 20 maps, the two mascot scripts with their checkver level split and pre-battle one-shot flags, the wing-based MAPCALLBACK_OBJECTS spawn checks, the field-move badge gates, wild/fishing tables, and an honest port-coverage pass that flags the missing Heavy Ball weight bonus. - -**Milestones:** No badges awarded in this section; EVENT_FOUGHT_LUGIA set - Lugia encountered at WHIRL_ISLAND_LUGIA_CHAMBER (Lv70 in Gold, Lv40 in Silver); EVENT_FOUGHT_HO_OH set - Ho-Oh encountered at TIN_TOWER_ROOF (Lv70 in Silver, Lv40 in Gold), always holding SACRED_ASH via BATTLETYPE_FORCEITEM - -**Maps:** `MAP_ROUTE_41`, `MAP_WHIRL_ISLAND_NW`, `MAP_WHIRL_ISLAND_NE`, `MAP_WHIRL_ISLAND_SW`, `MAP_WHIRL_ISLAND_SE`, `MAP_WHIRL_ISLAND_CAVE`, `MAP_WHIRL_ISLAND_B1F`, `MAP_WHIRL_ISLAND_B2F`, `MAP_WHIRL_ISLAND_LUGIA_CHAMBER`, `MAP_ECRUTEAK_CITY`, `MAP_ECRUTEAK_TIN_TOWER_ENTRANCE`, `MAP_ECRUTEAK_TIN_TOWER_BACK_ENTRANCE`, `MAP_TIN_TOWER_1F`, `MAP_TIN_TOWER_2F`, `MAP_TIN_TOWER_3F`, `MAP_TIN_TOWER_4F`, `MAP_TIN_TOWER_5F`, `MAP_TIN_TOWER_6F`, `MAP_TIN_TOWER_7F`, `MAP_TIN_TOWER_8F`, `MAP_TIN_TOWER_9F`, `MAP_TIN_TOWER_ROOF` - -**Gates:** - -- SURF requires ENGINE_FOGBADGE (engine/events/overworld.asm:322, badge test :340) to reach Route 41 at all -- WHIRLPOOL requires ENGINE_GLACIERBADGE (overworld.asm:1061/:1077 menu path, :1167/:1171 A-press path) to clear the Route 41 whirlpool blocks (TILESET_JOHTO block $07 -> $36, data/collision/field_move_blocks.asm:48-55) -- FLASH requires ENGINE_ZEPHYRBADGE and a PALETTE_DARK map (overworld.asm:271) - every Whirl Island map is PALETTE_DARK -- WATERFALL requires ENGINE_RISINGBADGE (overworld.asm:611/:618, :683/:687) only for the UP direction; descending the B2F waterfall needs nothing -- Lugia object only appears while checkitem SILVER_WING is true and EVENT_FOUGHT_LUGIA is clear (WhirlIslandLugiaChamberLugiaCallback, MAPCALLBACK_OBJECTS) -- Ho-Oh object only appears while checkitem RAINBOW_WING is true and EVENT_FOUGHT_HO_OH is clear (TinTowerRoofHoOhCallback) -- TIN_TOWER_1F stairs at (10,2) are physically occupied by TINTOWER1F_SAGE, masked only when EVENT_TEAM_ROCKET_DISBANDED is set (maps/RadioTower5F.asm:130 in Gold, maps/PewterCity.asm:51 in Silver) -- ECRUTEAK_TIN_TOWER_ENTRANCE coord_events at (4,7)/(5,7) block the corridor while the map scene is SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS; cleared by maps/EcruteakGym.asm:34 setmapscene on the Fog Badge -- EVENT_FOUGHT_LUGIA / EVENT_FOUGHT_HO_OH are set BEFORE startbattle, so fleeing, fainting or a KO burns the only encounter - save first -- Escape Rope only works in CAVE or DUNGEON environments (engine/events/overworld.asm:724-754); TIN_TOWER_ROOF is ROUTE, so you must descend to 9F first - -**Unresolved (11):** - -- Walkthrough says Whirlpool needs eight badges; the asm gates it on ENGINE_GLACIERBADGE alone (seven badges) -- Walkthrough lists 'Max Revive x2' in the Whirl Islands; only one MAX_REVIVE item ball exists (WhirlIslandB2FMaxRevive at B2F (6,4)). It also omits Full Restore x2, Carbos, Nugget, Max Elixer and three B1F hidden items -- Tin Tower item list is wrong for pokegold: no PP_UP, no HP_UP, no Max Potion item ball (only hidden TinTower4FHiddenMaxPotion at (11,6)), only one Full Heal (3F). It omits Super Potion, Nugget, Full Restore and the 4F/5F hidden items -- Walkthrough places a Max Potion and a Full Heal on the floor matching TIN_TOWER_6F; maps/TinTower6F.asm has empty def_bg_events and def_object_events -- Ho-Oh's claimed moveset (Safeguard, Ancient Power, Punishment, Sacred Fire) is impossible - PUNISHMENT does not exist in Gen 2. Derived from data/pokemon/evos_attacks.asm:3324-3336 plus FillMoves (engine/pokemon/evolve.asm:478), a Lv70 Ho-Oh should have Recover/Fire Blast/Sunny Day/Swift. The derivation is inferred, not a literal table -- Whirl Islands wild list omits GOLBAT (grass slot 5 on every floor) and TENTACOOL/TENTACRUEL (SW and B2F water); Tin Tower list omits GASTLY at night -- Whirlpool positions near Swimmer Kara (44,28) are .blk block data, not events - a bot must scan maps/Route41.blk for TILESET_JOHTO block $07 -- All ledge-hop and jump-platform routing in both dungeons is .blk collision data; only the warp endpoints are asm facts -- 'Bell Tower' is the post-GS rename; pokegold text and signs say TIN TOWER throughout -- Port: Heavy Ball weight bonus is missing (src/battle/gen2/Catching.lua:36 has HEAVY_BALL = 1, no dex-weight lookup), so the FAQ's Lugia Heavy Ball tip does not work in this repo -- Port: could not locate an EscapeRopeFunction equivalent with the CAVE/DUNGEON environment check, so the TIN_TOWER_ROOF refusal is unverified in this repo - -### 26. [mount moon and routes 1 4](section-26-mount-moon-and-routes-1-4.md) - -Maps the walkthrough's Route 3 -> Mt. Moon (rival battle, Mt. Moon Square, gift shop) -> Route 4 -> Viridian City / Trainer House -> Route 1 -> Pallet Town / Oak's Lab stretch onto pokegold, with verbatim warp/coord/bg/object tables, map headers, trainer parties, wild tables and script control flow. Ends by handing off to Route 21 (next section) and records the port's gaps, notably that OBJECTTYPE_ITEMBALL pickup and smashable-rock interaction are unimplemented. - -**Milestones:** No badge is earned in this section; EVENT_BEAT_RIVAL_IN_MT_MOON (last mandatory Silver battle; unlocks the Indigo Plateau and Dragon's Den rival re-encounters); ENGINE_FLYPOINT_VIRIDIAN (Viridian City MAPCALLBACK_NEWMAP); ENGINE_FLYPOINT_PALLET (Pallet Town MAPCALLBACK_NEWMAP); EVENT_TALKED_TO_OAK_IN_KANTO (Oak's Kanto-badge conversation; EVENT_OPENED_MT_SILVER at 16 badges); HP_UP item ball on Route 4 (EVENT_ROUTE_4_HP_UP) - -**Maps:** `MAP_ROUTE_3`, `MAP_MOUNT_MOON`, `MAP_MOUNT_MOON_SQUARE`, `MAP_MOUNT_MOON_GIFT_SHOP`, `MAP_ROUTE_4`, `MAP_VIRIDIAN_CITY`, `MAP_TRAINER_HOUSE_1F`, `MAP_TRAINER_HOUSE_B1F`, `MAP_ROUTE_1`, `MAP_PALLET_TOWN`, `MAP_OAKS_LAB` - -**Gates:** - -- MountMoon scene 0 SCENE_MOUNTMOON_RIVAL_BATTLE -> sdefer MountMoonRivalBattleScript (42:55b0): unavoidable RIVAL2 battle; only setscene SCENE_MOUNTMOON_NOOP at the end of a win clears it -- MountMoonSquare ClefairyDance: requires ENGINE_MT_MOON_SQUARE_CLEFAIRY clear AND VAR_WEEKDAY == MONDAY AND checktime NITE; one-shot via setflag -- EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE is re-set by MountMoonSquareDisappearMoonStoneCallback on every MAPCALLBACK_NEWMAP, so the Moon Stone must be taken in the same visit as the dance -- MtMoonSquareRock -> jumpstd SmashRockScript -> engine/events/overworld.asm:1365 AskRockSmashScript / HasRockSmash: needs ROCK_SMASH in the party (CheckPartyMove), no badge check -- TrainerHouseB1F ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY: one CAL battle per day -- TrainerHouse opponent identity: special TrainerHouse reads sMysteryGiftTrainerHouseFlag (engine/events/specials.asm:454); without Mystery Gift always CAL3 -- Fly: engine/events/overworld.asm:544 FlyFunction .TryFly requires ENGINE_STORMBADGE plus the destination's ENGINE_FLYPOINT_* -- Surf south from Pallet to Route 21 (next section): engine/events/overworld.asm:338 SurfFunction .TrySurf requires ENGINE_FOGBADGE -- Indigo Plateau rival rematch: EVENT_BEAT_RIVAL_IN_MT_MOON + ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT clear + weekday Monday or Wednesday (maps/IndigoPlateauPokecenter1F.asm:45,67) -- Dragon's Den rival sighting: EVENT_BEAT_RIVAL_IN_MT_MOON + weekday Tuesday or Thursday (maps/DragonsDenB1F.asm:12) - -**Unresolved (13):** - -- Rival party ORDER contradicts the walkthrough: parties.asm has the starter evolution LAST at L45, not third (data/trainers/parties.asm:2042-2069) -- Walkthrough's Route 3 and Route 4 wild lists (Ekans/Arbok) are the ELIF DEF(_SILVER) arms; the _GOLD arms have no Ekans or Arbok (data/wild/kanto_grass.asm:314, :369) -- Walkthrough's Mt. Moon wild list omits Gold's L8 SANDSHREW, L10 SANDSLASH and two L8 CLEFAIRY slots (data/wild/kanto_grass.asm:33) -- Walkthrough's Route 1 wild list omits the nite column, which has no Pidgey or Furret at all (data/wild/kanto_grass.asm:250) -- 'Item on Route 1: Bitter Berry' is a fruittree FRUITTREE_ROUTE_1 (daily respawn), not a ground item (data/items/fruit_trees.asm) -- The two Mt. Moon paths described in the walkthrough could not be confirmed against maps/MountMoon.blk collision; only the warp graph was verified -- 'Bring Rock Smash' is supported indirectly (the rock is appeared onto the Moon Stone tile mid-cutscene) but whether it actually blocks the A press was not verified -- Route 4 hidden ULTRA_BALL at (10,3) and Viridian's TM_DREAM_EATER are real but absent from the walkthrough -- Mt. Moon Gift Shop has no NITE clerk: both SPRITE_GRAMPS rows are gated -1, MORN and -1, DAY -- Route 3's only warp is at (52,1) but its Mt. Moon Square sign is at (49,13); odd but verbatim -- PORT GAP: OBJECTTYPE_ITEMBALL pickup is unimplemented. RomExtractorGen2.lua:2968 records obj.itemball but World:interact (src/world/gen2/World.lua:5257-5310) has no arm for it, so the Route 4 HP Up cannot be taken -- PORT GAP: SPRITEMOVEDATA_SMASHABLE_ROCK has no interact arm (World.isStrengthBoulder exists at World.lua:4071, no rock equivalent anywhere in src/) -- PORT FIDELITY: src/script/gen2/Vm.lua:92-93 runs sdefer immediately instead of deferring, so the Mt. Moon rival cutscene starts marginally earlier than on cart - -### 27. [cinnabar island and seafoam islands gym](section-27-cinnabar-island-and-seafoam-islands-gym.md) - -Maps the FAQ's Cinnabar Island / Seafoam Islands Gym chapter onto pokegold: the surf route Pallet -> Route 21 -> Cinnabar Island -> Route 20 -> Seafoam Gym, with verbatim warp/bg/object tables, map headers, connections, the four trainers plus BLAINE, and every EVENT_/ENGINE_ flag those scripts touch. Includes the two non-obvious side effects a bot must not skip - talking to Blue clears EVENT_VIRIDIAN_GYM_BLUE (the only unlock for the Viridian Gym leader) and merely loading Route 20 sets EVENT_CINNABAR_ROCKS_CLEARED (which unseals Route 19). - -**Milestones:** ENGINE_VOLCANOBADGE (VOLCANOBADGE from BLAINE, maps/SeafoamGym.asm:34); EVENT_BEAT_BLAINE (maps/SeafoamGym.asm:29); ENGINE_FLYPOINT_CINNABAR (CinnabarIslandFlypointCallback, maps/CinnabarIsland.asm:11); EVENT_CINNABAR_ROCKS_CLEARED (Route20ClearRocksCallback, maps/Route20.asm:13); EVENT_VIRIDIAN_GYM_BLUE cleared by CinnabarIslandBlue (maps/CinnabarIsland.asm:23) - the Viridian Gym leader unlock; Hidden RARE_CANDY, EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY (maps/CinnabarIsland.asm:36) - -**Maps:** `MAP_ROUTE_21`, `MAP_CINNABAR_ISLAND`, `MAP_CINNABAR_POKECENTER_1F`, `MAP_ROUTE_20`, `MAP_SEAFOAM_GYM` - -**Gates:** - -- SURF over open sea for the whole section: engine/events/overworld.asm:469 TrySurfOW -> ENGINE_FOGBADGE CheckEngineFlag at :490 plus CheckPartyMove SURF (menu path SurfFunction .TrySurf, ENGINE_FOGBADGE at :340) -- Blue absent from Viridian Gym until talked to here: maps/ViridianGym.asm:183-184 object rows carry EVENT_VIRIDIAN_GYM_BLUE, set at engine/events/std_scripts.asm:552, cleared only by CinnabarIslandBlue -- Route 19 sealed by six changeblock $7a rock blocks in maps/Route19.asm:15 Route19ClearRocksCallback while EVENT_CINNABAR_ROCKS_CLEARED is clear; loading Route 20 once sets it -- Blaine re-battle blocked by checkflag ENGINE_VOLCANOBADGE / iftrue .FightDone (maps/SeafoamGym.asm:17) -- Seafoam gym guide does not exist until the win branch runs appear SEAFOAMGYM_GYM_GUIDE (maps/SeafoamGym.asm:26); flag EVENT_SEAFOAM_GYM_GYM_GUIDE set at engine/events/std_scripts.asm:553 -- Fly to Cinnabar requires ENGINE_FLYPOINT_CINNABAR, set only by walking onto the map (data/maps/flypoints.asm:28, spawn_points.asm:24) - -**Unresolved (9):** - -- Blaine's movesets in the walkthrough (Yawn/Recover/Overheat, Flare Blitz, Bounce) do not exist in Gen 2 and contradict data/trainers/parties.asm:2303-2308 (CURSE/SMOG/FLAMETHROWER/ROCK_SLIDE, THUNDERPUNCH/FIRE_PUNCH/SUNNY_DAY/CONFUSE_RAY, QUICK_ATTACK/FIRE_SPIN/FURY_ATTACK/FIRE_BLAST). Species and levels match exactly. -- "Two Full Restores" contradicts data/trainers/attributes.asm:276 db MAX_POTION, FULL_HEAL. -- "You now have 15 badges" has no asm anchor; Kanto gym order is unenforced. -- Route 21 wild list abridged: asm also has MR__MIME in grass (kanto_grass.asm:1005) and TENTACRUEL in water (kanto_water.asm:61); Route 20 also has TENTACRUEL. -- Surf paths ("way left then down" on Route 21, the cave mouth on Route 20) are block data in maps/Route21.blk / maps/Route20.blk and were not decoded; only the trainer/warp coordinates are verified. -- No MAP_SEAFOAM_ISLANDS dungeon exists in Gen 2 - constants/map_constants.asm has only SEAFOAM_GYM; "Seafoam Islands" is LANDMARK_SEAFOAM_ISLANDS (data/maps/landmarks.asm:94). -- Per-mon EXP figures are not stored anywhere in the asm (money figures all reproduce exactly as base reward x last-mon level x 4). -- EVENT_BEAT_BLAINE has no reader anywhere in maps/, data/, engine/, home/ or constants/ - ENGINE_VOLCANOBADGE is the flag the cart branches on. -- Port gap (not a walkthrough issue): setflag ENGINE_VOLCANOBADGE lands on save.engineFlags but nothing writes save.player.kantoBadges, so the badge never reaches the trainer card or VAR_BADGES; BadgeTypeBoosts (FIRE +12.5%) is absent from src/battle/gen2/; movement byte $4c (teleport_from, Blue's exit) decodes to nop in src/script/gen2/Movement.lua. - -### 28. [routes 19 20 and viridian city gym](section-28-routes-19-20-and-viridian-city-gym.md) - -Maps the optional Swimmer sweep across Routes 20 and 19 and the Viridian Gym fight with Blue onto pokegold, with verbatim warp/coord/bg/object tables, map headers, connections, trainer parties, water and fishing tables, and the exact opcode flow of ViridianGymBlueScript. Ends with the real gates (FOGBADGE Surf, the Route 20 rock-clearing callback, EVENT_VIRIDIAN_GYM_BLUE hiding both gym objects, STORMBADGE/flypoint Fly), a literal 18-step bot checklist, and a port-coverage table for src/world/gen2, src/script/gen2 and src/battle/gen2. - -**Milestones:** EARTHBADGE (ENGINE_EARTHBADGE) from Blue in Viridian Gym - the 16th badge; EVENT_BEAT_BLUE; EVENT_BEAT_SWIMMERF_LORI; EVENT_BEAT_SWIMMERF_NICOLE; EVENT_BEAT_SWIMMERM_TUCKER; EVENT_BEAT_SWIMMERF_DAWN; EVENT_BEAT_SWIMMERM_JEROME; TM42 Dream Eater from VIRIDIANCITY_FISHER (optional, not in the FAQ) - -**Maps:** `MAP_ROUTE_20`, `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_VIRIDIAN_CITY`, `MAP_VIRIDIAN_POKECENTER_1F`, `MAP_VIRIDIAN_GYM` - -**Gates:** - -- ENGINE_FOGBADGE + SURF in party to reach Routes 19/20 at all (engine/events/overworld.asm:322 SurfFunction, :469 TrySurfOW) -- EVENT_CINNABAR_ROCKS_CLEARED gates the six changeblock $7a WALL rocks on northern Route 19 (maps/Route19.asm:15); set unconditionally by entering Route 20 (maps/Route20.asm:12 MAPCALLBACK_NEWMAP) -- EVENT_VIRIDIAN_GYM_BLUE must be CLEAR or the gym is empty - both ViridianGym objects carry it; set at new game by engine/events/std_scripts.asm:552, cleared only by maps/CinnabarIsland.asm:23 CinnabarIslandBlue -- Fly to Viridian needs ENGINE_STORMBADGE + FLY + ENGINE_FLYPOINT_VIRIDIAN, which ViridianCityFlypointCallback only sets on a first on-foot visit -- ENGINE_EARTHBADGE checkflag makes the Blue battle one-shot (maps/ViridianGym.asm:13); no rematch or phone entry -- Next section: maps/OaksLab.asm:26 readvar VAR_BADGES / ifequal NUM_BADGES gates EVENT_OPENED_MT_SILVER on this badge - -**Unresolved (6):** - -- The FAQ's 'blocked by boulders south of Fuchsia City' contradicts the asm: Route20ClearRocksCallback sets EVENT_CINNABAR_ROCKS_CLEARED on first entry to Route 20, so the rocks are already gone by the time the walkthrough reaches Route 19 -- Route 19 declares connection north FuchsiaCity (data/maps/attributes.asm:288) as well as a warp to Route19FuchsiaGate; I did not decode Route19.blk to see whether the north edge is actually walkable -- The FAQ says Route 19 has no further trainers, but TrainerSwimmermHarold (13,28) and TrainerSwimmermCameron (12,13, Route 20) both exist with sight range 3 -- Blue's strategy prose cites Air Slash (nonexistent in Gen 2) and Exeggutor's Psychic/Hypnosis; BlueGroup gives Pidgeot WING_ATTACK and Exeggutor SUNNY_DAY/LEECH_SEED/EGG_BOMB/SOLARBEAM -- 'One of only two trainers tougher than Champion Lance' has no asm counterpart -- ViridianGym's maps.asm row sets the phone column TRUE; no phone_call names BLUE, but data/phone/ was not read exhaustively - -### 29. [routes 22 28](section-29-routes-22-28.md) - -Covers the post-Kanto run to Mt. Silver: Fly to Pallet Town, talk to Prof. Oak with 16 badges to set EVENT_OPENED_MT_SILVER, Fly to Viridian and Cut west for TM42 Dream Eater, cross Route 22 to the Victory Road Gate, then west to Route 28, the Mt. Silver Pokecenter, and TM47 Steel Wing. Every map's warp/coord/bg/object tables are transcribed verbatim, and the Cut trees, ledges and NPC blockers were verified cell-by-cell against the .blk block data and the kanto/gate tileset collision tables. - -**Maps:** `MAP_PALLET_TOWN`, `MAP_OAKS_LAB`, `MAP_VIRIDIAN_CITY`, `MAP_ROUTE_22`, `MAP_VICTORY_ROAD_GATE`, `MAP_ROUTE_28`, `MAP_ROUTE_28_STEEL_WING_HOUSE`, `MAP_SILVER_CAVE_OUTSIDE`, `MAP_SILVER_CAVE_POKECENTER_1F` - -**Gates:** - -- EVENT_OPENED_MT_SILVER hides VICTORYROADGATE_BLACK_BELT1 at cell (7,5), the only tile joining the gate's centre corridor to its west vestibule and therefore to the ROUTE_28 warps at (1,7)/(2,7). Oak sets it only when VAR_BADGES == NUM_BADGES (16), via `ifequal NUM_BADGES` in maps/OaksLab.asm:27. This, not the cave mouth, is what gates Mt. Silver. -- EVENT_FOUGHT_SNORLAX hides VICTORYROADGATE_BLACK_BELT2 at (12,5), the equivalent chokepoint to the ROUTE_22 vestibule. Already set by this point, but a flag-cleared test save walls itself into the gate. -- HM01 Cut + ENGINE_HIVEBADGE (engine/events/overworld.asm:1741 TryCutOW): required at ViridianCity cell (8,22) for the TM42 Dream Eater fisher, and at SilverCaveOutside cells (31,24) and (34,23) - BOTH trees - to reach ROUTE_28 (0,3), the Steel Wing house and the hidden Rare Candy. -- HM02 Fly + ENGINE_STORMBADGE (engine/events/overworld.asm:545) plus the ENGINE_FLYPOINT_PALLET / _VIRIDIAN / _SILVER_CAVE bits set by each map's MAPCALLBACK_NEWMAP. -- Victory Road Gate coord event (10,11) needs >= 8 Johto badges, but the Route 22 -> Route 28 path never crosses it. -- Ledge hopping: Route 22 has no ledge-free route to the gate warp - (35,12) and (18,6) are COLL_HOP_DOWN over COLL_WALL. The Route 28 west strip likewise exits only over the y=4 ledge row, one-way. - -**Unresolved (8):** - -- FAQ files TM42 Dream Eater under 'Items on Route 22'; the NPC is actually ViridianCity object (6,23). maps/Route22.asm has an empty def_object_events. -- FAQ says there is a Pokemon Center on Route 28; it is on MAP_SILVER_CAVE_OUTSIDE (warp 1 at (23,19)), one map west. -- FAQ says TM47 Steel Wing is a Route 28 item; it is inside ROUTE_28_STEEL_WING_HOUSE. Route 28's only item is the hidden Rare Candy at (25,2), which the FAQ omits. -- FAQ's Route 28 species list omits Ursaring (Gold's 20% slot where Silver has Donphan) and Doduo/Dodrio, and does not say Sneasel is nite-only. -- FAQ's Route 22 list omits Doduo, Ponyta and Fearow. -- The two-cell ledge jump distance was inferred from the collision permission table ($a0..$a7 = LAND_TILE) plus the fact that every ledge on these maps has COLL_WALL directly beyond it; the DoPlayerMovement ledge branch in engine/overworld/player_movement.asm was not read line by line. All Route 22 / Route 28 waypoints depend on that inference. -- SilverCaveOutside's east edge is also walkable at y=14..18 and y=33..35, mapping to Route 28 y=-4..0 and y=15..17, which are outside every reachable component - assumed filler, not chased. -- Nothing in the asm checks the bag, so the FAQ's 'bring 10 Revives / 50 Ultra Balls' is advice, not a gate. - -### 30. [lapras](section-30-lapras.md) - -Maps the Friday-only static Lapras hunt onto the pokegold disassembly: the Route 32 approach, the surf route down through Union Cave 1F and B1F, and the full Union Cave B2F event tables, with UnionCaveB2FLaprasCallback's weekday/daily-flag logic and UnionCaveLapras's unconditional disappear+setflag transcribed opcode by opcode. Includes verbatim warp/bg/object tables for all three floors, the five trainers' parties and prize-money arithmetic (base x last level x 4, which reproduces the walkthrough's gold figures exactly), the grass and water wild tables, and an honest port-coverage audit that flags item-ball pickup and Gen 2 Escape Rope as missing. - -**Maps:** `MAP_UNION_CAVE_1F`, `MAP_UNION_CAVE_B1F`, `MAP_UNION_CAVE_B2F`, `MAP_ROUTE_32` - -**Gates:** - -- Surf field move: SurfFunction.TrySurf (engine/events/overworld.asm:322) checks ENGINE_FOGBADGE, so the Fog Badge plus a mon knowing SURF is required to reach any of the three floors' water legs, including Lapras at UNION_CAVE_B2F 11,31 -- Friday-only: UnionCaveB2FLaprasCallback (maps/UnionCaveB2F.asm:15, sym 44:575e) runs readvar VAR_WEEKDAY / ifequal FRIDAY under MAPCALLBACK_OBJECTS, re-evaluated on every B2F map load -- Daily one-shot: the same callback's checkflag ENGINE_UNION_CAVE_LAPRAS (constants/engine_flags.asm:107, wDailyFlags2 bit DAILYFLAGS2_UNION_CAVE_LAPRAS_F) hides Lapras once UnionCaveLapras has run; cleared only by CheckDailyResetTimer (engine/overworld/time.asm:88) -- Unconditional consumption: UnionCaveLapras runs disappear + setflag after startbattle with no wBattleResult guard, so a KO, a flee or a blackout burns the encounter for the day - save before pressing A -- Object visibility inversion: EVENT_UNION_CAVE_B2F_LAPRAS SET means hidden (CheckObjectFlag, engine/overworld/map_objects_2.asm:31); appear clears it, disappear sets it -- Single exit: UNION_CAVE_B2F has exactly one warp (5,3 -> UNION_CAVE_B1F warp 5); leaving otherwise needs Escape Rope / Dig -- Port blocker: OBJECTTYPE_ITEMBALL objects have no consumer in this repo (extractor sets obj.itemball at src/import/RomExtractorGen2.lua:2969, World:talk only dispatches scriptKey at src/world/gen2/World.lua:5276), so the B2F Elixer and Hyper Potion cannot be picked up -- Port blocker: no Gen 2 Escape Rope path found under src/core/gen2 or src/world/gen2 (only the Gen 1 route in src/ui/PartyMenu.lua:634) - -**Unresolved (8):** - -- EXP figures quoted per mon (637 Marowak, 975 Kangaskhan, 355/511/924/928/933/786 and two '?' entries) are runtime-computed from base EXP x level, not stored in any trainer table, so none could be pinned to an asm row -- Walkthrough lists Nick as Charmander/Bulbasaur/Squirtle and Gwen as Eevee/Vaporeon/Jolteon/Flareon; data/trainers/parties.asm:742 has Charmander/Squirtle/Bulbasaur and :867 has Eevee/Flareon/Vaporeon/Jolteon - same sets, different order (matters because Gwen's last row, Jolteon L22, sets the prize level) -- 'once you've gotten HM Surf' implies a scripted gate that does not exist; no HM or badge check appears in UnionCaveB2FLaprasCallback or UnionCaveLapras, only reachability via SurfFunction.TrySurf's ENGINE_FOGBADGE test -- In-game NPC hints disagree with the code: PokemaniacLarryAfterBattleText says 'Every Friday' (matches the callback) while FirebreatherBillAfterBattleText says 'On weekends'; the walkthrough follows Larry and is correct -- Identification of 'the guy on the cliff still spinning around' as Firebreather Bill (UNIONCAVE1F_FISHER2 at 15,15, SPRITEMOVEDATA_SPINRANDOM_FAST) is inferred from coordinates and movement data, not stated anywhere; walking order depends on UnionCave1F.blk, which was not decoded -- Which 1F ladder the walkthrough means is inferred: warp 2 at 3,33 -> UNION_CAVE_B1F warp 4 at 3,33, chosen because Andrew stands at B1F 5,32; warp 1 at 5,19 lands nowhere near him -- B2F 'right side' / 'left side' landings are consistent with the object coordinates (12,19 and 17,23 right; 5,13 and 3,28 left) but the actual land/water split comes from UnionCaveB2F.blk, which was not decoded -- Walkthrough omits the B2F Elixer item ball at 16,2 (UnionCaveB2FElixer, EVENT_UNION_CAVE_B2F_ELIXER) entirely - an omission, not a contradiction - -### 31. [mount silver and red](section-31-mount-silver-and-red.md) - -Maps the final walkthrough stretch - Route 28's west edge into Mt. Silver, the three Silver Cave floors, the two-chamber item map, and the Red battle - onto pokegold, with verbatim warp/bg/object tables, the Red script's opcode-by-opcode control flow, RedGroup's party and prize-money maths, and the full johto_grass/johto_water entries with their morn/day/nite and Gold-vs-Silver splits. Gates are traced to enforcing code (VictoryRoadGate badge coord_event, PALETTE_DARK/Flash, Surf/Waterfall badge checks, EVENT_RED_IN_MT_SILVER object masking), and port coverage is marked per beat against src/world/gen2, src/script/gen2 and src/import/RomExtractorGen2.lua. - -**Milestones:** No badge awarded in this section; Red (RED/RED1) defeated in SilverCaveRoom3 - final boss; credits opcode runs, wSpawnAfterChampion = SPAWN_RED, post-credits respawn at SPAWN_MT_SILVER - -**Maps:** `MAP_SILVER_CAVE_OUTSIDE`, `MAP_SILVER_CAVE_POKECENTER_1F`, `MAP_SILVER_CAVE_ROOM_1`, `MAP_SILVER_CAVE_ROOM_2`, `MAP_SILVER_CAVE_ITEM_ROOMS`, `MAP_SILVER_CAVE_ROOM_3`, `MAP_VICTORY_ROAD_GATE` - -**Gates:** - -- VictoryRoadGate coord_event (10,11) SCENE_VICTORYROADGATE_BADGE_CHECK -> VictoryRoadGateBadgeCheckScript: readvar VAR_BADGES / ifgreater NUM_JOHTO_BADGES - 1 -- EVENT_RED_IN_MT_SILVER must be CLEAR for Red's object to appear; cleared only by maps/HallOfFame.asm:36 (Elite Four induction), set by InitializeEventsScript at new game and re-set by disappear after the fight -- SilverCaveRoom1 is PALETTE_DARK -> ReplaceTimeOfDayPals .NeedsFlash; needs STATUSFLAGS_FLASH_F via FlashFunction (ENGINE_ZEPHYRBADGE + HM05) -- ENGINE_FOGBADGE + SURF (SurfFunction.TrySurf) to cross both SilverCaveRoom2 lakes -- ENGINE_RISINGBADGE + WATERFALL (WaterfallFunction.TryWaterfall / CheckMapCanWaterfall) to reach either SilverCaveItemRooms chamber -- Gen 2 ledge hop (.TryJump / HI_NYBBLE_LEDGES on the STANDING tile) required to leave the Escape Rope ledge row in SilverCaveRoom1 - -**Unresolved (9):** - -- Walkthrough lists Protein as a Mt. Silver item; no PROTEIN appears in any Silver Cave map asm -- Walkthrough lists Donphan (#232) - that is the _SILVER column; Gold has Ursaring in those slots -- Walkthrough says Red carries three Full Restores; data/trainers/attributes.asm gives the RED class exactly two item slots -- Red's party order in the walkthrough (Pikachu, Espeon, Blastoise, Snorlax, Charizard, Venusaur) differs from RedGroup (Pikachu, Espeon, Snorlax, Venusaur, Charizard, Blastoise); the quoted per-mon EXP yields are runtime-computed and not in any table -- Walkthrough calls Espeon a dark type immune to ghost moves; data/pokemon/base_stats/espeon.asm:6 is PSYCHIC_TYPE, PSYCHIC_TYPE -- Walkthrough says Red 'flies away'; the Red script only does FadeOutToBlack / disappear / FadeInFromBlack, no applymovement -- Which SilverCaveItemRooms chamber the Surf+Waterfall route reaches was derived by expanding the .blk files through data/tilesets/cave_collision.asm, not stated in asm - route coordinates should be confirmed in-game -- EVENT_OPENED_MT_SILVER (set by maps/OaksLab.asm on 16 badges) has no consumer on the Route 28 / Silver Cave path; its only reader is the hide-flag of a Black Belt in maps/VictoryRoadGate.asm:118, so the commonly-cited 16-badge entry requirement could not be pinned to enforcing code -- WARP_CARPET_DOWN collision cells with no warp_event: (11,27) and (13,27) in SilverCaveRoom1, (11,33) in SilverCaveRoom3 - -### 32. [raikou entei and suicune](section-32-raikou-entei-and-suicune.md) - -Maps the roaming-legendary hunt onto pokegold: the roam_struct WRAM layout, InitRoamMons starting routes (Raikou 42 / Entei 37 / Suicune 38), the verbatim 16-entry RoamMaps graph, the shared-random-byte .Update walk, and the exact map-setup-script table showing that connections and door warps run UpdateRoamMons while Fly/Teleport and Continue run JumpRoamMons. Adds full warp/coord/bg/object transcriptions and wild tables for the five maps the walkthrough names (Violet City, Routes 35/36/37/42), the catch and flee math including two shipped-ROM bugs, and an honest port-coverage audit that lists five concrete gaps. - -**Maps:** `MAP_ROUTE_29`, `MAP_ROUTE_30`, `MAP_ROUTE_31`, `MAP_ROUTE_32`, `MAP_ROUTE_33`, `MAP_ROUTE_34`, `MAP_ROUTE_35`, `MAP_ROUTE_36`, `MAP_ROUTE_37`, `MAP_ROUTE_38`, `MAP_ROUTE_39`, `MAP_ROUTE_42`, `MAP_ROUTE_43`, `MAP_ROUTE_44`, `MAP_ROUTE_45`, `MAP_ROUTE_46`, `MAP_VIOLET_CITY`, `MAP_ROUTE_40`, `MAP_ROUTE_41`, `MAP_BURNED_TOWER_B1F` - -**Gates:** - -- EVENT_RELEASED_THE_BEASTS / special InitRoamMons at maps/BurnedTowerB1F.asm:65 is the only thing that creates the three roam_structs; without it no beast exists anywhere -- CheckEncounterRoamMon's CheckOnWater bail: roamers can never be met while surfing -- RoamMaps (data/wild/roammon_maps.asm) restricts roamers to 16 Johto routes; ROUTE_40 and ROUTE_41 are deliberately absent -- LoadWildMonDataPointer must succeed before CheckEncounterRoamMon runs, so maps with no wild table can never host a beast -- ChooseWildEncounter_BugContest never calls CheckEncounterRoamMon, so no beast can appear during the Bug Catching Contest -- SudowoodoScript on Route 36 (tile x=35,y=9) needs SQUIRTBOTTLE; EVENT_ROUTE_36_SUDOWOODO blocks the Violet/Route 36/Route 37 loop until cleared -- AlwaysFleeMons (data/wild/flee_mons.asm) is exactly RAIKOU/ENTEI/SUICUNE, so TryEnemyFlee ends the battle after one player action unless SUBSTATUS_CANT_RUN, a live wEnemyWrapCount, SLP or FRZ applies -- CheckRepelEffect compares wCurPartyLevel (40) against the lead's level: a lead of level 41 or higher repels the roamer itself, which is why the walkthrough specifies a level 39 Scyther -- FastBallMultiplier bug (engine/items/item_effects.asm:986) means Fast Balls give no bonus on the beasts; ULTRA_BALL x2 or MASTER_BALL only -- Paralysis gives no catch-rate bonus at all (commented-out wEnemyMonStatus reload in .statuscheck); only SLP/FRZ give +10 -- All three beasts know ROAR at level 40; BattleCommand_ForceSwitch's wild branch ends the battle as a DRAW against any lead at level 40 or below - -**Unresolved (7):** - -- Walkthrough says roamers 'show up on the map in Pokegear'; FindNest's only caller is Pokedex_GetArea (engine/pokegear/pokegear.asm:2430, 24:5c7f), i.e. the Pokedex AREA screen drawn with town-map graphics, not the Pokegear map menu -- Walkthrough says tracking becomes possible 'after the initial encounter'; FindNest has no encountered gate, and nothing in maps/BurnedTowerB1F.asm marks the beasts SEEN during ReleaseTheBeasts, so the SEEN requirement is inferred rather than proven by a SetSeenMon call -- Walkthrough advises catching while 'asleep or paralyzed'; the .statuscheck block (engine/items/item_effects.asm:344) gives +10 for SLP/FRZ but nothing for PAR/BRN/PSN because the wEnemyMonStatus reload is commented out -- Walkthrough says roamers 'occasionally jump a few routes'; the only jump in the asm is the 1-in-32 and %00011111 branch into JumpRoamMon, which is a uniformly random one of the 16 entries, not an adjacent hop -- Walkthrough's 'as quickly as every 10 seconds' has no basis in code: roam movement is purely event-driven off map setup scripts and battle ends, with no timer anywhere -- Whether wCurPartyLevel still holds the wild mon's level at BattleCommand_ForceSwitch time after a mid-battle player switch was not traced; the guaranteed-Roar claim is asserted from the code as written -- Walkthrough leans on biking; CanEncounterWildMon (engine/overworld/events.asm:1164) and GetMapEncounterRate have no bicycle term, so the bike affects travel speed only - -## Map -> section lookup - -| Map constant | Sections | -|---|---| -| `MAP_AZALEA_GYM` | 04 | -| `MAP_AZALEA_POKECENTER_1F` | 04 | -| `MAP_AZALEA_TOWN` | 04, 05 | -| `MAP_BILLS_FAMILYS_HOUSE` | 05 | -| `MAP_BILLS_HOUSE` | 21 | -| `MAP_BLACKTHORN_CITY` | 13, 15 | -| `MAP_BLACKTHORN_DRAGON_SPEECH_HOUSE` | 13 | -| `MAP_BLACKTHORN_EMYS_HOUSE` | 13 | -| `MAP_BLACKTHORN_GYM_1F` | 13 | -| `MAP_BLACKTHORN_GYM_2F` | 13 | -| `MAP_BLACKTHORN_MART` | 13 | -| `MAP_BLACKTHORN_POKECENTER_1F` | 13 | -| `MAP_BRUNOS_ROOM` | 18 | -| `MAP_BURNED_TOWER_B1F` | 32 | -| `MAP_CELADON_CAFE` | 22 | -| `MAP_CELADON_CITY` | 22 | -| `MAP_CELADON_DEPT_STORE_1F` | 22 | -| `MAP_CELADON_DEPT_STORE_2F` | 22 | -| `MAP_CELADON_DEPT_STORE_3F` | 22 | -| `MAP_CELADON_DEPT_STORE_4F` | 22 | -| `MAP_CELADON_DEPT_STORE_5F` | 22 | -| `MAP_CELADON_DEPT_STORE_6F` | 22 | -| `MAP_CELADON_GAME_CORNER` | 22 | -| `MAP_CELADON_GAME_CORNER_PRIZE_ROOM` | 22 | -| `MAP_CELADON_GYM` | 22 | -| `MAP_CELADON_MANSION_1F` | 22 | -| `MAP_CELADON_MANSION_2F` | 22 | -| `MAP_CELADON_MANSION_3F` | 22 | -| `MAP_CELADON_MANSION_ROOF` | 22 | -| `MAP_CELADON_MANSION_ROOF_HOUSE` | 22 | -| `MAP_CERULEAN_CITY` | 21 | -| `MAP_CERULEAN_GYM` | 21 | -| `MAP_CHERRYGROVE_CITY` | 01 | -| `MAP_CHERRYGROVE_EVOLUTION_SPEECH_HOUSE` | 01 | -| `MAP_CHERRYGROVE_GYM_SPEECH_HOUSE` | 01 | -| `MAP_CHERRYGROVE_MART` | 01 | -| `MAP_CHERRYGROVE_POKECENTER_1F` | 01 | -| `MAP_CIANWOOD_CITY` | 08 | -| `MAP_CIANWOOD_GYM` | 08 | -| `MAP_CIANWOOD_LUGIA_SPEECH_HOUSE` | 08 | -| `MAP_CIANWOOD_PHARMACY` | 08 | -| `MAP_CIANWOOD_PHOTO_STUDIO` | 08 | -| `MAP_CIANWOOD_POKECENTER_1F` | 08 | -| `MAP_CINNABAR_ISLAND` | 27 | -| `MAP_CINNABAR_POKECENTER_1F` | 27 | -| `MAP_COPYCATS_HOUSE_1F` | 22 | -| `MAP_COPYCATS_HOUSE_2F` | 22 | -| `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE` | 15, 16 | -| `MAP_DARK_CAVE_VIOLET_ENTRANCE` | 01, 15, 16 | -| `MAP_DAY_CARE` | 05 | -| `MAP_DAY_OF_WEEK_SIBLINGS_HOUSE` | 16 | -| `MAP_DIGLETTS_CAVE` | 24 | -| `MAP_DRAGONS_DEN_1F` | 13 | -| `MAP_DRAGONS_DEN_B1F` | 13 | -| `MAP_ECRUTEAK_CITY` | 10, 25 | -| `MAP_ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 14, 25 | -| `MAP_ECRUTEAK_TIN_TOWER_ENTRANCE` | 14, 25 | -| `MAP_ELMS_LAB` | 00, 01, 16, 19 | -| `MAP_FAST_SHIP_1F` | 19 | -| `MAP_FAST_SHIP_B1F` | 19 | -| `MAP_FAST_SHIP_CABINS_NNW_NNE_NE` | 19 | -| `MAP_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN` | 19 | -| `MAP_FAST_SHIP_CABINS_SW_SSW_NW` | 19 | -| `MAP_FIGHTING_DOJO` | 20 | -| `MAP_FUCHSIA_CITY` | 23 | -| `MAP_FUCHSIA_GYM` | 23 | -| `MAP_FUCHSIA_POKECENTER_1F` | 23 | -| `MAP_GOLDENROD_BIKE_SHOP` | 05 | -| `MAP_GOLDENROD_CITY` | 05, 09, 12 | -| `MAP_GOLDENROD_DEPT_STORE_1F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_2F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_3F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_4F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_5F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_6F` | 05 | -| `MAP_GOLDENROD_DEPT_STORE_B1F` | 12 | -| `MAP_GOLDENROD_FLOWER_SHOP` | 05, 06 | -| `MAP_GOLDENROD_GAME_CORNER` | 05 | -| `MAP_GOLDENROD_GYM` | 05 | -| `MAP_GOLDENROD_MAGNET_TRAIN_STATION` | 05 | -| `MAP_GOLDENROD_POKECENTER_1F` | 05 | -| `MAP_GOLDENROD_UNDERGROUND` | 05, 09, 12 | -| `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 05, 09, 12 | -| `MAP_GOLDENROD_UNDERGROUND_WAREHOUSE` | 12 | -| `MAP_GUIDE_GENTS_HOUSE` | 01 | -| `MAP_HALL_OF_FAME` | 18 | -| `MAP_ICE_PATH_1F` | 13 | -| `MAP_ICE_PATH_B1F` | 13 | -| `MAP_ICE_PATH_B2F_BLACKTHORN_SIDE` | 13 | -| `MAP_ICE_PATH_B2F_MAHOGANY_SIDE` | 13 | -| `MAP_ICE_PATH_B3F` | 13 | -| `MAP_ILEX_FOREST` | 05 | -| `MAP_ILEX_FOREST_AZALEA_GATE` | 05 | -| `MAP_INDIGO_PLATEAU_POKECENTER_1F` | 18 | -| `MAP_KARENS_ROOM` | 18 | -| `MAP_KOGAS_ROOM` | 18 | -| `MAP_KURTS_HOUSE` | 04 | -| `MAP_LAKE_OF_RAGE` | 10 | -| `MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE` | 10 | -| `MAP_LAKE_OF_RAGE_MAGIKARP_HOUSE` | 10 | -| `MAP_LANCES_ROOM` | 18 | -| `MAP_LAVENDER_TOWN` | 22 | -| `MAP_LAV_RADIO_TOWER_1F` | 22 | -| `MAP_MAHOGANY_GYM` | 11 | -| `MAP_MAHOGANY_MART_1F` | 10, 11 | -| `MAP_MAHOGANY_POKECENTER_1F` | 10, 11 | -| `MAP_MAHOGANY_RED_GYARADOS_SPEECH_HOUSE` | 10 | -| `MAP_MAHOGANY_TOWN` | 10, 11, 15 | -| `MAP_MANIAS_HOUSE` | 08 | -| `MAP_MOUNT_MOON` | 26 | -| `MAP_MOUNT_MOON_GIFT_SHOP` | 26 | -| `MAP_MOUNT_MOON_SQUARE` | 26 | -| `MAP_MOUNT_MORTAR_1F_INSIDE` | 15 | -| `MAP_MOUNT_MORTAR_1F_OUTSIDE` | 15 | -| `MAP_MOUNT_MORTAR_2F_INSIDE` | 15 | -| `MAP_MOUNT_MORTAR_B1F` | 15 | -| `MAP_MOVE_DELETERS_HOUSE` | 13 | -| `MAP_MR_POKEMONS_HOUSE` | 01 | -| `MAP_MR_PSYCHICS_HOUSE` | 20 | -| `MAP_NATIONAL_PARK` | 06 | -| `MAP_NATIONAL_PARK_BUG_CONTEST` | 06 | -| `MAP_NEW_BARK_TOWN` | 00, 01, 16, 19 | -| `MAP_OAKS_LAB` | 26, 29 | -| `MAP_OLIVINE_CAFE` | 08 | -| `MAP_OLIVINE_CITY` | 08, 09, 19 | -| `MAP_OLIVINE_GOOD_ROD_HOUSE` | 08 | -| `MAP_OLIVINE_GYM` | 09 | -| `MAP_OLIVINE_LIGHTHOUSE_1F` | 08, 09 | -| `MAP_OLIVINE_LIGHTHOUSE_2F` | 08, 09 | -| `MAP_OLIVINE_LIGHTHOUSE_3F` | 08, 09 | -| `MAP_OLIVINE_LIGHTHOUSE_4F` | 08, 09 | -| `MAP_OLIVINE_LIGHTHOUSE_5F` | 08, 09 | -| `MAP_OLIVINE_LIGHTHOUSE_6F` | 08, 09 | -| `MAP_OLIVINE_MART` | 08 | -| `MAP_OLIVINE_POKECENTER_1F` | 08 | -| `MAP_OLIVINE_PORT` | 19 | -| `MAP_OLIVINE_PORT_PASSAGE` | 19 | -| `MAP_PALLET_TOWN` | 26, 29 | -| `MAP_PEWTER_CITY` | 24 | -| `MAP_PEWTER_GYM` | 24 | -| `MAP_PEWTER_POKECENTER_1F` | 24 | -| `MAP_PLAYERS_HOUSE_1F` | 00, 01 | -| `MAP_PLAYERS_HOUSE_2F` | 00 | -| `MAP_POKEMON_FAN_CLUB` | 19, 22 | -| `MAP_POWER_PLANT` | 21, 22 | -| `MAP_RADIO_TOWER_1F` | 05, 12 | -| `MAP_RADIO_TOWER_2F` | 12 | -| `MAP_RADIO_TOWER_3F` | 12 | -| `MAP_RADIO_TOWER_4F` | 12 | -| `MAP_RADIO_TOWER_5F` | 12 | -| `MAP_ROCK_TUNNEL_1F` | 22 | -| `MAP_ROCK_TUNNEL_B1F` | 22 | -| `MAP_ROUTE_1` | 26 | -| `MAP_ROUTE_10_NORTH` | 21, 22 | -| `MAP_ROUTE_10_SOUTH` | 22 | -| `MAP_ROUTE_11` | 23 | -| `MAP_ROUTE_12` | 23 | -| `MAP_ROUTE_12_SUPER_ROD_HOUSE` | 23 | -| `MAP_ROUTE_13` | 23 | -| `MAP_ROUTE_14` | 23 | -| `MAP_ROUTE_15` | 23 | -| `MAP_ROUTE_15_FUCHSIA_GATE` | 23 | -| `MAP_ROUTE_16` | 23 | -| `MAP_ROUTE_16_FUCHSIA_SPEECH_HOUSE` | 23 | -| `MAP_ROUTE_16_GATE` | 23 | -| `MAP_ROUTE_17` | 23 | -| `MAP_ROUTE_17_ROUTE_18_GATE` | 23 | -| `MAP_ROUTE_18` | 23 | -| `MAP_ROUTE_19` | 24, 28 | -| `MAP_ROUTE_19_FUCHSIA_GATE` | 24, 28 | -| `MAP_ROUTE_2` | 24 | -| `MAP_ROUTE_20` | 24, 27, 28 | -| `MAP_ROUTE_21` | 27 | -| `MAP_ROUTE_22` | 29 | -| `MAP_ROUTE_23` | 17, 18 | -| `MAP_ROUTE_24` | 21 | -| `MAP_ROUTE_25` | 21 | -| `MAP_ROUTE_26` | 16 | -| `MAP_ROUTE_26_HEAL_HOUSE` | 16 | -| `MAP_ROUTE_27` | 16 | -| `MAP_ROUTE_27_SANDSTORM_HOUSE` | 16 | -| `MAP_ROUTE_28` | 29 | -| `MAP_ROUTE_28_STEEL_WING_HOUSE` | 29 | -| `MAP_ROUTE_29` | 01, 32 | -| `MAP_ROUTE_29_ROUTE_46_GATE` | 01, 16 | -| `MAP_ROUTE_2_GATE` | 24 | -| `MAP_ROUTE_2_NUGGET_HOUSE` | 24 | -| `MAP_ROUTE_3` | 26 | -| `MAP_ROUTE_30` | 01, 32 | -| `MAP_ROUTE_30_BERRY_HOUSE` | 01 | -| `MAP_ROUTE_31` | 01, 15, 32 | -| `MAP_ROUTE_31_VIOLET_GATE` | 01 | -| `MAP_ROUTE_32` | 03, 30, 32 | -| `MAP_ROUTE_32_POKECENTER_1F` | 03 | -| `MAP_ROUTE_32_RUINS_OF_ALPH_GATE` | 03 | -| `MAP_ROUTE_33` | 03, 32 | -| `MAP_ROUTE_34` | 05, 32 | -| `MAP_ROUTE_34_ILEX_FOREST_GATE` | 05 | -| `MAP_ROUTE_35` | 06, 32 | -| `MAP_ROUTE_35_GOLDENROD_GATE` | 06 | -| `MAP_ROUTE_35_NATIONAL_PARK_GATE` | 06 | -| `MAP_ROUTE_36` | 06, 32 | -| `MAP_ROUTE_36_NATIONAL_PARK_GATE` | 06 | -| `MAP_ROUTE_36_RUINS_OF_ALPH_GATE` | 03 | -| `MAP_ROUTE_37` | 06, 32 | -| `MAP_ROUTE_38` | 08, 32 | -| `MAP_ROUTE_38_ECRUTEAK_GATE` | 08 | -| `MAP_ROUTE_39` | 08, 32 | -| `MAP_ROUTE_39_BARN` | 08 | -| `MAP_ROUTE_39_FARMHOUSE` | 08 | -| `MAP_ROUTE_4` | 26 | -| `MAP_ROUTE_40` | 08, 32 | -| `MAP_ROUTE_41` | 08, 14, 25, 32 | -| `MAP_ROUTE_42` | 10, 15, 32 | -| `MAP_ROUTE_42_ECRUTEAK_GATE` | 10 | -| `MAP_ROUTE_43` | 10, 32 | -| `MAP_ROUTE_43_GATE` | 10 | -| `MAP_ROUTE_43_MAHOGANY_GATE` | 10 | -| `MAP_ROUTE_44` | 13, 32 | -| `MAP_ROUTE_45` | 15, 16, 32 | -| `MAP_ROUTE_46` | 01, 15, 16, 32 | -| `MAP_ROUTE_5` | 21 | -| `MAP_ROUTE_5_CLEANSE_TAG_HOUSE` | 21 | -| `MAP_ROUTE_5_SAFFRON_GATE` | 21 | -| `MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE` | 21 | -| `MAP_ROUTE_6` | 20 | -| `MAP_ROUTE_6_SAFFRON_GATE` | 20 | -| `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | 20 | -| `MAP_ROUTE_7` | 22 | -| `MAP_ROUTE_7_SAFFRON_GATE` | 22 | -| `MAP_ROUTE_8` | 22 | -| `MAP_ROUTE_8_SAFFRON_GATE` | 22 | -| `MAP_ROUTE_9` | 21 | -| `MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER` | 03 | -| `MAP_RUINS_OF_ALPH_INNER_CHAMBER` | 03 | -| `MAP_RUINS_OF_ALPH_KABUTO_CHAMBER` | 03 | -| `MAP_RUINS_OF_ALPH_OUTSIDE` | 03 | -| `MAP_RUINS_OF_ALPH_RESEARCH_CENTER` | 03 | -| `MAP_SAFFRON_CITY` | 20, 22 | -| `MAP_SAFFRON_GYM` | 20 | -| `MAP_SAFFRON_MAGNET_TRAIN_STATION` | 20, 22 | -| `MAP_SAFFRON_MART` | 20 | -| `MAP_SAFFRON_POKECENTER_1F` | 20 | -| `MAP_SEAFOAM_GYM` | 27 | -| `MAP_SILPH_CO_1F` | 20 | -| `MAP_SILVER_CAVE_ITEM_ROOMS` | 31 | -| `MAP_SILVER_CAVE_OUTSIDE` | 29, 31 | -| `MAP_SILVER_CAVE_POKECENTER_1F` | 29, 31 | -| `MAP_SILVER_CAVE_ROOM_1` | 31 | -| `MAP_SILVER_CAVE_ROOM_2` | 31 | -| `MAP_SILVER_CAVE_ROOM_3` | 31 | -| `MAP_SLOWPOKE_WELL_B1F` | 04 | -| `MAP_SLOWPOKE_WELL_B2F` | 04 | -| `MAP_SOUL_HOUSE` | 22 | -| `MAP_SPROUT_TOWER_1F` | 02 | -| `MAP_SPROUT_TOWER_2F` | 02 | -| `MAP_SPROUT_TOWER_3F` | 02 | -| `MAP_TEAM_ROCKET_BASE_B1F` | 11 | -| `MAP_TEAM_ROCKET_BASE_B2F` | 11 | -| `MAP_TEAM_ROCKET_BASE_B3F` | 11 | -| `MAP_TIN_TOWER_1F` | 14, 25 | -| `MAP_TIN_TOWER_2F` | 14, 25 | -| `MAP_TIN_TOWER_3F` | 14, 25 | -| `MAP_TIN_TOWER_4F` | 14, 25 | -| `MAP_TIN_TOWER_5F` | 14, 25 | -| `MAP_TIN_TOWER_6F` | 14, 25 | -| `MAP_TIN_TOWER_7F` | 14, 25 | -| `MAP_TIN_TOWER_8F` | 14, 25 | -| `MAP_TIN_TOWER_9F` | 14, 25 | -| `MAP_TIN_TOWER_ROOF` | 14, 25 | -| `MAP_TOHJO_FALLS` | 16 | -| `MAP_TRAINER_HOUSE_1F` | 26 | -| `MAP_TRAINER_HOUSE_B1F` | 26 | -| `MAP_UNION_CAVE_1F` | 03, 30 | -| `MAP_UNION_CAVE_B1F` | 03, 30 | -| `MAP_UNION_CAVE_B2F` | 03, 30 | -| `MAP_VERMILION_CITY` | 19, 22, 24 | -| `MAP_VERMILION_GYM` | 19 | -| `MAP_VERMILION_POKECENTER_1F` | 24 | -| `MAP_VERMILION_PORT` | 19 | -| `MAP_VERMILION_PORT_PASSAGE` | 19 | -| `MAP_VICTORY_ROAD` | 17 | -| `MAP_VICTORY_ROAD_GATE` | 16, 17, 29, 31 | -| `MAP_VIOLET_CITY` | 01, 02, 32 | -| `MAP_VIOLET_GYM` | 02 | -| `MAP_VIOLET_POKECENTER_1F` | 02, 03 | -| `MAP_VIRIDIAN_CITY` | 26, 28, 29 | -| `MAP_VIRIDIAN_GYM` | 28 | -| `MAP_VIRIDIAN_POKECENTER_1F` | 28 | -| `MAP_WHIRL_ISLAND_B1F` | 14, 25 | -| `MAP_WHIRL_ISLAND_B2F` | 14, 25 | -| `MAP_WHIRL_ISLAND_CAVE` | 14, 25 | -| `MAP_WHIRL_ISLAND_LUGIA_CHAMBER` | 14, 25 | -| `MAP_WHIRL_ISLAND_NE` | 14, 25 | -| `MAP_WHIRL_ISLAND_NW` | 14, 25 | -| `MAP_WHIRL_ISLAND_SE` | 14, 25 | -| `MAP_WHIRL_ISLAND_SW` | 14, 25 | -| `MAP_WILLS_ROOM` | 18 | - diff --git a/docs/gold-walkthrough/asm-walk/_TEMPLATE.md b/docs/gold-walkthrough/asm-walk/_TEMPLATE.md deleted file mode 100644 index 34b61843..00000000 --- a/docs/gold-walkthrough/asm-walk/_TEMPLATE.md +++ /dev/null @@ -1,126 +0,0 @@ -# ASM Walk output format - -Every `section-NN-*.md` in this folder follows the structure below. The goal is a -single place a bot author can look at to answer "for this stretch of the -walkthrough, which maps, warps, objects, scripts, flags, items and trainers are -involved, and where in the pokegold disassembly do they live?" - -Conventions: - -- Disassembly paths are written relative to the pokegold checkout root, e.g. - `maps/NewBarkTown.asm`, `data/maps/maps.asm`, `constants/event_flags.asm`. -- Port paths are written relative to this repo root, e.g. `src/world/gen2/Map.lua`. -- Coordinates are the raw asm values (map-block coordinates as written in the - `warp_event` / `object_event` / `bg_event` rows), not screen pixels. -- Anything not verified by opening the file is omitted rather than guessed. If a - walkthrough claim could not be located in the asm, it goes under - "Unresolved / verify by hand". - ---- - -# Section NN - - -Source: `../section-NN-<slug>.txt` -Maps covered: `MAP_A`, `MAP_B`, ... -Badges / key milestones in this section: ... - -## 1. Route order - -Ordered list of map transitions the walkthrough takes, one row per hop. - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| - -## 2. Maps - -Repeat this block per map. - -### MAP_<NAME> - -- Script: `maps/<Name>.asm` -- Blocks: `maps/<Name>.blk` (if present) -- Header: `data/maps/maps.asm` -> tileset, environment, location, music, phone, palette -- Dimensions / attributes: `constants/map_constants.asm` (`map_const <NAME>, W, H`) -- Connections: north/south/east/west map constants - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| - -**Coord events** (`def_coord_events`) - the scripted trip-wires a bot will hit - -| scene | x | y | script label | effect | -|---|---|---|---|---| - -**BG events** (`def_bg_events`) - signs, hidden items - -| x | y | type | script/item | -|---|---|---|---| - -**Object events** (`def_object_events`) - NPCs, item balls, trainers - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| - -**Scripts of interest** - -For each script label the walkthrough actually depends on: label, a short prose -summary of what the opcodes do, and the flags/items/battles it touches. Cite the -label name so it can be grepped. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| - -Include `EVENT_*` (`constants/event_flags.asm`), `ENGINE_*` -(`constants/engine_flags.asm`), and any `SCENE_*` values -(`constants/map_setup_constants.asm` / the map's own `object_const_def` block). - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| - -**Wild encounters** - -Table entry in `data/wild/johto_grass.asm` / `johto_water.asm` / -`kanto_grass.asm` / `kanto_water.asm`, plus fishing (`data/wild/fish.asm`), -headbutt (`data/wild/treemons.asm`) and rock smash where relevant. Note the -morn/day/nite split. - -## 3. Blockers and gates - -What actually stops forward progress in this section, and the precise check. - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| - -Cover badge checks, HM field-move checks (`engine/overworld/`), key items, -`EVENT_*` guards, and NPC-blocked tiles. - -## 4. Bot checklist - -The machine-actionable version: an ordered list of steps a scripted playthrough -would execute. Each step names the map, the target coordinates or object const, -the input intent (talk / walk / use item / battle), the precondition flag and the -postcondition flag. Keep it literal enough that someone can turn a row into a -driver command without reopening the asm. - -## 5. Port coverage - -Where this section's mechanics land in this repo, and what is missing. - -| Beat | Port file | Status | -|---|---|---| - -## 6. Unresolved / verify by hand - -Anything the walkthrough asserts that could not be pinned to a specific asm -location, plus any contradictions found between the two. diff --git a/docs/gold-walkthrough/asm-walk/section-00-chikorita-cyndaquil-or-totodile.md b/docs/gold-walkthrough/asm-walk/section-00-chikorita-cyndaquil-or-totodile.md deleted file mode 100644 index 0984319b..00000000 --- a/docs/gold-walkthrough/asm-walk/section-00-chikorita-cyndaquil-or-totodile.md +++ /dev/null @@ -1,535 +0,0 @@ -# Section 00 - Chikorita, Cyndaquil, or Totodile? - -Source: `../section-00-chikorita-cyndaquil-or-totodile.txt` -Maps covered: `MAP_PLAYERS_HOUSE_2F`, `MAP_PLAYERS_HOUSE_1F`, `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB` -Badges / key milestones in this section: no badge. Milestones are the new-game -clock set, the #GEAR from Mom (`ENGINE_POKEGEAR`), the first rival encounter, -and the starter (`EVENT_GOT_A_POKEMON_FROM_ELM`), which is what un-gates the -walk west out of New Bark Town. - -Coordinate note: every `warp_event` / `coord_event` / `bg_event` / -`object_event` x,y below is copied verbatim from the map asm. Those are 0-based -walk-grid cells (2 per map block, so a `W`-block-wide map runs x = 0..2W-1), -which is the same grid `src/world/gen2/Map.lua` calls a cell. - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | (no map) | `engine/menus/intro_menu.asm` `NewGame` / `OakSpeech`, `engine/rtc/timeset.asm` `InitClock` | title screen -> NEW GAME | `InitializeWorld` -> `SPAWN_HOME` | "Choose new game and start… Professor Oak will ask you the time… he asks you what your name is" | -| 1 | `MAP_PLAYERS_HOUSE_2F` | `maps/PlayersHouse2F.asm` | spawn `SPAWN_HOME` at (3,3), `data/maps/spawn_points.asm` | warp 1 at (7,0) | "You are in your room at the beginning. Exit the room" | -| 2 | `MAP_PLAYERS_HOUSE_1F` | `maps/PlayersHouse1F.asm` | warp 3 at (9,0) | warp 1 (6,7) or warp 2 (7,7) | "you will meet your mom… You get the Pokegear… input the day of the week" | -| 3 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | warp 2 at (13,5) | warp 1 at (6,3) | "Leave your house and head left to Professor Elm's lab. Notice the guy standing left of it" (rival at (3,2)) | -| 4 | `MAP_ELMS_LAB` | `maps/ElmsLab.asm` | warp 1 (4,11) / warp 2 (5,11) | warp 1/2 back to New Bark Town | "go into Elm's lab… You get to pick your starter Pokémon" | -| 5 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | warp 1 at (6,3) | west connection to `ROUTE_29` (`data/maps/attributes.asm`) | section ends here; the walk west to Route 29 / Cherrygrove belongs to section 01 | - -## 2. Maps - -### MAP_PLAYERS_HOUSE_2F - -- Script: `maps/PlayersHouse2F.asm` -- Blocks: `maps/PlayersHouse2F.blk` -- Header (`data/maps/maps.asm:478`): `map PlayersHouse2F, TILESET_PLAYERS_ROOM, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:443`): `map_const PLAYERS_HOUSE_2F, 4, 3` (4x3 blocks = 8x6 cells) -- Connections: none (indoor) -- Scene var: `wPlayersHouse2FSceneID` is *not* in `data/maps/scenes.asm`; the map has an empty `def_scene_scripts` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 0 | `PLAYERS_HOUSE_1F` | 3 | - -**Coord events** (`def_coord_events`) - -None (the block is empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 1 | `BGEVENT_UP` | `PlayersHousePCScript` | -| 3 | 1 | `BGEVENT_READ` | `PlayersHouseRadioScript` | -| 5 | 1 | `BGEVENT_READ` | `PlayersHouseBookshelfScript` | -| 6 | 0 | `BGEVENT_IFSET` | `PlayersHousePosterScript` (guarded by `EVENT_PLAYERS_ROOM_POSTER`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PLAYERSHOUSE2F_CONSOLE` | `SPRITE_CONSOLE` | 4 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseGameConsoleScript` | `EVENT_PLAYERS_HOUSE_2F_CONSOLE` | -| `PLAYERSHOUSE2F_DOLL_1` | `SPRITE_DOLL_1` | 4 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseDoll1Script` | `EVENT_PLAYERS_HOUSE_2F_DOLL_1` | -| `PLAYERSHOUSE2F_DOLL_2` | `SPRITE_DOLL_2` | 5 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseDoll2Script` | `EVENT_PLAYERS_HOUSE_2F_DOLL_2` | -| `PLAYERSHOUSE2F_BIG_DOLL` | `SPRITE_BIG_DOLL` | 0 | 1 | `SPRITEMOVEDATA_BIGDOLL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseBigDollScript` | `EVENT_PLAYERS_HOUSE_2F_BIG_DOLL` | - -**Scripts of interest** - -- `PlayersHouse2FInitializeRoomCallback` (`callback MAPCALLBACK_NEWMAP`): runs - `special ToggleDecorationsVisibility`, sets - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8` (read by `engine/phone/scripts/mom.asm`), - then `checkevent EVENT_INITIALIZED_EVENTS` -> if clear, - `jumpstd InitializeEventsScript`. This is the one-time world seeding for a new - game: `engine/events/std_scripts.asm:438` sets ~70 `EVENT_*` bits including - `EVENT_COP_IN_ELMS_LAB` (which *hides* the officer), `EVENT_RIVAL_CHERRYGROVE_CITY`, - `EVENT_ROUTE_30_YOUNGSTER_JOEY`, and finally `EVENT_INITIALIZED_EVENTS` - (`engine/events/std_scripts.asm:557`) so it never runs again. -- `PlayersHouse2FSetUpTileDecorationsCallback` (`callback MAPCALLBACK_TILES`): - `special ToggleMaptileDecorations`. -- `PlayersHouseRadioScript`: three-branch. Before `EVENT_GOT_A_POKEMON_FROM_ELM` - it plays `MUSIC_POKEMON_TALK`, prints four texts and sets - `EVENT_LISTENED_TO_INITIAL_RADIO`; after that flag it prints only - `PlayersRadioText4`; after `EVENT_GOT_A_POKEMON_FROM_ELM` it becomes the normal - `jumpstd Radio1Script`. Purely flavour, no gate. -- `PlayersHousePCScript`: `special PlayersHousePC`, `iftrue .Warp` -> `warp NONE, 0, 0` - (the "leave the PC by warping in place" reload). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_INITIALIZED_EVENTS` | `constants/event_flags.asm:63` | read by `PlayersHouse2FInitializeRoomCallback`, set by `InitializeEventsScript` | first-load-only world seed; must be clear on a fresh save | -| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8` | `constants/event_flags.asm` | set here, read by `engine/phone/scripts/mom.asm:13` | "player is at home right now" | -| `EVENT_LISTENED_TO_INITIAL_RADIO` | `constants/event_flags.asm:468` | `PlayersHouseRadioScript` | shortens the radio text on repeat | -| `EVENT_PLAYERS_ROOM_POSTER` | `constants/event_flags.asm:378` | `conditional_event` in `PlayersHousePosterScript` | decoration present | - -**Items**: none on this map. - -**Trainers**: none. - -**Wild encounters**: none (indoor). - -### MAP_PLAYERS_HOUSE_1F - -- Script: `maps/PlayersHouse1F.asm` -- Blocks: `maps/PlayersHouse1F.blk` -- Header (`data/maps/maps.asm:477`): `map PlayersHouse1F, TILESET_PLAYERS_HOUSE, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:442`): `map_const PLAYERS_HOUSE_1F, 5, 4` (10x8 cells) -- Connections: none (indoor) -- Scene var: `wPlayersHouse1FSceneID` (`data/maps/scenes.asm:30`). Scene ids come - from declaration order in `def_scene_scripts` (`macros/scripts/maps.asm:25`): - `SCENE_PLAYERSHOUSE1F_MEET_MOM` = 0, `SCENE_PLAYERSHOUSE1F_NOOP` = 1. A new game - zeroes the scene, so scene 0 is live on the first visit. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 7 | `NEW_BARK_TOWN` | 2 | -| 2 | 7 | 7 | `NEW_BARK_TOWN` | 2 | -| 3 | 9 | 0 | `PLAYERS_HOUSE_2F` | 1 | - -**Coord events** (`def_coord_events`) - -None. The Mom cutscene is a **scene script**, not a coord event: `scene_script -PlayersHouse1FMeetMomScene, SCENE_PLAYERSHOUSE1F_MEET_MOM` -> `sdefer MeetMomScript`, -so it fires on map entry from the stairs while the scene id is still 0. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `PlayersHouse1FStoveScript` | -| 1 | 1 | `BGEVENT_READ` | `PlayersHouse1FSinkScript` | -| 2 | 1 | `BGEVENT_READ` | `PlayersHouse1FFridgeScript` | -| 4 | 1 | `BGEVENT_READ` | `PlayersHouse1FTVScript` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PLAYERSHOUSE1F_MOM1` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` (time `-1`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_1` | -| `PLAYERSHOUSE1F_MOM2` | `SPRITE_MOM` | 2 | 2 | `SPRITEMOVEDATA_STANDING_UP` (time `MORN`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | -| `PLAYERSHOUSE1F_MOM3` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` (time `DAY`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | -| `PLAYERSHOUSE1F_MOM4` | `SPRITE_MOM` | 0 | 2 | `SPRITEMOVEDATA_STANDING_UP` (time `NITE`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | - -Note the object flags are inverted relative to intuition: a *set* event flag hides -the object. Mom1 is the cutscene Mom (visible while `EVENT_PLAYERS_HOUSE_MOM_1` -is clear); `MeetMomScript` ends by setting `EVENT_PLAYERS_HOUSE_MOM_1` and -clearing `EVENT_PLAYERS_HOUSE_MOM_2`, which swaps in the three time-of-day Moms -on the next map load. - -**Scripts of interest** - -- `MeetMomScript` (the whole Pokegear beat, `maps/PlayersHouse1F.asm:21`): - 1. `applymovement PLAYER, PlayersHouseDownstairsMovement` (one `step DOWN`, i.e. off the stair tile). - 2. `playmusic MUSIC_MOM`, `turnobject PLAYERSHOUSE1F_MOM1, UP`, `showemote EMOTE_SHOCK, PLAYERSHOUSE1F_MOM1, 15`. - 3. `applymovement PLAYERSHOUSE1F_MOM1, MomWalksToPlayerMovement` (`slow_step RIGHT, RIGHT, UP`: (7,3) -> (9,2), beside the player at (9,1)). - 4. `writetext ElmsLookingForYouText`, `getstring STRING_BUFFER_4, PokegearName` ("#GEAR"), `scall PlayersHouse1FReceiveItemStd` -> `jumpstd ReceiveItemScript` (`engine/events/std_scripts.asm:656`). - 5. `setflag ENGINE_POKEGEAR`, `setflag ENGINE_PHONE_CARD`, `addcellnum PHONE_MOM`. - 6. `setscene SCENE_PLAYERSHOUSE1F_NOOP`, `setevent EVENT_PLAYERS_HOUSE_MOM_1`, `clearevent EVENT_PLAYERS_HOUSE_MOM_2`. - 7. `special SetDayOfWeek`, then the DST loop: `IsItDSTText` -> `yesorno` -> `special InitialSetDSTFlag` / `special InitialClearDSTFlag`, each confirmed by a second `yesorno` that loops back to `.SetDayOfWeek` on "no". - 8. Phone-instructions branch (`.KnowPhone` / `.ExplainPhone`, both fall into `.FinishPhone`), then `applymovement PLAYERSHOUSE1F_MOM1, MomWalksBackMovement` and `special RestartMapMusic`. - Crucially the #GEAR is **not** an inventory item: it is two engine flags. There - is no `giveitem` in this script. -- `MomScript` (talking to Mom afterwards): checks, in order, - `EVENT_FIRST_TIME_BANKING_WITH_MOM` -> `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST` - -> `EVENT_GAVE_MYSTERY_EGG_TO_ELM` -> `EVENT_GOT_A_POKEMON_FROM_ELM`; in this - section all four are clear so it prints `HurryUpElmIsWaitingText`. The - `special BankOfMom` path is not reachable yet. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_POKEGEAR` | `constants/engine_flags.asm:8` | set by `MeetMomScript` | Pokegear menu becomes usable | -| `ENGINE_PHONE_CARD` | `constants/engine_flags.asm:6` | set by `MeetMomScript` | Pokegear PHONE card unlocked | -| `EVENT_PLAYERS_HOUSE_MOM_1` | `constants/event_flags.asm:1129` | set by `MeetMomScript` | hides the cutscene Mom | -| `EVENT_PLAYERS_HOUSE_MOM_2` | `constants/event_flags.asm:1130` | cleared by `MeetMomScript` | reveals the time-of-day Moms | -| `SCENE_PLAYERSHOUSE1F_MEET_MOM` (0) / `SCENE_PLAYERSHOUSE1F_NOOP` (1) | `maps/PlayersHouse1F.asm:9-10` | `setscene` at step 6 | scene 0 must be live exactly once | -| `PHONE_MOM` | `constants/phone_constants.asm` (via `addcellnum`) | `MeetMomScript` | Mom's number in the Pokegear | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| POKEGEAR (not an item - `ENGINE_POKEGEAR` + `ENGINE_PHONE_CARD`) | Mom cutscene | `MeetMomScript` | `EVENT_PLAYERS_HOUSE_MOM_1` / `SCENE_PLAYERSHOUSE1F_NOOP` | - -**Trainers**: none. - -**Wild encounters**: none (indoor). - -### MAP_NEW_BARK_TOWN - -- Script: `maps/NewBarkTown.asm` -- Blocks: `maps/NewBarkTown.blk` -- Header (`data/maps/maps.asm:475`): `map NewBarkTown, TILESET_JOHTO, TOWN, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions (`constants/map_constants.asm:440`): `map_const NEW_BARK_TOWN, 10, 9` (20x18 cells) -- Connections (`data/maps/attributes.asm:119-121`): `map_attributes NewBarkTown, NEW_BARK_TOWN, $05`, `connection west, Route29, ROUTE_29, 0`, `connection east, Route27, ROUTE_27, 0` -- Scene var: `wNewBarkTownSceneID` (`data/maps/scenes.asm:28`). - `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` = 0, `SCENE_NEWBARKTOWN_NOOP` = 1. -- Fly point / spawn: `NewBarkTownFlypointCallback` sets `ENGINE_FLYPOINT_NEW_BARK` - (`constants/engine_flags.asm:79`) on every `MAPCALLBACK_NEWMAP`, and also - `clearevent EVENT_FIRST_TIME_BANKING_WITH_MOM`. `SPAWN_NEW_BARK` is - (13,6) in `data/maps/spawn_points.asm:27`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 3 | `ELMS_LAB` | 1 | -| 2 | 13 | 5 | `PLAYERS_HOUSE_1F` | 1 | -| 3 | 3 | 11 | `PLAYERS_NEIGHBORS_HOUSE` | 1 | -| 4 | 11 | 13 | `ELMS_HOUSE` | 1 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | 1 | 8 | `NewBarkTown_TeacherStopsYouScene1` | teacher walks 4x LEFT to (2,8), `follow`s the player and drags them back 4x RIGHT | -| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | 1 | 9 | `NewBarkTown_TeacherStopsYouScene2` | same, 5 steps, teacher ends at (1,8) facing DOWN | - -These two cells are the entire west-exit gate. There is no coord event on the -east side. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 8 | 8 | `BGEVENT_READ` | `NewBarkTownSign` | -| 11 | 5 | `BGEVENT_READ` | `NewBarkTownPlayersHouseSign` | -| 3 | 3 | `BGEVENT_READ` | `NewBarkTownElmsLabSign` | -| 9 | 13 | `BGEVENT_READ` | `NewBarkTownElmsHouseSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `NEWBARKTOWN_TEACHER` | `SPRITE_TEACHER` | 6 | 8 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `NewBarkTownTeacherScript` | `-1` (always visible) | -| `NEWBARKTOWN_FISHER` | `SPRITE_FISHER` | 12 | 9 | `SPRITEMOVEDATA_WALK_UP_DOWN`, `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `NewBarkTownFisherScript` | `-1` | -| `NEWBARKTOWN_RIVAL` | `SPRITE_RIVAL` | 3 | 2 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `NewBarkTownRivalScript` | `EVENT_RIVAL_NEW_BARK_TOWN` | - -**Scripts of interest** - -- `NewBarkTownRivalScript` (the "he'll push you out of the way" beat): no - `faceplayer`. Prints `NewBarkTownRivalText1` ("So this is the famous ELM #MON - LAB…"), `turnobject NEWBARKTOWN_RIVAL, LEFT`, prints `NewBarkTownRivalText2`, - then `follow PLAYER, NEWBARKTOWN_RIVAL` + - `applymovement PLAYER, NewBarkTown_RivalPushesYouAwayMovement` (`turn_head UP`, - `step DOWN`), `stopfollow`, `turnobject NEWBARKTOWN_RIVAL, DOWN`, - `playsound SFX_TACKLE`, `applymovement PLAYER, NewBarkTown_RivalShovesYouOutMovement` - (`fix_facing`, `jump_step DOWN`), and the rival steps RIGHT back to (3,2). - **It sets no flag** - it is repeatable and skippable. `EVENT_RIVAL_NEW_BARK_TOWN` - is only set much later, at `maps/MrPokemonsHouse.asm:122`. -- `NewBarkTown_TeacherStopsYouScene1/2`: the "It's dangerous to go out without a - #MON!" block. Ends with `special RestartMapMusic` and does **not** change the - scene, so it re-fires every time you step on (1,8)/(1,9) until the scene id is - changed from Elm's Lab. -- `NewBarkTownTeacherScript`: four-way text branch on - `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST` / `EVENT_GAVE_MYSTERY_EGG_TO_ELM` / - `EVENT_GOT_A_POKEMON_FROM_ELM`; flavour only. -- `NewBarkTownFlypointCallback`: `setflag ENGINE_FLYPOINT_NEW_BARK` on first entry. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | `maps/NewBarkTown.asm:8` | cleared to `SCENE_NEWBARKTOWN_NOOP` by `ElmDirectionsScript`'s `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP` | the west-exit gate | -| `EVENT_RIVAL_NEW_BARK_TOWN` | `constants/event_flags.asm:1119` | set at `maps/MrPokemonsHouse.asm:122` | rival object hidden once set; clear (visible) all through this section | -| `ENGINE_FLYPOINT_NEW_BARK` | `constants/engine_flags.asm:79` | `NewBarkTownFlypointCallback` | Fly destination registered | -| `EVENT_FIRST_TIME_BANKING_WITH_MOM` | `constants/event_flags.asm` | cleared by `NewBarkTownFlypointCallback` | not reachable this section | - -**Items**: none. See "Unresolved" for the Everstone. - -**Trainers**: none. The rival encounter here is a conversation, not a battle. - -**Wild encounters** - -- No `def_grass_wildmons NEW_BARK_TOWN` entry exists in `data/wild/johto_grass.asm`. -- Water (`data/wild/johto_water.asm:211`): `def_water_wildmons NEW_BARK_TOWN`, - `db 6 percent`, `db 20, TENTACOOL` / `db 15, TENTACOOL` / `db 20, TENTACRUEL`. - Needs Surf, unreachable this section. -- Fishing: the header's `FISHGROUP_OCEAN` selects - `data/wild/fish.asm` `.Ocean_Old` (Magikarp 10 / Magikarp 10 / Tentacool 10), - `.Ocean_Good`, `.Ocean_Super`. No rod yet. -- Headbutt: `data/wild/treemon_maps.asm:28` `treemon_map NEW_BARK_TOWN, TREEMON_SET_CITY` - -> `data/wild/treemons.asm:18` `TreeMonSet_City` (Venonat/Abra/Venomoth common, - Venonat/Magnemite/Abra/Venomoth rare, all L15). Needs TM02, far later. - -### MAP_ELMS_LAB - -- Script: `maps/ElmsLab.asm` -- Blocks: `maps/ElmsLab.blk` -- Header (`data/maps/maps.asm:476`): `map ElmsLab, TILESET_LAB, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_PROF_ELM, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:441`): `map_const ELMS_LAB, 5, 6` (10x12 cells) -- Connections: none (indoor) -- Scene var: `wElmsLabSceneID` (`data/maps/scenes.asm:29`). Declaration order - gives `SCENE_ELMSLAB_MEET_ELM` = 0, `SCENE_ELMSLAB_CANT_LEAVE` = 1, - `SCENE_ELMSLAB_NOOP` = 2, `SCENE_ELMSLAB_MEET_OFFICER` = 3, - `SCENE_ELMSLAB_UNUSED` = 4, `SCENE_ELMSLAB_AIDE_GIVES_POTION` = 5, - `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` = 6 (the last one is a bare - `scene_const`, no script row). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 11 | `NEW_BARK_TOWN` | 1 | -| 2 | 5 | 11 | `NEW_BARK_TOWN` | 1 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ELMSLAB_CANT_LEAVE` (1) | 4 | 6 | `LabTryToLeaveScript` | Elm turns DOWN, `LabWhereGoingText`, `applymovement PLAYER, ElmsLab_CantLeaveMovement` (one `step UP`) | -| `SCENE_ELMSLAB_CANT_LEAVE` (1) | 5 | 6 | `LabTryToLeaveScript` | same | -| `SCENE_ELMSLAB_MEET_OFFICER` (3) | 4 | 5 | `MeetCopScript` | post-theft, next section | -| `SCENE_ELMSLAB_MEET_OFFICER` (3) | 5 | 5 | `MeetCopScript2` | post-theft, next section | -| `SCENE_ELMSLAB_AIDE_GIVES_POTION` (5) | 4 | 8 | `AideScript_WalkPotion1` | aide walks 2x RIGHT, gives POTION, walks back | -| `SCENE_ELMSLAB_AIDE_GIVES_POTION` (5) | 5 | 8 | `AideScript_WalkPotion2` | aide walks 3x RIGHT, gives POTION, walks back | -| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` (6) | 4 | 8 | `AideScript_WalkBalls1` | 5x POKE_BALL, next section | -| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` (6) | 5 | 8 | `AideScript_WalkBalls2` | 5x POKE_BALL, next section | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 1 | `BGEVENT_READ` | `ElmsLabHealingMachine` | -| 6 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 7 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 8 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 9 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 0 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip1` | -| 1 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip2` | -| 2 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip3` | -| 3 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip4` | -| 6 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 7 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 8 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 9 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` | -| 9 | 3 | `BGEVENT_READ` | `ElmsLabTrashcan` | -| 5 | 0 | `BGEVENT_READ` | `ElmsLabWindow` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ELMSLAB_ELM` | `SPRITE_ELM` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ProfElmScript` | `-1` | -| `ELMSLAB_ELMS_AIDE` | `SPRITE_SCIENTIST` | 2 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `ElmsAideScript` | `EVENT_ELMS_AIDE_IN_LAB` | -| `ELMSLAB_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `CyndaquilPokeBallScript` | `EVENT_CYNDAQUIL_POKEBALL_IN_ELMS_LAB` | -| `ELMSLAB_POKE_BALL2` | `SPRITE_POKE_BALL` | 7 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `TotodilePokeBallScript` | `EVENT_TOTODILE_POKEBALL_IN_ELMS_LAB` | -| `ELMSLAB_POKE_BALL3` | `SPRITE_POKE_BALL` | 8 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `ChikoritaPokeBallScript` | `EVENT_CHIKORITA_POKEBALL_IN_ELMS_LAB` | -| `ELMSLAB_OFFICER` | `SPRITE_OFFICER` | 5 | 3 | `SPRITEMOVEDATA_STANDING_UP`, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `CopScript` | `EVENT_COP_IN_ELMS_LAB` | - -The ball order on screen is left-to-right **Cyndaquil (6,3), Totodile (7,3), -Chikorita (8,3)** - not the Pokedex order the walkthrough's title uses. The -officer is hidden all through this section because `InitializeEventsScript` -*sets* `EVENT_COP_IN_ELMS_LAB` (`engine/events/std_scripts.asm:469`). - -**Scripts of interest** - -- `ElmsLabMeetElmScene` (scene 0) -> `sdefer ElmsLabWalkUpToElmScript`: - `applymovement PLAYER, ElmsLab_WalkUpToElmMovement` (nine `step UP` then - `turn_head RIGHT`, i.e. from the door at (4,11) up to (4,2) beside Elm at - (5,2)), `turnobject ELMSLAB_ELM, LEFT`, `writetext ElmText_Intro` (the Mr. - Pokemon errand speech), then `setscene SCENE_ELMSLAB_CANT_LEAVE`. -- `CyndaquilPokeBallScript` / `TotodilePokeBallScript` / `ChikoritaPokeBallScript`: - identical shape. `checkevent EVENT_GOT_A_POKEMON_FROM_ELM` -> if set, just - `LookAtElmPokeBallScript`. Otherwise `turnobject ELMSLAB_ELM, DOWN`, - `reanchormap`, `pokepic <SPECIES>`, `cry <SPECIES>`, `closepokepic`, - `writetext Take<Species>Text`, `yesorno`; `iffalse DidntChooseStarterScript` - (fully cancellable, nothing is written). On yes: - `disappear ELMSLAB_POKE_BALL<n>`, `setevent EVENT_GOT_<SPECIES>_FROM_ELM`, - `ChoseStarterText`, `getmonname STRING_BUFFER_3`, `ReceivedStarterText`, - `playsound SFX_CAUGHT_MON`, then **`givepoke <SPECIES>, 5, BERRY`** - level 5, - holding a BERRY. Finally an `applymovement` that walks the player to (5,3), - directly below Elm, and `sjump ElmDirectionsScript`: - - Cyndaquil: `readvar VAR_FACING` / `ifequal RIGHT, ElmDirectionsScript` (skips the walk if you took it from the left side), else `AfterCyndaquilMovement` = LEFT, UP. - - Totodile: `AfterTotodileMovement` = LEFT, LEFT, UP. - - Chikorita: `AfterChikoritaMovement` = LEFT, LEFT, LEFT, UP. -- `ElmDirectionsScript` - the one script that opens the section up: - `turnobject PLAYER, UP`, three Elm texts, `addcellnum PHONE_ELM`, - `playsound SFX_REGISTER_PHONE_NUMBER`, then - `setevent EVENT_GOT_A_POKEMON_FROM_ELM`, - `setevent EVENT_RIVAL_CHERRYGROVE_CITY`, - `setscene SCENE_ELMSLAB_AIDE_GIVES_POTION`, - `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP`. - That last opcode is what removes the teacher gate. -- `AideScript_GivePotion` (reached by the (4,8)/(5,8) coord events once the scene - is 5): `writetext AideText_GiveYouPotion`, `verbosegiveitem POTION`, - `AideText_AlwaysBusy`, `setscene SCENE_ELMSLAB_NOOP`. One-shot, gated by the - scene id rather than an event flag. -- `LabTryToLeaveScript`: the "you cannot walk out yet" push-back described above. -- `ElmsLabHealingMachine` (bg_event at (2,1)): before - `EVENT_GOT_A_POKEMON_FROM_ELM` it just prints text; after, it offers a - `yesorno` -> `special HealParty`, `setval HEALMACHINE_ELMS_LAB`, - `special HealMachineAnim`. Free healing point for a bot from this section on. -- `ProfElmScript`: long check chain. In this section every branch falls through - to `writetext ElmText_LetYourMonBattleIt` (after the starter) or - `ElmDescribesMrPokemonScript`. The `ElmGiveEverstoneScript` arm needs - `EVENT_SHOWED_TOGEPI_TO_ELM`; the Master Ball arm needs `ENGINE_RISINGBADGE`; - the S.S. Ticket arm needs `EVENT_BEAT_ELITE_FOUR`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_A_POKEMON_FROM_ELM` | `constants/event_flags.asm:35` | set by `ElmDirectionsScript`; read by the ball scripts, `MomScript`, `NewBarkTownTeacherScript`, `PlayersHouseRadioScript`, `ElmsLabHealingMachine` | the section's terminal flag | -| `EVENT_GOT_CYNDAQUIL_FROM_ELM` | `constants/event_flags.asm:36` | `CyndaquilPokeBallScript` | which starter (Totodile/Chikorita variants are the next two consts) | -| `EVENT_CYNDAQUIL_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:993` | `disappear ELMSLAB_POKE_BALL1` | ball object removed | -| `EVENT_TOTODILE_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:994` | `disappear ELMSLAB_POKE_BALL2` | ball object removed | -| `EVENT_CHIKORITA_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:995` | `disappear ELMSLAB_POKE_BALL3` | ball object removed | -| `EVENT_COP_IN_ELMS_LAB` | `constants/event_flags.asm:1188` | set by `InitializeEventsScript`, cleared at `maps/MrPokemonsHouse.asm:127` | officer hidden this section | -| `EVENT_ELMS_AIDE_IN_LAB` | `constants/event_flags.asm:1187` | cleared at `maps/VioletPokecenter1F.asm:31`, set at `engine/phone/scripts/elm.asm:85` | aide visible (flag clear) this section | -| `EVENT_RIVAL_CHERRYGROVE_CITY` | `constants/event_flags.asm` | set by `InitializeEventsScript` and again by `ElmDirectionsScript` | Cherrygrove rival still hidden | -| `EVENT_GOT_EVERSTONE_FROM_ELM` | `constants/event_flags.asm:95` | `ElmGiveEverstoneScript` | **not reachable in this section** | -| `SCENE_ELMSLAB_*` | `maps/ElmsLab.asm:11-17` | `setscene` / `setmapscene` | drives every coord event on the map | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `POTION` | walk onto (4,8) or (5,8) on the way out after picking a starter | `AideScript_GivePotion` (`verbosegiveitem POTION`) | `SCENE_ELMSLAB_AIDE_GIVES_POTION` -> `SCENE_ELMSLAB_NOOP` | -| starter, L5, holding `BERRY` | `givepoke` in the chosen ball script | `CyndaquilPokeBallScript` / `TotodilePokeBallScript` / `ChikoritaPokeBallScript` | `EVENT_GOT_<SPECIES>_FROM_ELM` + `EVENT_GOT_A_POKEMON_FROM_ELM` | -| `EVERSTONE` | Elm, **after** showing him a hatched Togepi | `ElmGiveEverstoneScript` (`maps/ElmsLab.asm:341`) | `EVENT_GOT_EVERSTONE_FROM_ELM` | -| `POKE_BALL` x5 | aide, after the Mystery Egg hand-off | `AideScript_GiveYouBalls` | `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` -> `SCENE_ELMSLAB_NOOP` | - -**Trainers**: none. No `loadtrainer` / `startbattle` anywhere in -`maps/ElmsLab.asm`, `maps/NewBarkTown.asm`, `maps/PlayersHouse1F.asm` or -`maps/PlayersHouse2F.asm`. - -**Wild encounters**: none (indoor). - -**Starter data** (`data/pokemon/evos_attacks.asm`, checked against the -walkthrough's claims - all three match) - -| species | evolves | L5 known moves | later moves the FAQ names | -|---|---|---|---| -| `CHIKORITA` | `EVOLVE_LEVEL, 16, BAYLEEF`; Bayleef `EVOLVE_LEVEL, 32, MEGANIUM` | TACKLE (1), GROWL (1) | RAZOR_LEAF 8, REFLECT 12, POISONPOWDER 15, SYNTHESIS 22, BODY_SLAM 29, LIGHT_SCREEN 36, SAFEGUARD 43, SOLARBEAM 50 | -| `CYNDAQUIL` | `EVOLVE_LEVEL, 14, QUILAVA`; Quilava `EVOLVE_LEVEL, 36, TYPHLOSION` | TACKLE (1), LEER (1) | SMOKESCREEN 6, EMBER 12, QUICK_ATTACK 19, FLAME_WHEEL 27, SWIFT 36, FLAMETHROWER 46 | -| `TOTODILE` | `EVOLVE_LEVEL, 18, CROCONAW`; Croconaw `EVOLVE_LEVEL, 30, FERALIGATR` | SCRATCH (1), LEER (1) | RAGE 7, WATER_GUN 13, BITE 20, SCARY_FACE 27, SLASH 35, SCREECH 43, HYDRO_PUMP 52 | - -Note the FAQ's move lists are aspirational (they mix level-up moves with HM03 / -HM04). At the moment you receive it the starter knows exactly its two level-1 -moves; Cyndaquil does **not** have Ember yet at L5. - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Clock must be set before the world exists | `engine/menus/intro_menu.asm:494` `OakSpeech` -> `farcall InitClock` (`engine/rtc/timeset.asm:4`); default hour is 10 (`ld a, 10 ; default hour = 10 AM`) | hour then minute then a `YesNoBox` confirm each | answering "yes" to both confirmations | -| Player must be named | `engine/menus/intro_menu.asm` `NamePlayer` -> `ShowPlayerNamingChoices` / `StorePlayerName` | pick a preset or type a name | any accepted name | -| Cannot walk out of Elm's Lab before the errand speech ends | `maps/ElmsLab.asm` coord events (4,6) and (5,6) under `SCENE_ELMSLAB_CANT_LEAVE`, script `LabTryToLeaveScript` | scene id must leave 1 | `ElmDirectionsScript`'s `setscene SCENE_ELMSLAB_AIDE_GIVES_POTION` - i.e. take a starter | -| Cannot leave New Bark Town west toward Route 29 | `maps/NewBarkTown.asm` coord events (1,8) and (1,9) under `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU`, scripts `NewBarkTown_TeacherStopsYouScene1/2` | scene id must leave 0 | `ElmDirectionsScript`'s `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP` | -| Aide's Potion is scene-gated, not flag-gated | `maps/ElmsLab.asm` coord events (4,8)/(5,8), `AideScript_GivePotion` | be on scene 5 and step on x=4 or 5, y=8 | script itself does `setscene SCENE_ELMSLAB_NOOP` | -| Everstone (listed by the FAQ under New Bark Town) | `maps/ElmsLab.asm:63,338-347` `ElmCheckEverstone` / `ElmGiveEverstoneScript` | `EVENT_SHOWED_TOGEPI_TO_ELM`, which needs a hatched Togepi in the party (`special FindPartyMonThatSpeciesYourTrainerID`) | many sections later | - -No HM field-move gate applies in this section: nothing under -`engine/overworld/` (cut/surf/strength/whirlpool/waterfall/flash/fly) is -consulted between the bedroom and the starter. - -## 4. Bot checklist - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | - (main menu) | NEW GAME | menu select | fresh save | `NewGame` runs `ResetWRAM` | -| 2 | - (`InitClock`) | hour, then minute, then yes/no per field | menu | in `OakSpeech` | RTC base written; default hour 10 | -| 3 | - (`NamePlayer`) | any name | naming screen | after `OakText6` | `wPlayerName` set | -| 4 | `PLAYERS_HOUSE_2F` | spawn (3,3) | - | `SPAWN_HOME` | `MAPCALLBACK_NEWMAP` runs `InitializeEventsScript` once; `EVENT_INITIALIZED_EVENTS` set | -| 5 | `PLAYERS_HOUSE_2F` | warp 1 at (7,0) | walk onto | - | now on `PLAYERS_HOUSE_1F` warp 3 (9,0) | -| 6 | `PLAYERS_HOUSE_1F` | (scene 0 auto-fires) | wait / mash A through `MeetMomScript` | `wPlayersHouse1FSceneID` == 0 | `ENGINE_POKEGEAR`, `ENGINE_PHONE_CARD`, `PHONE_MOM`, `EVENT_PLAYERS_HOUSE_MOM_1`, scene -> 1 | -| 6a | `PLAYERS_HOUSE_1F` | DST `yesorno` pair | answer, then confirm | inside `MeetMomScript` | answering "no" to the confirm loops back to `.SetDayOfWeek` - a bot must confirm, not just answer | -| 7 | `PLAYERS_HOUSE_1F` | warp 1 (6,7) or warp 2 (7,7) | walk onto | - | `NEW_BARK_TOWN` at warp 2 (13,5) | -| 8 | `NEW_BARK_TOWN` | object `NEWBARKTOWN_RIVAL` at (3,2) | talk (optional) | rival visible while `EVENT_RIVAL_NEW_BARK_TOWN` clear | pushed one cell DOWN + a `jump_step DOWN`; **no flag set**, skippable | -| 9 | `NEW_BARK_TOWN` | warp 1 at (6,3) | walk onto | avoid (1,8)/(1,9) until step 12 | `ELMS_LAB` at warp 1 (4,11) | -| 10 | `ELMS_LAB` | (scene 0 auto-fires) | wait / mash A | `wElmsLabSceneID` == 0 | player auto-walked to (4,2); scene -> 1 (`CANT_LEAVE`) | -| 11 | `ELMS_LAB` | ball object: (6,3) Cyndaquil / (7,3) Totodile / (8,3) Chikorita | face + A, answer **yes** to `yesorno` | `EVENT_GOT_A_POKEMON_FROM_ELM` clear | starter L5 w/ BERRY in party; `EVENT_GOT_<SP>_FROM_ELM`; ball disappears; auto-walk to (5,3); `ElmDirectionsScript` runs -> `EVENT_GOT_A_POKEMON_FROM_ELM`, `PHONE_ELM`, `ELMS_LAB` scene -> 5, `NEW_BARK_TOWN` scene -> 1 | -| 12 | `ELMS_LAB` | (4,8) or (5,8) | walk onto on the way out | scene == 5 | `POTION` in bag; scene -> 2 (`NOOP`) | -| 13 | `ELMS_LAB` | bg_event (2,1) | face UP + A -> yes (optional) | `EVENT_GOT_A_POKEMON_FROM_ELM` set | party healed (`special HealParty`) | -| 14 | `ELMS_LAB` | warp 1 (4,11) or warp 2 (5,11) | walk onto | - | `NEW_BARK_TOWN` (6,3) | -| 15 | `NEW_BARK_TOWN` | west edge past (1,8)/(1,9) | walk west | `NEW_BARK_TOWN` scene == 1 | crosses the `connection west, Route29` into `ROUTE_29` - section 01 | - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Title -> NEW GAME -> Oak speech -> name pick -> naming screen -> bedroom | `src/ui/gen2/TitleState.lua`, `src/ui/gen2/MainMenu.lua`, `src/ui/gen2/OakSpeech.lua`, `src/ui/gen2/NamePick.lua`, `src/ui/gen2/NamingScreen.lua`; asserted end-to-end by `tests/drivers/gold_boot_smoke.lua` | implemented | -| `InitClock` (the "what time is it?" / minute screens) | none - `src/ui/gen2/OakSpeech.lua:3` says outright "Gender select / InitClock are Crystal-later" | **missing** (bot step 2 has no screen to drive) | -| `SPAWN_HOME` start at `PLAYERS_HOUSE_2F` (3,3) | `src/world/gen2/World.lua:228-232` (`START_MAP = "PLAYERS_HOUSE_2F"`, cites `data/maps/spawn_points.asm`) | implemented | -| Map load, warps, collision, connections | `src/world/gen2/Map.lua`, `src/world/gen2/World.lua`; `tests/drivers/gold_walk_smoke.lua`, `tests/drivers/gold_warp_scene.lua` (asserts the New Bark door lands on `ELMS_LAB`) | implemented | -| Scene scripts + coord events (the mechanism every gate in this section uses) | `src/world/gen2/World.lua:5011-5035` (`coordEvents` scan filtered by `self:scene()`, `sceneScripts` lookup), extracted by `src/import/RomExtractorGen2.lua:804-977` | implemented | -| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES`, `ToggleDecorationsVisibility`, `ToggleMaptileDecorations` | `src/script/gen2/Specials.lua:489-505`, `tests/drivers/gold_map_callbacks.lua`, `tests/gen2_map_callbacks_test.lua` | implemented | -| `MeetMomScript` (emote, follow-free walk-in, `yesorno` under a prompt, mid-scene object swap) | driven by the VM; regression driver `tests/drivers/gold_mom_scene.lua` exists specifically for this scene; the Mom object-swap timing is commented at `src/world/gen2/World.lua:558-568,730` | implemented | -| `special SetDayOfWeek` / `InitialSetDSTFlag` / `InitialClearDSTFlag` | `src/script/gen2/Specials.lua:836-860` | partial - the flags/RTC fields are written from the host clock, but no day-of-week picker UI is drawn, so the player never chooses | -| Teacher west-exit gate | `tests/drivers/gold_teacher_scene.lua` (asserts `follow` drags the player back and she stands beside them) | implemented | -| Rival push-out (`follow` + `jump_step` + `SFX_TACKLE`) | VM ops `follow`/`stopfollow` (`src/script/gen2/Opcodes.lua:117-118`), movement in `src/script/gen2/Movement.lua` | partial - opcodes present, no driver covering `NewBarkTownRivalScript` specifically | -| Starter pick: `pokepic`, `cry`, `yesorno`, `disappear`, `givepoke SP, 5, BERRY` | `src/script/gen2/Opcodes.lua:91-92,51`; `src/script/gen2/Vm.lua:386-394` (`pokepic` -> `showPicFn`), `:439-445` (`givepoke` -> `givePokeFn` with species/level/held item) | partial - all opcodes implemented, but no driver or test in `tests/drivers/gold_*.lua` walks the Elm's Lab starter choice end to end | -| `verbosegiveitem POTION` (aide) | `src/script/gen2/Vm.lua:490-510` (received text, SFX, "put in the ITEM POCKET") | implemented | -| `addcellnum PHONE_MOM` / `PHONE_ELM`, Pokegear phone card | `src/script/gen2/Vm.lua:611`, `src/core/gen2/Phone.lua:137,146`, `src/ui/gen2/Pokegear.lua` | implemented | -| `special HealParty` / `HealMachineAnim` (lab healing machine) | `src/script/gen2/Specials.lua:456` | implemented | -| `special PlayersHousePC` + `warp NONE, 0, 0` | `src/script/gen2/Specials.lua:467-487`, `src/ui/gen2/PcMenu.lua` | implemented | -| `special NameRival` (fires next section, from `CopScript`) | `src/script/gen2/Specials.lua:825-828` -> `World:nameRival` (`src/world/gen2/World.lua:4931`) | implemented | -| Event-flag bitfield semantics (set flag = object hidden) | `src/world/gen2/Events.lua` (header comment cites `CheckObjectFlag` in `map_objects_2.asm`) | implemented | - -## 6. Unresolved / verify by hand - -- **Everstone.** The FAQ lists "Everstone" under "Items found in New Bark Town". - There is no Everstone item ball, hidden item or bg_event on `MAP_NEW_BARK_TOWN`, - `MAP_ELMS_LAB`, `MAP_PLAYERS_HOUSE_1F` or `MAP_PLAYERS_HOUSE_2F`. The only New - Bark Town Everstone is `ElmGiveEverstoneScript` (`maps/ElmsLab.asm:338-347`), - which requires `EVENT_SHOWED_TOGEPI_TO_ELM` - a hatched Togepi shown to Elm, - many sections later. Treat the FAQ line as forward-looking, not as an item to - collect here. -- **"Pokegear" as an item.** The FAQ says "You get the Pokegear". In the asm it is - `setflag ENGINE_POKEGEAR` + `setflag ENGINE_PHONE_CARD`, with the item name - faked through `getstring STRING_BUFFER_4, PokegearName` ("#GEAR") into the - standard `ReceiveItemScript` text. Nothing enters the bag - a bot must not - check inventory for it. -- **"Professor Oak will ask you the time. First answer what hour it is and what - minute it is."** `InitClock` (`engine/rtc/timeset.asm`) is reached from - `OakSpeech` *before* any Oak text is printed (`farcall InitClock` is the first - instruction of `OakSpeech`), so the clock screen actually comes first and Oak's - greeting second. The FAQ's ordering is the reverse. -- **"even if you don't have the GBA on, the time will keep running"** - an RTC - hardware claim, nothing in the disassembly to cite; ignore. -- **East exit toward Route 27.** `data/maps/attributes.asm:121` gives New Bark Town - a `connection east, Route27, ROUTE_27, 0`, and unlike the west side there is - *no* coord event guarding it. Whether a bot can actually walk east before - getting Surf depends on the shoreline tiles in `maps/NewBarkTown.blk`, which I - did not decode. Verify by hand before relying on the west gate as the only exit - block. -- **Teacher gate coverage.** Only (1,8) and (1,9) carry coord events. If the map's - west connection strip is reachable at any other y, the gate has a hole. I read - the coord table but not the collision data, so this is unverified. -- **Rival battle.** The FAQ says only that he pushes you; confirmed - there is no - `loadtrainer`/`startbattle` in `NewBarkTownRivalScript`. The first rival battle - is not in this section. diff --git a/docs/gold-walkthrough/asm-walk/section-01-cherrygrove-city-and-routes-29-31.md b/docs/gold-walkthrough/asm-walk/section-01-cherrygrove-city-and-routes-29-31.md deleted file mode 100644 index 6d0275d1..00000000 --- a/docs/gold-walkthrough/asm-walk/section-01-cherrygrove-city-and-routes-29-31.md +++ /dev/null @@ -1,1081 +0,0 @@ -# Section 01 - Cherrygrove City and Routes 29-31 - -Source: `../section-01-cherrygrove-city-and-routes-29-31.txt` -Maps covered: `MAP_ROUTE_29`, `MAP_ROUTE_29_ROUTE_46_GATE`, `MAP_ROUTE_46`, -`MAP_CHERRYGROVE_CITY`, `MAP_CHERRYGROVE_POKECENTER_1F`, `MAP_CHERRYGROVE_MART`, -`MAP_GUIDE_GENTS_HOUSE`, `MAP_CHERRYGROVE_GYM_SPEECH_HOUSE`, -`MAP_CHERRYGROVE_EVOLUTION_SPEECH_HOUSE`, `MAP_ROUTE_30`, -`MAP_ROUTE_30_BERRY_HOUSE`, `MAP_MR_POKEMONS_HOUSE`, `MAP_ROUTE_31`, -`MAP_ROUTE_31_VIOLET_GATE`, plus the return leg through `MAP_NEW_BARK_TOWN`, -`MAP_ELMS_LAB` and `MAP_PLAYERS_HOUSE_1F` (those three are first visited in the -previous section; only the beats this section's text drives are transcribed -here). - -Badges / key milestones in this section: - -- No badge. The section ends at the Violet City gate. -- `ENGINE_MAP_CARD` (Pokegear MAP card, Guide Gent tour). -- `ENGINE_POKEDEX` (Prof. Oak, Mr. Pokemon's House). -- `MYSTERY_EGG` obtained, then handed to Elm (`EVENT_GAVE_MYSTERY_EGG_TO_ELM`, - the single biggest content switch in the section - it opens Poke Ball sales, - the Route 29 catch tutorial, the Route 30 trainers and the Bank of Mom). -- Rival battle 1 in Cherrygrove City (`RIVAL1`, level 5 starter). -- `ENGINE_FLYPOINT_CHERRYGROVE`, and `blackoutmod CHERRYGROVE_CITY`. - -Coordinate convention: every `warp_event` / `coord_event` / `bg_event` / -`object_event` row below is copied verbatim from the map asm. Those x/y are map -cell coordinates starting at 0 (`macros/scripts/maps.asm` adds the +4 border -offset when it assembles the row, so the asm number is the one a bot wants). -Map width/height in `constants/map_constants.asm` is in **blocks**, i.e. half -the cell count in each axis - `ROUTE_29` at `30, 9` is 60x18 cells, which is why -its object x values run up to 53. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ELMS_LAB` | `maps/ElmsLab.asm` | (already inside, previous section) | warps 1/2 at (4,11)/(5,11) -> `NEW_BARK_TOWN` warp 1 | Aide's `SCENE_ELMSLAB_AIDE_GIVES_POTION` coord event hands over a POTION on the way out | -| 2 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | lab door | west edge connection | "Head west onto Route 29" | -| 3 | `MAP_ROUTE_29` | `maps/Route29.asm` | east connection from New Bark | west connection to Cherrygrove | grinding, BERRY tree, POTION ball | -| 4 | `MAP_ROUTE_29_ROUTE_46_GATE` | `maps/Route29Route46Gate.asm` | Route 29 warp 1 at (27,1) | gate warps 1/2 -> Route 46 | optional detour "to the north is Route 46" | -| 5 | `MAP_ROUTE_46` | `maps/Route46.asm` | gate warps 1/2 at (7,33)/(8,33) | back through the gate | optional Rattata / Geodude hunting | -| 6 | `MAP_CHERRYGROVE_CITY` | `maps/CherrygroveCity.asm` | east connection from Route 29 | north connection to Route 30 | Guide Gent tour -> MAP CARD; Pokemon Center heal | -| 7 | `MAP_CHERRYGROVE_POKECENTER_1F` | `maps/CherrygrovePokecenter1F.asm` | city warp 2 at (29,3) | pokecenter warps 1/2 | heal | -| 8 | `MAP_ROUTE_30` | `maps/Route30.asm` | south connection from Cherrygrove | warp 2 at (17,5) | north to Mr. Pokemon; berry house; hidden POTION | -| 9 | `MAP_ROUTE_30_BERRY_HOUSE` | `maps/Route30BerryHouse.asm` | Route 30 warp 1 at (7,39) | house warps 1/2 | free BERRY from the Pokefan | -| 10 | `MAP_MR_POKEMONS_HOUSE` | `maps/MrPokemonsHouse.asm` | Route 30 warp 2 at (17,5) | house warps 1/2 | MYSTERY EGG + POKEDEX + Elm's panic call | -| 11 | `MAP_CHERRYGROVE_CITY` (2nd) | `maps/CherrygroveCity.asm` | south connection from Route 30 | east side, past (33,6)/(33,7) | rival battle trip-wire on the way out | -| 12 | `MAP_ROUTE_29` (2nd) | `maps/Route29.asm` | west connection | east connection | walk back to New Bark | -| 13 | `MAP_ELMS_LAB` (2nd) | `maps/ElmsLab.asm` | New Bark warp 1 at (6,3) | lab warps 1/2 | cop names the rival; give Elm the egg; aide gives 5 POKE BALLs | -| 14 | `MAP_PLAYERS_HOUSE_1F` | `maps/PlayersHouse1F.asm` | New Bark warp 2 at (13,5) | house warps 1/2 | Mom starts saving money (`special BankOfMom`) | -| 15 | `MAP_ROUTE_29` (3rd) | `maps/Route29.asm` | east connection | west connection | catch tutorial fires at (53,8)/(53,9) | -| 16 | `MAP_ROUTE_46` (2nd, optional) | `maps/Route46.asm` | via the gate | via the gate | catch a Rattata | -| 17 | `MAP_CHERRYGROVE_CITY` (3rd) | `maps/CherrygroveCity.asm` | east connection | north connection | heal, buy POKE BALLs (mart list now `MART_CHERRYGROVE_DEX`) | -| 18 | `MAP_ROUTE_30` (2nd) | `maps/Route30.asm` | south connection | north connection | Joey, Mikey, Don on the **west** fork | -| 19 | `MAP_ROUTE_31` | `maps/Route31.asm` | south connection from Route 30 | warps 1/2 at (4,6)/(4,7) | Wade, ANTIDOTE, POKE BALL, BITTER BERRY, Dark Cave mouth | -| 20 | `MAP_ROUTE_31_VIOLET_GATE` | `maps/Route31VioletGate.asm` | Route 31 warps 1/2 | gate warps 1/2 -> `VIOLET_CITY` warps 8/9 | "through the house-ish entry, and into Violet City" | - -Spill-over: the text's last paragraph enters `MAP_VIOLET_CITY` -(`maps/VioletCity.asm`, header `data/maps/maps.asm`, connection -`connection east, Route31, ROUTE_31, 9` in `data/maps/attributes.asm`) and points -at Sprout Tower. Violet City belongs to the next section and is not transcribed -here. `MAP_DARK_CAVE_VIOLET_ENTRANCE` is likewise only touched at its mouth (see -the Route 31 block and section 3). - ---- - -## 2. Maps - -### MAP_ROUTE_29 - -- Script: `maps/Route29.asm` -- Blocks: `maps/Route29.blk` -- Header: `data/maps/maps.asm` -> - `map Route29, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_29, MUSIC_ROUTE_29, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm` -> `map_const ROUTE_29, 30, 9` (60x18 cells) -- Connections (`data/maps/attributes.asm`, `map_attributes Route29, ROUTE_29, $05`): - north `Route46` (offset 10), west `CherrygroveCity` (0), east `NewBarkTown` (0) -- Scenes (implicit ordinals from `def_scene_scripts`): `SCENE_ROUTE29_NOOP` = 0, - `SCENE_ROUTE29_CATCH_TUTORIAL` = 1 - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 27 | 1 | `ROUTE_29_ROUTE_46_GATE` | 3 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE29_CATCH_TUTORIAL` | 53 | 8 | `Route29Tutorial1` (`4a:4d98`) | DUDE catch demo, north lane | -| `SCENE_ROUTE29_CATCH_TUTORIAL` | 53 | 9 | `Route29Tutorial2` (`4a:4dce`) | DUDE catch demo, south lane | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 51 | 7 | `BGEVENT_READ` | `Route29Sign1` | -| 3 | 5 | `BGEVENT_READ` | `Route29Sign2` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE29_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 50 | 12 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `CatchingTutorialDudeScript` (`4a:4e1c`) | -1 | -| `ROUTE29_YOUNGSTER` | `SPRITE_YOUNGSTER` | 27 | 16 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius y 1) | `OBJECTTYPE_SCRIPT` | `Route29YoungsterScript` | -1 | -| `ROUTE29_TEACHER1` | `SPRITE_TEACHER` | 15 | 11 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius x 1) | `OBJECTTYPE_SCRIPT` | `Route29TeacherScript` | -1 | -| `ROUTE29_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 12 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route29FruitTree` (`4a:4eb4`) | -1 | -| `ROUTE29_FISHER` | `SPRITE_FISHER` | 25 | 3 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `Route29FisherScript` | -1 | -| `ROUTE29_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 13 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route29CooltrainerMScript` | -1 | -| `ROUTE29_TUSCANY` | `SPRITE_TEACHER` | 29 | 12 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `TuscanyScript` (`4a:4e74`) | `EVENT_ROUTE_29_TUSCANY_OF_TUESDAY` | -| `ROUTE29_POKE_BALL` | `SPRITE_POKE_BALL` | 48 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route29Potion` (`4a:4eb6`) | `EVENT_ROUTE_29_POTION` | - -**Scripts of interest** - -- `Route29TuscanyCallback` (`MAPCALLBACK_OBJECTS`): `checkflag ENGINE_ZEPHYRBADGE`; - without the badge it `disappear ROUTE29_TUSCANY` unconditionally. With the badge - it `readvar VAR_WEEKDAY` and only `appear`s her on `TUESDAY`. So the walkthrough's - "Pink Bow" is **not** obtainable in this section - it needs ZEPHYRBADGE first. -- `Route29Tutorial1` / `Route29Tutorial2`: `turnobject`, `showemote EMOTE_SHOCK`, - `applymovement DudeMovementData1a/2a`, `setevent EVENT_DUDE_TALKED_TO_YOU`, - `yesorno`. On yes: `follow ROUTE29_COOLTRAINER_M1, PLAYER`, - `applymovement DudeMovementData1b/2b`, `stopfollow`, `loadwildmon RATTATA, 5`, - `catchtutorial BATTLETYPE_TUTORIAL`, then `setscene SCENE_ROUTE29_NOOP` and - `setevent EVENT_LEARNED_TO_CATCH_POKEMON`. On no (`Script_RefusedTutorial1/2`) - the scene is still cleared, so a bot that declines does not get stuck. -- `CatchingTutorialDudeScript`: repeatable demo. Requires `VAR_BOXSPACE != 0`, - `EVENT_LEARNED_TO_CATCH_POKEMON` clear and `EVENT_GAVE_MYSTERY_EGG_TO_ELM` set. -- `Route29Potion`: `itemball POTION`, one-shot on `EVENT_ROUTE_29_POTION`. -- `Route29FruitTree`: `fruittree FRUITTREE_ROUTE_29` -> `BERRY` - (`data/items/fruit_trees.asm`, first row). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `SCENE_ROUTE29_CATCH_TUTORIAL` | implicit ordinal 1, `maps/Route29.asm` | set by `ElmAfterTheftScript` (`setmapscene ROUTE_29, ...`) | tutorial trip-wire armed only after the egg is delivered | -| `EVENT_DUDE_TALKED_TO_YOU` | `constants/event_flags.asm:74` | `Route29Tutorial1/2` | demo started | -| `EVENT_LEARNED_TO_CATCH_POKEMON` | `constants/event_flags.asm:75` | `Route29Tutorial1/2`, `CatchingTutorialDudeScript` | demo finished | -| `EVENT_ROUTE_29_POTION` | `constants/event_flags.asm:1102` | `itemball` | ball taken (object hidden when set) | -| `EVENT_ROUTE_29_TUSCANY_OF_TUESDAY` | `constants/event_flags.asm:1275` | `Route29TuscanyCallback` | Tuscany visible when **clear** | -| `EVENT_MET_TUSCANY_OF_TUESDAY` / `EVENT_GOT_PINK_BOW_FROM_TUSCANY` | `constants/event_flags.asm:109,110` | `TuscanyScript` | one-shot PINK BOW | -| `ENGINE_ZEPHYRBADGE` | `constants/engine_flags.asm:38` | `Route29TuscanyCallback` | gate on Tuscany appearing at all | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `POTION` | item ball at (48,2) | `Route29Potion` | `EVENT_ROUTE_29_POTION` | -| `BERRY` | fruit tree at (12,2) | `Route29FruitTree` / `FRUITTREE_ROUTE_29` | daily, not an event flag | -| `PINK_BOW` | Tuscany at (29,12) | `TuscanyScript`, `verbosegiveitem PINK_BOW` | `EVENT_GOT_PINK_BOW_FROM_TUSCANY`; needs ZEPHYRBADGE **and** `VAR_WEEKDAY == TUESDAY` | - -**Trainers** - -None (the DUDE is `OBJECTTYPE_SCRIPT`; his battle is `catchtutorial`, not a trainer). - -**Wild encounters** - -`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_29`, rates -`10 percent, 10 percent, 10 percent`: - -- morn / day (identical): 2 PIDGEY, 3 SENTRET, 3 PIDGEY, 2 SENTRET, 4 RATTATA, 4 PIDGEY, 4 PIDGEY -- nite: 2 HOOTHOOT, 3 HOOTHOOT, 3 HOOTHOOT, 2 RATTATA, 4 RATTATA, 4 HOOTHOOT, 4 HOOTHOOT - -No `def_water_wildmons ROUTE_29`. Fishing group `FISHGROUP_SHORE` -(`data/wild/fish.asm` `.Shore_Old/Good/Super`: MAGIKARP/KRABBY). - -### MAP_ROUTE_29_ROUTE_46_GATE - -- Script: `maps/Route29Route46Gate.asm` -- Blocks: shared `maps/NorthSouthGate.blk` (`data/maps/blocks.asm:213`) -- Header: `map Route29Route46Gate, TILESET_GATE, GATE, LANDMARK_ROUTE_29, MUSIC_ROUTE_29, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const ROUTE_29_ROUTE_46_GATE, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_46` | 1 | -| 2 | 5 | 0 | `ROUTE_46` | 2 | -| 3 | 4 | 7 | `ROUTE_29` | 1 | -| 4 | 5 | 7 | `ROUTE_29` | 1 | - -**Coord events** / **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE29ROUTE46GATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route29Route46GateOfficerScript` | -1 | -| `ROUTE29ROUTE46GATE_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` | `OBJECTTYPE_SCRIPT` | `Route29Route46GateYoungsterScript` | -1 | - -Both are `jumptextfaceplayer` only - nothing blocks. - -### MAP_ROUTE_46 - -- Script: `maps/Route46.asm` -- Blocks: `maps/Route46.blk` -- Header: `map Route46, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_46, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `map_const ROUTE_46, 10, 18` (20x36 cells) -- Connections: south `Route29` (offset -10), east `Route45` (-36) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 33 | `ROUTE_29_ROUTE_46_GATE` | 1 | -| 2 | 8 | 33 | `ROUTE_29_ROUTE_46_GATE` | 2 | -| 3 | 14 | 5 | `DARK_CAVE_VIOLET_ENTRANCE` | 3 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 27 | `BGEVENT_READ` | `Route46Sign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE46_POKEFAN_M` | `SPRITE_POKEFAN_M` | 12 | 18 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerHikerBailey` | -1 | -| `ROUTE46_YOUNGSTER` | `SPRITE_YOUNGSTER` | 3 | 13 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerCamperTed` | -1 | -| `ROUTE46_LASS` | `SPRITE_LASS` | 1 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerPicnickerErin1` | -1 | -| `ROUTE46_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 7 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route46FruitTree1` | -1 | -| `ROUTE46_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 8 | 6 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route46FruitTree2` | -1 | -| `ROUTE46_POKE_BALL` | `SPRITE_POKE_BALL` | 0 | 12 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route46DireHit` | `EVENT_ROUTE_46_DIRE_HIT` | - -**Trainers** (all three are far above the walkthrough's level range at this point, -and all three stand at y <= 18, i.e. past the ledges at the north end of the map) - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `BAILEY` | `HIKER` (`$2c`) | 11th in class | `HikerGroup` "BAILEY": 5x L13 GEODUDE | `TrainerHikerBailey` | no | -| `TED` | `CAMPER` (`$36`) | 11th in class | `CamperGroup` "TED": L17 MANKEY | `TrainerCamperTed` | no | -| `ERIN1` | `PICNICKER` (`$35`) | 10th in class | `PicnickerGroup` "ERIN": L16 PONYTA, L16 PONYTA | `TrainerPicnickerErin1` | phone `PHONE_PICNICKER_ERIN`, rematch gated on `EVENT_BEAT_ELITE_FOUR` / `EVENT_RESTORED_POWER_TO_KANTO` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `DIRE_HIT` | item ball at (0,12) | `Route46DireHit` | `EVENT_ROUTE_46_DIRE_HIT` | -| `BERRY` | tree at (7,5) | `FRUITTREE_ROUTE_46_1` | daily | -| `PRZCUREBERRY` | tree at (8,6) | `FRUITTREE_ROUTE_46_2` | daily | - -**Wild encounters** (`def_grass_wildmons ROUTE_46`, 10/10/10 percent) - -- morn / day: 3 GEODUDE, 2 SPEAROW, 2 RATTATA, 2 GEODUDE, 3 SPEAROW, 3 JIGGLYPUFF, 5 JIGGLYPUFF -- nite: 3 GEODUDE, 3 RATTATA, 2 RATTATA, 2 GEODUDE, 4 GEODUDE, 3 JIGGLYPUFF, 5 JIGGLYPUFF - -The walkthrough's "better chance of finding a Rattata" is true only at night -(2 of 7 slots vs 1 of 7 by day); by day the slot is a single level-2 RATTATA. - -### MAP_CHERRYGROVE_CITY - -- Script: `maps/CherrygroveCity.asm` -- Blocks: `maps/CherrygroveCity.blk` -- Header: `map CherrygroveCity, TILESET_JOHTO, TOWN, LANDMARK_CHERRYGROVE_CITY, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `map_const CHERRYGROVE_CITY, 20, 9` (40x18 cells) -- Connections (`map_attributes CherrygroveCity, CHERRYGROVE_CITY, $35`): - north `Route30` (offset 5), east `Route29` (0) -- Spawn: `data/maps/spawn_points.asm` -> `spawn CHERRYGROVE_CITY, 29, 4`; - fly point `data/maps/flypoints.asm` -> `db LANDMARK_CHERRYGROVE_CITY, SPAWN_CHERRYGROVE` -- Scenes: `SCENE_CHERRYGROVECITY_NOOP` = 0, `SCENE_CHERRYGROVECITY_MEET_RIVAL` = 1 - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 3 | `CHERRYGROVE_MART` | 2 | -| 2 | 29 | 3 | `CHERRYGROVE_POKECENTER_1F` | 1 | -| 3 | 17 | 7 | `CHERRYGROVE_GYM_SPEECH_HOUSE` | 1 | -| 4 | 25 | 9 | `GUIDE_GENTS_HOUSE` | 1 | -| 5 | 31 | 11 | `CHERRYGROVE_EVOLUTION_SPEECH_HOUSE` | 1 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_CHERRYGROVECITY_MEET_RIVAL` | 33 | 6 | `CherrygroveRivalSceneNorth` (`48:4479`) | rival walks in and battles | -| `SCENE_CHERRYGROVECITY_MEET_RIVAL` | 33 | 7 | `CherrygroveRivalSceneSouth` (`48:4475`) | same, after `moveobject CHERRYGROVECITY_RIVAL, 39, 7` | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 30 | 8 | `BGEVENT_READ` | `CherrygroveCitySign` | -| 23 | 9 | `BGEVENT_READ` | `GuideGentsHouseSign` | -| 24 | 3 | `BGEVENT_READ` | `CherrygroveCityMartSign` | -| 30 | 3 | `BGEVENT_READ` | `CherrygroveCityPokecenterSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CHERRYGROVECITY_GRAMPS` | `SPRITE_GRAMPS` | 32 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CherrygroveCityGuideGent` (`48:43de`) | `EVENT_GUIDE_GENT_IN_HIS_HOUSE` | -| `CHERRYGROVECITY_RIVAL` | `SPRITE_RIVAL` | 39 | 6 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_CHERRYGROVE_CITY` | -| `CHERRYGROVECITY_TEACHER` | `SPRITE_TEACHER` | 27 | 12 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `CherrygroveTeacherScript` | -1 | -| `CHERRYGROVECITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 23 | 7 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `CherrygroveYoungsterScript` | -1 | -| `CHERRYGROVECITY_FISHER` | `SPRITE_FISHER` | 7 | 12 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MysticWaterGuy` | -1 | - -Object visibility rule (verified in `engine/overworld/map_objects_2.asm` -`CheckObjectFlag` and `engine/overworld/scripting.asm` -`ApplyEventActionAppearDisappear`): an object is **masked when its event flag is -SET**. `appear` clears the flag, `disappear` sets it. - -**Scripts of interest** - -- `CherrygroveCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): - `setflag ENGINE_FLYPOINT_CHERRYGROVE`. Entering the map once is enough. -- `CherrygroveCityGuideGent` (`48:43de`): `yesorno`. On yes it - `playmusic MUSIC_SHOW_ME_AROUND`, `follow CHERRYGROVECITY_GRAMPS, PLAYER`, then - five `applymovement GuideGentMovement1..5` legs (Pokecenter -> Mart -> Route 30 -> - sea -> his house), `getstring STRING_BUFFER_4, "MAP CARD"`, - `jumpstd ReceiveItemScript`, **`setflag ENGINE_MAP_CARD`**, `stopfollow`, - `applymovement GuideGentMovement6`, `disappear CHERRYGROVECITY_GRAMPS` - (-> sets `EVENT_GUIDE_GENT_IN_HIS_HOUSE`) and - `clearevent EVENT_GUIDE_GENT_VISIBLE_IN_CHERRYGROVE` (-> makes the indoor gramps - in `GuideGentsHouse.asm` appear). Declining leaves everything unchanged and he - can be re-asked. -- `CherrygroveRivalSceneNorth` / `...South` (`48:4479` / `48:4475`): - `special FadeOutMusic`, `appear CHERRYGROVECITY_RIVAL`, - `applymovement CherrygroveCity_RivalWalksToYou` (5x `step LEFT`), - `playmusic MUSIC_RIVAL_ENCOUNTER`, then the starter fork - - `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `RIVAL1_1_CHIKORITA`, - `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `RIVAL1_1_CYNDAQUIL`, - otherwise `RIVAL1_1_TOTODILE`. Always - `loadvar VAR_BATTLETYPE, BATTLETYPE_CANLOSE`, so losing does not black you out. - After either outcome: `applymovement PLAYER, CherrygroveCity_RivalPushesYouOutOfTheWay` - (`big_step DOWN`), `applymovement CHERRYGROVECITY_RIVAL, CherrygroveCity_RivalExitsStageLeft`, - `disappear CHERRYGROVECITY_RIVAL`, `setscene SCENE_CHERRYGROVECITY_NOOP`, - `special HealParty`. -- `MysticWaterGuy` at (7,12): free `MYSTIC_WATER` via `verbosegiveitem`, one-shot on - `EVENT_GOT_MYSTIC_WATER_IN_CHERRYGROVE`. The walkthrough never mentions it. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_MAP_CARD` | `constants/engine_flags.asm:5` | `CherrygroveCityGuideGent` | Pokegear MAP card; `CherrygroveTeacherScript` reads it | -| `ENGINE_FLYPOINT_CHERRYGROVE` | `constants/engine_flags.asm:80` | map callback | Fly destination unlocked on first entry | -| `ENGINE_POKEDEX` | `constants/engine_flags.asm:20` | set in `MrPokemonsHouse_OakScript`, read by `CherrygroveYoungsterScript` | dex owned | -| `EVENT_GUIDE_GENT_IN_HIS_HOUSE` | `constants/event_flags.asm:1184` | `disappear` at end of tour | outdoor gramps hidden when set | -| `EVENT_GUIDE_GENT_VISIBLE_IN_CHERRYGROVE` | `constants/event_flags.asm:1185` | set at new game by `InitializeEventsScript`, cleared at end of tour | indoor gramps hidden while set | -| `EVENT_RIVAL_CHERRYGROVE_CITY` | `constants/event_flags.asm:1120` | set by `InitializeEventsScript` and `ElmDirectionsScript`; cleared by `appear` in the scene | rival object hidden until the scene runs | -| `SCENE_CHERRYGROVECITY_MEET_RIVAL` | ordinal 1, `maps/CherrygroveCity.asm` | armed by `MrPokemonsHouse_OakScript` (`setmapscene CHERRYGROVE_CITY, ...`) | rival trip-wire only after the Pokedex | -| `EVENT_GOT_MYSTIC_WATER_IN_CHERRYGROVE` | `constants/event_flags.asm:86` | `MysticWaterGuy` | one-shot | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| MAP CARD (`ENGINE_MAP_CARD`, not a bag item) | finish the Guide Gent tour | `CherrygroveCityGuideGent` | engine flag, not an event | -| `MYSTIC_WATER` | talk to the fisher at (7,12) | `MysticWaterGuy` | `EVENT_GOT_MYSTIC_WATER_IN_CHERRYGROVE` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL1_1_CHIKORITA` / `RIVAL1_1_CYNDAQUIL` / `RIVAL1_1_TOTODILE` | `RIVAL1` (class 9) | 1 / 2 / 3 | `Rival1Group` entries 1-3: a single level 5 starter | `CherrygroveRivalSceneNorth` / `...South` | one-off | - -Prize money: `data/trainers/attributes.asm` "Rival1" `db 15 ; base reward`; -`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) is -`base * level of the last mon`, and `engine/battle/core.asm:2340-2361` pays that -amount **four times** (`ld c, 4` loop across Mom's account and the wallet). -15 * 5 * 4 = 300, which is exactly the walkthrough's "You get: 300G". - -**Wild encounters** - -No `def_grass_wildmons CHERRYGROVE_CITY` and no water table. Surf/fish only via -`FISHGROUP_SHORE`. - -### MAP_CHERRYGROVE_POKECENTER_1F - -- Script: `maps/CherrygrovePokecenter1F.asm` -- Blocks: shared `maps/Pokecenter1F.blk` (`data/maps/blocks.asm:363`) -- Header: `map CherrygrovePokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_CHERRYGROVE_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const CHERRYGROVE_POKECENTER_1F, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `CHERRYGROVE_CITY` | 2 | -| 2 | 4 | 7 | `CHERRYGROVE_CITY` | 2 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CHERRYGROVEPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CherrygrovePokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 | -| `CHERRYGROVEPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 2 | 3 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `CherrygrovePokecenter1FFisherScript` | -1 | -| `CHERRYGROVEPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 8 | 6 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `CherrygrovePokecenter1FGentlemanScript` | -1 | -| `CHERRYGROVEPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 1 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `CherrygrovePokecenter1FTeacherScript` | -1 | - -No coord or bg events. Heal = talk to the nurse at (3,1) from (3,2). - -### MAP_CHERRYGROVE_MART - -- Script: `maps/CherrygroveMart.asm` -- Blocks: shared `maps/Mart.blk` (`data/maps/blocks.asm:334`) -- Header: `map CherrygroveMart, TILESET_MART, INDOOR, LANDMARK_CHERRYGROVE_CITY, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const CHERRYGROVE_MART, 6, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `CHERRYGROVE_CITY` | 1 | -| 2 | 3 | 7 | `CHERRYGROVE_CITY` | 1 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CHERRYGROVEMART_CLERK` | `SPRITE_CLERK` | 1 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `CherrygroveMartClerkScript` | -1 | -| `CHERRYGROVEMART_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `CherrygroveMartCooltrainerMScript` | -1 | -| `CHERRYGROVEMART_YOUNGSTER` | `SPRITE_YOUNGSTER` | 2 | 5 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CherrygroveMartYoungsterScript` | -1 | - -**Scripts of interest** - -`CherrygroveMartClerkScript`: `checkevent EVENT_GAVE_MYSTERY_EGG_TO_ELM`. -False -> `pokemart MARTTYPE_STANDARD, MART_CHERRYGROVE`; true -> -`pokemart MARTTYPE_STANDARD, MART_CHERRYGROVE_DEX`. - -`data/items/marts.asm`: -- `MartCherrygrove`: POTION, ANTIDOTE, PARLYZ_HEAL, AWAKENING -- `MartCherrygroveDex`: POKE_BALL, POTION, ANTIDOTE, PARLYZ_HEAL, AWAKENING - -This is the asm behind the walkthrough's "Don't bother trying to get Pokeballs -because they aren't being sold just yet." - -### MAP_GUIDE_GENTS_HOUSE - -- Script: `maps/GuideGentsHouse.asm` -- Blocks: shared `maps/House1.blk` (`data/maps/blocks.asm:198-201`) -- Header: `map GuideGentsHouse, TILESET_HOUSE, INDOOR, LANDMARK_CHERRYGROVE_CITY, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const GUIDE_GENTS_HOUSE, 4, 4` - -**Warps**: 1 (2,7) and 2 (3,7), both -> `CHERRYGROVE_CITY` warp 4. - -**BG events**: (0,1) and (1,1) `BGEVENT_READ` `GuideGentsHouseBookshelf`. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GUIDEGENTSHOUSE_GRAMPS` | `SPRITE_GRAMPS` | 2 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `GuideGentsHouseGuideGent` | `EVENT_GUIDE_GENT_VISIBLE_IN_CHERRYGROVE` | - -### MAP_CHERRYGROVE_GYM_SPEECH_HOUSE - -- Script: `maps/CherrygroveGymSpeechHouse.asm`; blocks shared `maps/House1.blk` -- Header: `map CherrygroveGymSpeechHouse, TILESET_HOUSE, INDOOR, LANDMARK_CHERRYGROVE_CITY, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const CHERRYGROVE_GYM_SPEECH_HOUSE, 4, 4` -- Warps: 1 (2,7), 2 (3,7) -> `CHERRYGROVE_CITY` warp 3 -- BG: (0,1), (1,1) `PictureBookshelfScript` -- Objects: `CHERRYGROVEGYMSPEECHHOUSE_POKEFAN_M` at (2,3), `..._BUG_CATCHER` at (5,5); both plain `jumptextfaceplayer`, no flags - -### MAP_CHERRYGROVE_EVOLUTION_SPEECH_HOUSE - -- Script: `maps/CherrygroveEvolutionSpeechHouse.asm`; blocks shared `maps/House1.blk` -- Header: `map CherrygroveEvolutionSpeechHouse, TILESET_HOUSE, INDOOR, LANDMARK_CHERRYGROVE_CITY, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const CHERRYGROVE_EVOLUTION_SPEECH_HOUSE, 4, 4` -- Warps: 1 (2,7), 2 (3,7) -> `CHERRYGROVE_CITY` warp 5 -- BG: (0,1), (1,1) `MagazineBookshelfScript` -- Objects: `..._LASS` at (3,5), `..._YOUNGSTER` at (2,5); both `opentext/writetext` - only (note: no `faceplayer`, so they must be talked to from the side they face) - -### MAP_ROUTE_30 - -- Script: `maps/Route30.asm` -- Blocks: `maps/Route30.blk` -- Header: `map Route30, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_30, MUSIC_ROUTE_30, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `map_const ROUTE_30, 10, 27` (20x54 cells; y grows southward, so - Cherrygrove is at high y and Route 31 at y=0) -- Connections (`map_attributes Route30, ROUTE_30, $05`): north `Route31` (-10), - south `CherrygroveCity` (-5) -- Scenes: none (`def_scene_scripts` is empty). No callbacks. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 39 | `ROUTE_30_BERRY_HOUSE` | 1 | -| 2 | 17 | 5 | `MR_POKEMONS_HOUSE` | 1 | - -**Coord events**: none. The "you can't pass that area just yet" block is done -with objects, not a coord event (see gates). - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 43 | `BGEVENT_READ` | `Route30Sign` | -| 13 | 29 | `BGEVENT_READ` | `MrPokemonsHouseDirectionsSign` | -| 15 | 5 | `BGEVENT_READ` | `MrPokemonsHouseSign` | -| 3 | 21 | `BGEVENT_READ` | `Route30TrainerTips` | -| 14 | 9 | `BGEVENT_ITEM` | `Route30HiddenPotion` (`4a:55a2`) = `hiddenitem POTION, EVENT_ROUTE_30_HIDDEN_POTION` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE30_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 5 | 26 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `YoungsterJoey_ImportantBattleScript` | `EVENT_ROUTE_30_BATTLE` | -| `ROUTE30_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 6 | 29 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerYoungsterJoey` (`4a:54c5`) | `EVENT_ROUTE_30_YOUNGSTER_JOEY` | -| `ROUTE30_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 5 | 23 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerYoungsterMikey` (`4a:5553`) | -1 | -| `ROUTE30_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 4 | 7 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerBugCatcherDon` (`4a:5567`) | -1 | -| `ROUTE30_YOUNGSTER4` | `SPRITE_YOUNGSTER` | 7 | 31 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route30YoungsterScript` | -1 | -| `ROUTE30_MONSTER1` | `SPRITE_MONSTER` | 5 | 24 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_ROUTE_30_BATTLE` | -| `ROUTE30_MONSTER2` | `SPRITE_MONSTER` | 5 | 25 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_ROUTE_30_BATTLE` | -| `ROUTE30_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 5 | 39 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route30FruitTree1` (`4a:559e`) | -1 | -| `ROUTE30_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 11 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route30FruitTree2` (`4a:55a0`) | -1 | -| `ROUTE30_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 2 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route30CooltrainerFScript` | -1 | - -**Scripts of interest** - -- `YoungsterJoey_ImportantBattleScript` (the three objects flagged - `EVENT_ROUTE_30_BATTLE` at (5,24), (5,25), (5,26)): a cutscene, not a battle - - `playmusic MUSIC_JOHTO_TRAINER_BATTLE`, two `applymovement` lunges, "Leave me - alone!", `special RestartMapMusic`, `end`. While the flag is clear these three - objects occupy the west lane at x=5, which is what stops the player walking north - on the left fork. -- `TrainerYoungsterJoey`: `trainer YOUNGSTER, JOEY1, EVENT_BEAT_YOUNGSTER_JOEY, ...` - then the phone chain - `checkcellnum PHONE_YOUNGSTER_JOEY`, - `EVENT_JOEY_ASKED_FOR_PHONE_NUMBER`, `askforphonenumber PHONE_YOUNGSTER_JOEY`. - Rematches pick `JOEY2` (`ENGINE_FLYPOINT_GOLDENROD`) or `JOEY3` - (`ENGINE_FLYPOINT_OLIVINE`). -- `TrainerYoungsterMikey`, `TrainerBugCatcherDon`: plain `trainer` + `endifjustbattled`. -- `Route30YoungsterScript` at (7,31): text switches on `EVENT_GAVE_MYSTERY_EGG_TO_ELM`. -- `Route30HiddenPotion`: `hiddenitem POTION, EVENT_ROUTE_30_HIDDEN_POTION` - a - `BGEVENT_ITEM` bg event, so it is picked up by pressing A on (14,9), not by - stepping on it. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_30_BATTLE` | `constants/event_flags.asm:1206` | **set** by `ElmAfterTheftScript` | when set, the three blockers vanish and the west fork opens | -| `EVENT_ROUTE_30_YOUNGSTER_JOEY` | `constants/event_flags.asm:1207` | set at new game by `InitializeEventsScript` (`engine/events/std_scripts.asm:476`), **cleared** by `ElmAfterTheftScript` | Joey the trainer only exists after the egg hand-in | -| `EVENT_BEAT_YOUNGSTER_JOEY` / `..._MIKEY` / `EVENT_BEAT_BUG_CATCHER_DON` | `constants/event_flags.asm:963,964,839` | `trainer` macro | battle already won | -| `EVENT_ROUTE_30_HIDDEN_POTION` | `constants/event_flags.asm:174` | `hiddenitem` | hidden POTION taken | -| `EVENT_GOT_BERRY_FROM_ROUTE_30_HOUSE` | `constants/event_flags.asm:48` | `Route30BerryHousePokefanMScript` | free BERRY taken | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `POTION` | hidden, press A facing (14,9) | `Route30HiddenPotion` bg event | `EVENT_ROUTE_30_HIDDEN_POTION` | -| `BERRY` | tree at (5,39) | `FRUITTREE_ROUTE_30_1` | daily | -| `PSNCUREBERRY` | tree at (11,5) | `FRUITTREE_ROUTE_30_2` | daily | -| `BERRY` | Pokefan inside the berry house | `Route30BerryHousePokefanMScript` (`62:455c`) | `EVENT_GOT_BERRY_FROM_ROUTE_30_HOUSE` | -| `MYSTERY_EGG`, POKEDEX | Mr. Pokemon's House (below) | | | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `JOEY1` | `YOUNGSTER` (`$10`) | 1 | `YoungsterGroup` (1) "JOEY": L4 RATTATA | `TrainerYoungsterJoey` | yes, `PHONE_YOUNGSTER_JOEY`; `JOEY2` = L15 RATTATA, `JOEY3` = L21 RATICATE (TAIL_WHIP, QUICK_ATTACK, HYPER_FANG, SCARY_FACE) | -| `MIKEY` | `YOUNGSTER` | 2 | `YoungsterGroup` (2) "MIKEY": **L2 PIDGEY, L4 RATTATA** | `TrainerYoungsterMikey` | no | -| `DON` | `BUG_CATCHER` (`$18`) | 1 | `BugCatcherGroup` (1) "DON": L3 CATERPIE, L3 CATERPIE | `TrainerBugCatcherDon` | no | - -Base rewards: YOUNGSTER `db 4`, BUG_CATCHER `db 4` -(`data/trainers/attributes.asm`), paid 4x -> Joey 4*4*4 = 64G, Mikey 4*4*4 = 64G, -Don 4*3*4 = 48G. All three match the walkthrough. - -**Wild encounters** (`def_grass_wildmons ROUTE_30`, 10/10/10 percent; the file is -`IF DEF(_GOLD) ... ELIF DEF(_SILVER)`) - -Gold: -- morn: 2 PIDGEY, 3 CATERPIE, 4 CATERPIE, 4 METAPOD, 4 PIDGEY, 4 PIDGEY, 4 PIDGEY -- day: 2 PIDGEY, 3 CATERPIE, 4 PIDGEY, 4 METAPOD, 4 CATERPIE, 5 METAPOD, 5 METAPOD -- nite: 3 SPINARAK, 3 RATTATA, 4 HOOTHOOT, 4 RATTATA, 4 HOOTHOOT, 4 HOOTHOOT, 4 HOOTHOOT - -Silver (for contrast, since the walkthrough lists both): LEDYBA/WEEDLE/KAKUNA in -the morn/day rows, HOOTHOOT/RATTATA at nite. - -Water (`data/wild/johto_water.asm`, `def_water_wildmons ROUTE_30`, 2 percent): -20 POLIWAG, 15 POLIWAG, 20 POLIWHIRL - surf only, so not reachable in this section. -Fishing group `FISHGROUP_POND` (`.Pond_Old`: MAGIKARP/POLIWAG). - -### MAP_ROUTE_30_BERRY_HOUSE - -- Script: `maps/Route30BerryHouse.asm`; blocks shared `maps/House1.blk` -- Header: `map Route30BerryHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_30, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const ROUTE_30_BERRY_HOUSE, 4, 4` - -**Warps**: 1 (2,7), 2 (3,7) -> `ROUTE_30` warp 1. -**BG events**: (0,1), (1,1) `MagazineBookshelfScript`. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE30BERRYHOUSE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route30BerryHousePokefanMScript` (`62:455c`) | -1 | - -Script: `checkevent EVENT_GOT_BERRY_FROM_ROUTE_30_HOUSE`; if clear, -`verbosegiveitem BERRY` then set the event. - -### MAP_MR_POKEMONS_HOUSE - -- Script: `maps/MrPokemonsHouse.asm` -- Blocks: `maps/MrPokemonsHouse.blk` (`data/maps/blocks.asm:940`) -- Header: `map MrPokemonsHouse, TILESET_FACILITY, INDOOR, LANDMARK_ROUTE_30, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const MR_POKEMONS_HOUSE, 4, 4` -- Scenes: `SCENE_MRPOKEMONSHOUSE_MEET_MR_POKEMON` = 0, - `SCENE_MRPOKEMONSHOUSE_NOOP` = 1 - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_30` | 2 | -| 2 | 3 | 7 | `ROUTE_30` | 2 | - -**Coord events**: none - the cutscene is a **scene script** -(`scene_script MrPokemonsHouseMeetMrPokemonScene, SCENE_MRPOKEMONSHOUSE_MEET_MR_POKEMON` --> `sdefer MrPokemonsHouseMrPokemonEventScript`), so it fires on map load. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `MrPokemonsHouse_ForeignMagazines` | -| 1 | 1 | `BGEVENT_READ` | `MrPokemonsHouse_ForeignMagazines` | -| 6 | 1 | `BGEVENT_READ` | `MrPokemonsHouse_BrokenComputer` | -| 7 | 1 | `BGEVENT_READ` | `MrPokemonsHouse_BrokenComputer` | -| 6 | 4 | `BGEVENT_READ` | `MrPokemonsHouse_StrangeCoins` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MRPOKEMONSHOUSE_GENTLEMAN` | `SPRITE_GENTLEMAN` | 3 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MrPokemonsHouse_MrPokemonScript` | -1 | -| `MRPOKEMONSHOUSE_OAK` | `SPRITE_OAK` | 6 | 5 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MR_POKEMONS_HOUSE_OAK` | - -**Scripts of interest** - -- `MrPokemonsHouseMrPokemonEventScript` (`62:464e`): `showemote EMOTE_SHOCK`, - `applymovement PLAYER, MrPokemonsHouse_PlayerWalksToMrPokemon` - (`step RIGHT`, `step UP`), `giveitem MYSTERY_EGG`, `itemnotify`, - `setevent EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON`, - **`blackoutmod CHERRYGROVE_CITY`** (respawn point moves), then falls into the Oak script. -- `MrPokemonsHouse_OakScript` (`62:46c1`): `playmusic MUSIC_PROF_OAK`, - `applymovement MRPOKEMONSHOUSE_OAK, MrPokemonsHouse_OakWalksToPlayer`, - **`setflag ENGINE_POKEDEX`**, `disappear MRPOKEMONSHOUSE_OAK`, - `special HealParty` (Mr. Pokemon's free heal the walkthrough mentions), then the - whole post-visit world state: - - `setevent EVENT_RIVAL_NEW_BARK_TOWN` - - `setscene SCENE_MRPOKEMONSHOUSE_NOOP` - - `setmapscene CHERRYGROVE_CITY, SCENE_CHERRYGROVECITY_MEET_RIVAL` - - `setmapscene ELMS_LAB, SCENE_ELMSLAB_MEET_OFFICER` - - `specialphonecall SPECIALCALL_ROBBED` - - `clearevent EVENT_COP_IN_ELMS_LAB` (cop appears) - - starter fork: sets `EVENT_TOTODILE_POKEBALL_IN_ELMS_LAB` / - `EVENT_CHIKORITA_POKEBALL_IN_ELMS_LAB` / `EVENT_CYNDAQUIL_POKEBALL_IN_ELMS_LAB` - (which ball is *missing* in the lab afterwards). -- `MrPokemonsHouse_MrPokemonScript`: repeat talk. Also the RED_SCALE -> - `EXP_SHARE` trade, far outside this section. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON` | `constants/event_flags.asm:39` | this scene | egg in bag | -| `ENGINE_POKEDEX` | `constants/engine_flags.asm:20` | `MrPokemonsHouse_OakScript` | dex owned | -| `EVENT_MR_POKEMONS_HOUSE_OAK` | `constants/event_flags.asm:1131` | `disappear MRPOKEMONSHOUSE_OAK` | Oak gone | -| `SPECIALCALL_ROBBED` | `constants/phone_constants.asm:46` | queued here, delivered by `ElmPhoneCallerScript` `.disaster` (`engine/phone/scripts/elm.asm:75`) | that call also does `setevent EVENT_ELM_CALLED_ABOUT_STOLEN_POKEMON` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MYSTERY_EGG` | scene on entry | `giveitem MYSTERY_EGG` in `MrPokemonsHouseMrPokemonEventScript` | `EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON` | -| POKEDEX (`ENGINE_POKEDEX`) | same scene | `MrPokemonsHouse_OakScript` | engine flag | - -### MAP_ROUTE_31 - -- Script: `maps/Route31.asm` -- Blocks: `maps/Route31.blk` -- Header: `map Route31, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_31, MUSIC_ROUTE_30, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `map_const ROUTE_31, 20, 9` (40x18 cells) -- Connections: south `Route30` (offset 10), west `VioletCity` (-9) -- Callbacks: `callback MAPCALLBACK_NEWMAP, Route31CheckMomCallCallback` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 6 | `ROUTE_31_VIOLET_GATE` | 3 | -| 2 | 4 | 7 | `ROUTE_31_VIOLET_GATE` | 4 | -| 3 | 34 | 5 | `DARK_CAVE_VIOLET_ENTRANCE` | 1 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 5 | `BGEVENT_READ` | `Route31Sign` | -| 31 | 5 | `BGEVENT_READ` | `DarkCaveSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE31_FISHER` | `SPRITE_FISHER` | 17 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route31MailRecipientScript` | -1 | -| `ROUTE31_YOUNGSTER` | `SPRITE_YOUNGSTER` | 9 | 5 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `Route31YoungsterScript` | -1 | -| `ROUTE31_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 18 | 15 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerBugCatcherWade1` (`4a:5a01`) | -1 | -| `ROUTE31_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 33 | 8 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `Route31CooltrainerMScript` | -1 | -| `ROUTE31_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 16 | 7 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route31FruitTree` (`4a:5b18`) | -1 | -| `ROUTE31_POKE_BALL1` | `SPRITE_POKE_BALL` | 29 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route31Antidote` (`4a:5b1a`) | `EVENT_ROUTE_31_ANTIDOTE` | -| `ROUTE31_POKE_BALL2` | `SPRITE_POKE_BALL` | 21 | 13 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route31PokeBall` (`4a:5b1c`) | `EVENT_ROUTE_31_POKE_BALL` | - -**Scripts of interest** - -- `Route31CheckMomCallCallback` (`MAPCALLBACK_NEWMAP`): - `checkevent EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST`; if **false**, - `specialphonecall SPECIALCALL_WORRIED`. A bot that skips the Bank of Mom talk - gets a scripted call the first time it walks onto Route 31. -- `TrainerBugCatcherWade1`: `trainer BUG_CATCHER, WADE1, EVENT_BEAT_BUG_CATCHER_WADE, ...`, - phone chain on `PHONE_BUG_CATCHER_WADE`, rematches `WADE2` / `WADE3` gated on - `ENGINE_FLYPOINT_GOLDENROD` / `ENGINE_FLYPOINT_MAHOGANY`. -- `Route31MailRecipientScript` at (17,7): the Kenya / SPEAROW-with-mail sidequest - (`EVENT_GOT_KENYA` -> `checkpokemail` -> `verbosegiveitem TM_NIGHTMARE`). Kenya - comes from Goldenrod, so this is dormant in this section. -- `Route31YoungsterScript`: the FALKNER / Violet Gym hint. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_31_ANTIDOTE` | `constants/event_flags.asm:1103` | `itemball` | ball at (29,5) taken | -| `EVENT_ROUTE_31_POKE_BALL` | `constants/event_flags.asm:1104` | `itemball` | ball at (21,13) taken | -| `EVENT_BEAT_BUG_CATCHER_WADE` | `constants/event_flags.asm:842` | `trainer` macro | Wade beaten | -| `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST` | `constants/event_flags.asm:73` | read by the map callback, set by `MomScript` | suppresses the "worried Mom" call | -| `SPECIALCALL_WORRIED` | `constants/phone_constants.asm:51` | map callback | queued call | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ANTIDOTE` | item ball at (29,5), just west of the Dark Cave mouth | `Route31Antidote` | `EVENT_ROUTE_31_ANTIDOTE` | -| `POKE_BALL` | item ball at (21,13) | `Route31PokeBall` | `EVENT_ROUTE_31_POKE_BALL` | -| `BITTER_BERRY` | tree at (16,7) | `FRUITTREE_ROUTE_31` (`data/items/fruit_trees.asm`) | daily | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `WADE1` | `BUG_CATCHER` (`$18`) | 4 | `BugCatcherGroup` (4) "WADE": L2 CATERPIE, L2 CATERPIE, L3 WEEDLE, L2 CATERPIE | `TrainerBugCatcherWade1` | yes, `PHONE_BUG_CATCHER_WADE` | - -Reward 4 (base) * 2 (last mon level) * 4 = 32G, matching the walkthrough. - -**Wild encounters** (`def_grass_wildmons ROUTE_31`, 10/10/10 percent, Gold arm) - -- morn: 3 PIDGEY, 4 CATERPIE, 3 BELLSPROUT, 5 METAPOD, 5 CATERPIE, 5 METAPOD, 5 METAPOD -- day: 3 PIDGEY, 4 CATERPIE, 3 BELLSPROUT, 5 METAPOD, 5 CATERPIE, 6 METAPOD, 6 METAPOD -- nite: 4 SPINARAK, 4 RATTATA, 3 BELLSPROUT, 5 RATTATA, 5 HOOTHOOT, 5 HOOTHOOT, 5 HOOTHOOT - -Water (`def_water_wildmons ROUTE_31`, 2 percent): 20 POLIWAG, 15 POLIWAG, -20 POLIWHIRL. Fishing group `FISHGROUP_POND`. - -### MAP_ROUTE_31_VIOLET_GATE - -- Script: `maps/Route31VioletGate.asm`; blocks shared `maps/EastWestGate.blk` - (`data/maps/blocks.asm:280`) -- Header: `map Route31VioletGate, TILESET_GATE, GATE, LANDMARK_ROUTE_31, MUSIC_ROUTE_30, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const ROUTE_31_VIOLET_GATE, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `VIOLET_CITY` | 8 | -| 2 | 0 | 5 | `VIOLET_CITY` | 9 | -| 3 | 9 | 4 | `ROUTE_31` | 1 | -| 4 | 9 | 5 | `ROUTE_31` | 2 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE31VIOLETGATE_OFFICER` | `SPRITE_OFFICER` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route31VioletGateOfficerScript` | -1 | -| `ROUTE31VIOLETGATE_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 1 | 2 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `Route31VioletGateCooltrainerFScript` | -1 | - -Nothing blocks; both NPCs are `jumptextfaceplayer`. - -### Return-leg maps - -Only the beats this section's text drives are transcribed. The first visit to all -three belongs to the previous section. - -#### MAP_NEW_BARK_TOWN - -- Script: `maps/NewBarkTown.asm`; blocks `maps/NewBarkTown.blk` -- Header: `map NewBarkTown, TILESET_JOHTO, TOWN, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `map_const NEW_BARK_TOWN, 10, 9`; connections west `Route29` (0), - east `Route27` (0) -- Callback `NewBarkTownFlypointCallback`: `setflag ENGINE_FLYPOINT_NEW_BARK` and - `clearevent EVENT_FIRST_TIME_BANKING_WITH_MOM` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 3 | `ELMS_LAB` | 1 | -| 2 | 13 | 5 | `PLAYERS_HOUSE_1F` | 1 | -| 3 | 3 | 11 | `PLAYERS_NEIGHBORS_HOUSE` | 1 | -| 4 | 11 | 13 | `ELMS_HOUSE` | 1 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (=0) | 1 | 8 | `NewBarkTown_TeacherStopsYouScene1` | teacher drags you back (`follow` + `applymovement`) | -| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` | 1 | 9 | `NewBarkTown_TeacherStopsYouScene2` | same, other lane | - -`ElmDirectionsScript` does `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP`, so -once a starter is in hand the west exit at x=1 is free. `NEWBARKTOWN_RIVAL` -(object at (3,2), flag `EVENT_RIVAL_NEW_BARK_TOWN`) is un-hidden by -`MrPokemonsHouse_OakScript` - he shoves the player away from the lab window. - -#### MAP_ELMS_LAB - -- Script: `maps/ElmsLab.asm`; blocks `maps/ElmsLab.blk` -- Header: `map ElmsLab, TILESET_LAB, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_PROF_ELM, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const ELMS_LAB, 5, 6` -- Scenes (ordinals): `SCENE_ELMSLAB_MEET_ELM` 0, `SCENE_ELMSLAB_CANT_LEAVE` 1, - `SCENE_ELMSLAB_NOOP` 2, `SCENE_ELMSLAB_MEET_OFFICER` 3, `SCENE_ELMSLAB_UNUSED` 4, - `SCENE_ELMSLAB_AIDE_GIVES_POTION` 5, `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` 6 - (the last is a bare `scene_const` - a scene id with no scene script, only coord - events) - -**Warps**: 1 (4,11), 2 (5,11) -> `NEW_BARK_TOWN` warp 1. - -**Coord events** (the ones this section fires) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ELMSLAB_MEET_OFFICER` | 4 | 5 | `MeetCopScript` | walk-up then `CopScript` | -| `SCENE_ELMSLAB_MEET_OFFICER` | 5 | 5 | `MeetCopScript2` | `step LEFT` first, then the same | -| `SCENE_ELMSLAB_AIDE_GIVES_POTION` | 4 | 8 | `AideScript_WalkPotion1` | `verbosegiveitem POTION` | -| `SCENE_ELMSLAB_AIDE_GIVES_POTION` | 5 | 8 | `AideScript_WalkPotion2` | same | -| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` | 4 | 8 | `AideScript_WalkBalls1` | `giveitem POKE_BALL, 5` | -| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` | 5 | 8 | `AideScript_WalkBalls2` | same | -| `SCENE_ELMSLAB_CANT_LEAVE` | 4/5 | 6 | `LabTryToLeaveScript` | pushes the player back up (pre-starter) | - -**Object events** (relevant rows) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ELMSLAB_ELM` | `SPRITE_ELM` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ProfElmScript` | -1 | -| `ELMSLAB_ELMS_AIDE` | `SPRITE_SCIENTIST` | 2 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `ElmsAideScript` | `EVENT_ELMS_AIDE_IN_LAB` | -| `ELMSLAB_OFFICER` | `SPRITE_OFFICER` | 5 | 3 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `CopScript` (`60:4366`) | `EVENT_COP_IN_ELMS_LAB` | - -**Scripts of interest** - -- `CopScript` (`60:4366`): `special NameRival` (this is the "name your rival" - prompt), then `applymovement OfficerLeavesMovement`, `disappear ELMSLAB_OFFICER`, - `setscene SCENE_ELMSLAB_NOOP`. -- `ElmAfterTheftScript` (`60:41f1`), reached from `ProfElmScript` when - `EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON` is set: `checkitem MYSTERY_EGG`, - `takeitem MYSTERY_EGG`, `setevent EVENT_GAVE_MYSTERY_EGG_TO_ELM`, - `setmapscene ROUTE_29, SCENE_ROUTE29_CATCH_TUTORIAL`, - `clearevent EVENT_ROUTE_30_YOUNGSTER_JOEY`, `setevent EVENT_ROUTE_30_BATTLE`, - `setscene SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS`. This one script is what turns on - the rest of the section. -- `AideScript_GiveYouBalls` (`60:4317`): `giveitem POKE_BALL, 5`, `itemnotify`, - `setscene SCENE_ELMSLAB_NOOP`. - -#### MAP_PLAYERS_HOUSE_1F - -- Script: `maps/PlayersHouse1F.asm`; blocks `maps/PlayersHouse1F.blk` -- Dimensions: `map_const PLAYERS_HOUSE_1F, 5, 4` -- Warps: 1 (6,7), 2 (7,7) -> `NEW_BARK_TOWN` warp 2; 3 (9,0) -> `PLAYERS_HOUSE_2F` 1 - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PLAYERSHOUSE1F_MOM1` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `MomScript` (`60:56cb`) | `EVENT_PLAYERS_HOUSE_MOM_1` | -| `PLAYERSHOUSE1F_MOM2` | `SPRITE_MOM` | 2 | 2 | `SPRITEMOVEDATA_STANDING_UP` (hours `-1, MORN`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | -| `PLAYERSHOUSE1F_MOM3` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` (hours `-1, DAY`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | -| `PLAYERSHOUSE1F_MOM4` | `SPRITE_MOM` | 0 | 2 | `SPRITEMOVEDATA_STANDING_UP` (hours `-1, NITE`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` | - -Mom's position depends on the time of day - a bot must find her, not assume (7,3). - -`MomScript` (`60:56cb`): with `EVENT_GAVE_MYSTERY_EGG_TO_ELM` set it runs -`.GaveMysteryEgg` -> `setevent EVENT_FIRST_TIME_BANKING_WITH_MOM`, -`setevent EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST`, `special BankOfMom`. -That is the walkthrough's "talk to your mom, who will save your money", and it is -also what silences the Route 31 `SPECIALCALL_WORRIED` callback. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Cannot leave New Bark Town westward before a starter | `maps/NewBarkTown.asm:NewBarkTown_TeacherStopsYouScene1/2` at coord events (1,8)/(1,9), scene `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` | a starter | `maps/ElmsLab.asm:ElmDirectionsScript` -> `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP` | -| Cannot leave the lab before a starter | `maps/ElmsLab.asm:LabTryToLeaveScript`, coord events (4,6)/(5,6) under `SCENE_ELMSLAB_CANT_LEAVE` | a starter | same `ElmDirectionsScript`, via `setscene SCENE_ELMSLAB_AIDE_GIVES_POTION` | -| Route 30 west fork blocked by "the two boys battling" | `maps/Route30.asm` object rows at (5,24), (5,25), (5,26), all flagged `EVENT_ROUTE_30_BATTLE`; objects are visible while the flag is **clear** (`engine/overworld/map_objects_2.asm:CheckObjectFlag`) | deliver the MYSTERY EGG | `maps/ElmsLab.asm:ElmAfterTheftScript` -> `setevent EVENT_ROUTE_30_BATTLE` (hides them) and `clearevent EVENT_ROUTE_30_YOUNGSTER_JOEY` (spawns Joey the trainer) | -| Rival battle before leaving Cherrygrove east | `maps/CherrygroveCity.asm` coord events (33,6)/(33,7) under `SCENE_CHERRYGROVECITY_MEET_RIVAL` | none - it is `BATTLETYPE_CANLOSE`, so a loss still advances | armed by `maps/MrPokemonsHouse.asm:MrPokemonsHouse_OakScript` (`setmapscene CHERRYGROVE_CITY, ...`); cleared by the scene's own `setscene SCENE_CHERRYGROVECITY_NOOP` | -| Cop cutscene in Elm's Lab | `maps/ElmsLab.asm` coord events (4,5)/(5,5) under `SCENE_ELMSLAB_MEET_OFFICER` -> `CopScript` (`special NameRival`) | walk to y=5 | `setscene SCENE_ELMSLAB_NOOP` at the end of `CopScript` | -| POKE BALLs not for sale | `maps/CherrygroveMart.asm:CherrygroveMartClerkScript` (`checkevent EVENT_GAVE_MYSTERY_EGG_TO_ELM`) -> `MART_CHERRYGROVE` vs `MART_CHERRYGROVE_DEX` in `data/items/marts.asm` | deliver the egg | `ElmAfterTheftScript` | -| Catch tutorial cannot fire yet | `maps/Route29.asm` coord events (53,8)/(53,9) need scene `SCENE_ROUTE29_CATCH_TUTORIAL` | deliver the egg | `ElmAfterTheftScript` -> `setmapscene ROUTE_29, SCENE_ROUTE29_CATCH_TUTORIAL` | -| Dark Cave is unusable (the walkthrough's "wait for Flash") | `engine/events/overworld.asm:FlashFunction/.CheckUseFlash`: `ld de, ENGINE_ZEPHYRBADGE / farcall CheckBadge / jr c, .nozephyrbadge`, then `wTimeOfDayPalset == DARKNESS_PALSET` | ZEPHYRBADGE **and** HM05 Flash in the party | Violet Gym, next sections. The cave itself is enterable (`Route31` warp 3 at (34,5)) - it is just black | -| Tuscany / PINK BOW not obtainable | `maps/Route29.asm:Route29TuscanyCallback` - `checkflag ENGINE_ZEPHYRBADGE`, else `disappear`; then `VAR_WEEKDAY == TUESDAY` | ZEPHYRBADGE + Tuesday | later section | -| Mom's "worried" call | `maps/Route31.asm:Route31CheckMomCallCallback` - fires `SPECIALCALL_WORRIED` unless `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST` | not a blocker, but an unavoidable interruption | talk to Mom (`MomScript`) before entering Route 31 | - ---- - -## 4. Bot checklist - -Preconditions in brackets, postconditions after the arrow. - -1. `ELMS_LAB` - walk to (4,8) or (5,8). [scene `SCENE_ELMSLAB_AIDE_GIVES_POTION`] - -> aide script runs, POTION in bag, `setscene SCENE_ELMSLAB_NOOP`. -2. `ELMS_LAB` - warp out at (4,11)/(5,11) -> `NEW_BARK_TOWN` warp 1 (6,3). -3. `NEW_BARK_TOWN` - walk west past x=1. [`SCENE_NEWBARKTOWN_NOOP`] -> Route 29 - (west connection). -4. `ROUTE_29` - walk west along y=11..13. Optional: face (12,2) and press A -> - BERRY (`fruittree`, daily); face/step on (48,2) and press A -> POTION, - sets `EVENT_ROUTE_29_POTION`. -5. `ROUTE_29` - grind wild encounters (10% step rate, `def_grass_wildmons ROUTE_29`) - to the walkthrough's level 8. -6. `ROUTE_29` -> `CHERRYGROVE_CITY` (west connection). -> map callback sets - `ENGINE_FLYPOINT_CHERRYGROVE`. -7. `CHERRYGROVE_CITY` - talk to `CHERRYGROVECITY_GRAMPS` at (32,6), answer YES. - [`EVENT_GUIDE_GENT_IN_HIS_HOUSE` clear] -> follow tour, MAP CARD, - `setflag ENGINE_MAP_CARD`, gramps disappears. -8. `CHERRYGROVE_CITY` - warp 2 at (29,3) -> Pokecenter; talk to nurse at (3,1) - from (3,2) -> party healed. Exit warps 1/2 at (3,7)/(4,7). -9. `CHERRYGROVE_CITY` - walk north out of the map (north connection, offset 5) -> - `ROUTE_30`, arriving at high y. -10. `ROUTE_30` - warp 1 at (7,39) -> berry house; talk to the Pokefan at (2,3) - -> BERRY, `EVENT_GOT_BERRY_FROM_ROUTE_30_HOUSE`. Exit warps 1/2 at (2,7)/(3,7). -11. `ROUTE_30` - face (11,5) press A -> PSNCUREBERRY (daily). Face (14,9) press A - -> hidden POTION, `EVENT_ROUTE_30_HIDDEN_POTION`. -12. `ROUTE_30` - take the **east** fork north to warp 2 at (17,5) -> - `MR_POKEMONS_HOUSE`. -13. `MR_POKEMONS_HOUSE` - the scene fires on load - (`SCENE_MRPOKEMONSHOUSE_MEET_MR_POKEMON`). Just advance text. - -> `MYSTERY_EGG`, `EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON`, - `blackoutmod CHERRYGROVE_CITY`, `ENGINE_POKEDEX`, party healed, - `SPECIALCALL_ROBBED` queued, `EVENT_COP_IN_ELMS_LAB` cleared, - `SCENE_CHERRYGROVECITY_MEET_RIVAL` and `SCENE_ELMSLAB_MEET_OFFICER` armed. -14. Walk out (warps 1/2 at (2,7)/(3,7)) and take a few overworld steps -> - Elm's phone call (`engine/phone/scripts/elm.asm` `.disaster`) -> - `EVENT_ELM_CALLED_ABOUT_STOLEN_POKEMON`. -15. `ROUTE_30` south -> `CHERRYGROVE_CITY`. Heal at the Pokecenter. -16. `CHERRYGROVE_CITY` - walk east through x=33 on row 6 or 7. -> rival scene, - battle `RIVAL1` id 1/2/3 by starter (level 5, `BATTLETYPE_CANLOSE`), then - `special HealParty`, `SCENE_CHERRYGROVECITY_NOOP`. -17. `CHERRYGROVE_CITY` east -> `ROUTE_29` east -> `NEW_BARK_TOWN`. -18. `NEW_BARK_TOWN` - warp 1 at (6,3) -> `ELMS_LAB`. Step onto (4,5) or (5,5) - [`SCENE_ELMSLAB_MEET_OFFICER`] -> `CopScript`, `special NameRival` prompt, - officer leaves, `SCENE_ELMSLAB_NOOP`. -19. `ELMS_LAB` - talk to `ELMSLAB_ELM` at (5,2). [`MYSTERY_EGG` in bag] - -> `ElmAfterTheftScript`: egg taken, `EVENT_GAVE_MYSTERY_EGG_TO_ELM`, - Route 29 tutorial armed, Route 30 blockers removed, Joey spawned, - `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS`. -20. `ELMS_LAB` - walk to (4,8) or (5,8) -> aide gives 5 POKE BALLs. -21. `NEW_BARK_TOWN` - warp 2 at (13,5) -> `PLAYERS_HOUSE_1F`; talk to Mom - (position depends on time of day: (7,3) by day, (2,2) morn, (0,2) nite) - -> `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST`, - `EVENT_FIRST_TIME_BANKING_WITH_MOM`, Bank of Mom menu. -22. `ROUTE_29` - step on (53,8) or (53,9) -> catch tutorial. YES runs the demo - (`loadwildmon RATTATA, 5` + `catchtutorial`); NO also clears the scene. -23. Optional `ROUTE_46`: `ROUTE_29` warp 1 at (27,1) -> gate warp 3, gate warps - 1/2 at (4,0)/(5,0) -> Route 46 (7,33)/(8,33). Hunt RATTATA (night has two - RATTATA slots). Grab DIRE_HIT at (0,12). Avoid the three trainers - they are - L13-L17 and far above the party. -24. `CHERRYGROVE_CITY` - heal; optionally buy POKE BALLs (`MART_CHERRYGROVE_DEX` - is live now) via warp 1 at (23,3). -25. `ROUTE_30` - take the **west** fork north. Trainers in walk order: - `TrainerYoungsterJoey` at (6,29) sight 4, `TrainerYoungsterMikey` at (5,23) - sight 1, `TrainerBugCatcherDon` at (4,7) sight 3. - -> `EVENT_BEAT_YOUNGSTER_JOEY`, `EVENT_BEAT_YOUNGSTER_MIKEY`, - `EVENT_BEAT_BUG_CATCHER_DON`. Joey will ask for a phone number. -26. `ROUTE_30` north (y -> 0) -> `ROUTE_31`. -> map callback: if Mom was not - talked to, `SPECIALCALL_WORRIED` is queued. -27. `ROUTE_31` - item ball at (29,5) -> ANTIDOTE. Sign at (31,5) is the Dark Cave - sign; the cave warp is at (34,5) - **skip it**, no Flash. -28. `ROUTE_31` - `TrainerBugCatcherWade1` at (18,15), sight 3 (approach from below). - -> `EVENT_BEAT_BUG_CATCHER_WADE`, phone number offer. -29. `ROUTE_31` - item ball at (21,13) -> POKE BALL. Tree at (16,7) -> BITTER BERRY. -30. `ROUTE_31` - warps 1/2 at (4,6)/(4,7) -> `ROUTE_31_VIOLET_GATE` warps 3/4; - cross to gate warps 1/2 at (0,4)/(0,5) -> `VIOLET_CITY` warps 8/9. - **End of section.** - ---- - -## 5. Port coverage - -The Gen 2 port is data-driven: `src/import/RomExtractorGen2.lua` reads map headers, -warps, coord events, bg events, object events, trainer structs and encounter tables -out of the ROM, and `src/world/gen2/World.lua` + `src/script/gen2/Vm.lua` replay -them. So coverage below is per-mechanic, not per-map. - -| Beat | Port file | Status | -|---|---|---| -| Map headers / warps / connections / coord events / bg events / objects | `src/import/RomExtractorGen2.lua`, `src/world/gen2/Map.lua`, `src/world/gen2/World.lua` (`self.map.def.coordEvents` at World.lua:5013) | implemented | -| Scene scripts + `setscene` / `setmapscene` | `src/script/gen2/Vm.lua` (`setmapscene` at Vm.lua:279), `src/world/gen2/World.lua` (`s.sceneId` match at World.lua:5033) | implemented | -| Object visibility from `EVENT_*` (`appear` / `disappear`, the Route 30 blockers, the rival, the cop) | `src/world/gen2/Events.lua`, `src/world/gen2/World.lua` | implemented | -| `follow` / `stopfollow` (Guide Gent tour, catch-tutorial DUDE, New Bark teacher) | `src/script/gen2/Vm.lua:982` | implemented; driver `tests/drivers/gold_teacher_scene.lua` asserts the follow leg | -| Overworld trainer sight + battle + beat flag | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua` | implemented; driver `tests/drivers/gold_trainer_smoke.lua` runs the **Route 30 bug catcher** end to end | -| Trainer phone numbers / rematch chain (Joey, Wade) | `src/core/gen2/Phone.lua`, `src/script/gen2/Vm.lua` (`askforphonenumber`, `checkcellnum`) | implemented (not audited against this section's specific rematch gates) | -| `specialphonecall` queue + Elm's `SPECIALCALL_ROBBED` / Mom's `SPECIALCALL_WORRIED` | `src/script/gen2/Vm.lua:1364`, `src/core/gen2/Phone.lua` | implemented | -| Catch tutorial (`catchtutorial BATTLETYPE_TUTORIAL`) | `src/core/gen2/CatchTutorial.lua`, `src/core/gen2/AutoInput.lua`, `src/script/gen2/Vm.lua` | implemented | -| Fruit trees (Route 29/30/31/46 berries) | `src/script/gen2/Vm.lua:1191` (inlines `FruitTreeScript` because the extractor cannot reach it), `src/core/gen2/Apricorns.lua` | implemented | -| Hidden items (`BGEVENT_ITEM`, Route 30's POTION) | `src/world/gen2/HiddenItems.lua`, dispatch at `src/world/gen2/World.lua:5291-5296` | implemented | -| **Item balls** (`OBJECTTYPE_ITEMBALL`: Route 29 POTION, Route 31 ANTIDOTE + POKE BALL, Route 46 DIRE HIT) | extracted into `obj.itemball` at `src/import/RomExtractorGen2.lua:2968`; **nothing consumes it** - the A-press dispatch at `src/world/gen2/World.lua:5265-5296` only handles trainer, strength boulder, `scriptKey`, then hidden items, and `itemball` appears nowhere else in `src/` | **missing** - every ground item ball in this section is un-takeable | -| `verbosegiveitem` / `giveitem` / `itemnotify` (egg, Poke Balls, berries, MYSTIC WATER) | `src/script/gen2/Vm.lua` | implemented | -| `pokemart` + the `MART_CHERRYGROVE` / `MART_CHERRYGROVE_DEX` switch | `src/ui/gen2/MartMenu.lua`, `src/script/gen2/Vm.lua` | implemented (the switch itself is just a `checkevent`, so it follows for free) | -| `special BankOfMom` (Mom saving money) | `src/ui/gen2/BankOfMom.lua`, `src/script/gen2/Specials.lua` | implemented; driver `tests/drivers/gold_mom_scene.lua` covers the `MeetMomScript` cutscene, not the banking talk | -| `special NameRival` (cop scene) | `src/script/gen2/Specials.lua`, `src/ui/gen2/NamingScreen.lua` | implemented | -| `setflag ENGINE_POKEDEX` / `ENGINE_FLYPOINT_*` / `ENGINE_ZEPHYRBADGE` | `src/script/gen2/Vm.lua:208`, `src/world/gen2/World.lua:1304-1339` (`save.engineFlags`) | implemented | -| **`setflag ENGINE_MAP_CARD` reaching the Pokegear** | written to `save.engineFlags` (World.lua:1325); `src/ui/gen2/Pokegear.lua:935` reads `save.pokegearFlags` with keys `map` / `radio` / `phone`, and no code in the repo writes `pokegearFlags` outside tests and `tests/drivers/gold_menu_shots.lua` | **partial** - the Guide Gent's reward is recorded but the MAP card will not appear on the Pokegear | -| Time-of-day wild tables (morn/day/nite split, per-time rate) | `src/battle/gen2/Encounter.lua` | implemented | -| Fishing groups (`FISHGROUP_SHORE` / `FISHGROUP_POND`) | `src/battle/gen2/Encounter.lua:81-99` | implemented | -| Water encounters (Route 30/31 Poliwag) | `src/battle/gen2/Encounter.lua` | implemented but unreachable in this section (needs Surf) | -| Flash / `ENGINE_ZEPHYRBADGE` gate on Dark Cave | `src/world/gen2/FieldMoves.lua` | implemented | -| `blackoutmod CHERRYGROVE_CITY` | `src/script/gen2/Vm.lua`, `src/world/gen2/World.lua` | implemented | -| Route 29 Tuscany weekday/badge callback | `src/world/gen2/World.lua` map callbacks; asserted by `tests/drivers/gold_map_callbacks.lua:118-158` | implemented | -| New Bark -> Route 29 edge crossing | `tests/drivers/gold_walk_smoke.lua` | implemented (driver walks bedroom -> Route 29) | -| Cherrygrove City itself (guide tour, rival scene, Pokecenter) | no dedicated driver found under `tests/drivers/gold_*.lua` | untested | - ---- - -## 6. Unresolved / verify by hand - -1. **Route 30 "Antidote" does not exist.** The walkthrough says "Just above the - house is an Antidote". `maps/Route30.asm` has no `OBJECTTYPE_ITEMBALL` object - at all; the only ground item is the hidden `POTION` at bg event (14,9) - (`Route30HiddenPotion`). The ANTIDOTE is on Route 31, item ball at (29,5). -2. **Route 31 "Potion" is an Antidote.** The walkthrough says "grab that Potion to - the left of [Dark Cave]". The ball west of the Dark Cave mouth (34,5) is at - (29,5) and is `itemball ANTIDOTE`. There is no POTION object on Route 31. -3. **Youngster Mikey's party.** The walkthrough says "Level 2 Rattata / Level 4 - Rattata". `data/trainers/parties.asm` `YoungsterGroup` (2) "MIKEY" is - `db 2, PIDGEY` / `db 4, RATTATA`. -4. **Hoppip on Route 29.** Listed by the walkthrough (#187), absent from - `def_grass_wildmons ROUTE_29`. Hoppip's first Johto grass appearance is - Route 32. -5. **Zubat and Poliwag on Routes 30/31.** Listed by the walkthrough. Poliwag is - in `data/wild/johto_water.asm` (surf, 2% rate) and in `.Pond_*` fishing, not in - grass. Zubat is not in either route's grass table at any time of day - it is a - Dark Cave mon. -6. **Weedle / Kakuna on Routes 30-31 in a Gold run.** The walkthrough lists them - for both routes; in `data/wild/johto_grass.asm` those slots are inside the - `ELIF DEF(_SILVER)` arm. A Gold cart gets CATERPIE / METAPOD instead. The - walkthrough's own "(Silver only)" / "(Gold only)" notes are right for Ledyba - and Spinarak but not applied to Weedle/Kakuna. -7. **"Pink Bow" listed as a Route 29 item.** Reachable only with - `ENGINE_ZEPHYRBADGE` **and** `VAR_WEEKDAY == TUESDAY` - (`Route29TuscanyCallback`), so it cannot be collected during this section. -8. **Onix in Violet City / TM31 / PRZCure Berry.** Listed in the walkthrough's - Violet City preamble; `maps/VioletCity.asm` was not opened (next section owns - it) so none of it is verified here. -9. **"You get: 300G / 64G / 48G / 32G".** Verified indirectly: reward = - `base_reward * last mon level` (`engine/battle/read_trainer_party.asm:300` - `ComputeTrainerReward`) paid four times by the `ld c, 4` loop in - `engine/battle/core.asm:2340-2361`. All four figures match. Worth a hand-check - in an emulator if a bot budgets money precisely, because the split between - `wMomsMoney` and `wMoney` depends on `wMomSavingMoney`, i.e. on whether the - player has done step 21. -10. **`EVENT_ROUTE_30_BATTLE` as a physical block.** The three objects at - (5,24)/(5,25)/(5,26) are what the walkthrough calls "You can't pass that area - just yet". Confirmed that they exist and vanish on - `ElmAfterTheftScript`, but whether that column of three fully seals the west - lane depends on `maps/Route30.blk` collision, which was not decoded. Verify in - game if a bot pathfinder wants to rely on it. -11. **MAP CARD on the Pokegear in this port.** See section 5 - the flag is stored - under `save.engineFlags` while the Pokegear reads `save.pokegearFlags`. No - bridge was found by grep; if one exists it is somewhere the string - `pokegearFlags` does not appear. diff --git a/docs/gold-walkthrough/asm-walk/section-02-sprout-tower-and-violet-city-gym.md b/docs/gold-walkthrough/asm-walk/section-02-sprout-tower-and-violet-city-gym.md deleted file mode 100644 index 2326ae4a..00000000 --- a/docs/gold-walkthrough/asm-walk/section-02-sprout-tower-and-violet-city-gym.md +++ /dev/null @@ -1,777 +0,0 @@ -# Section 02 - Sprout Tower and Violet City Gym - -Source: `../section-02-sprout-tower-and-violet-city-gym.txt` -Maps covered: `MAP_SPROUT_TOWER_1F`, `MAP_SPROUT_TOWER_2F`, `MAP_SPROUT_TOWER_3F`, -`MAP_VIOLET_CITY`, `MAP_VIOLET_POKECENTER_1F`, `MAP_VIOLET_GYM` -Badges / key milestones in this section: HM05 Flash (`EVENT_GOT_HM05_FLASH`), -the Sprout Tower rival cutscene (`EVENT_RIVAL_SPROUT_TOWER`), ZEPHYRBADGE -(`ENGINE_ZEPHYRBADGE`, `EVENT_BEAT_FALKNER`), TM31 Mud-Slap -(`EVENT_GOT_TM31_MUD_SLAP`). - -All map constants below use the `MAP_*` spelling that both -`constants/map_constants.asm` and this port's map ids use; the port's -`World:setMap` takes exactly the bare constant name (`"SPROUT_TOWER_1F"`), which -is what `tools/rom_manifest_gold.json` -> `constants.mapGroups` stores. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_SPROUT_TOWER_1F` | `maps/SproutTower1F.asm` | `MAP_VIOLET_CITY` warp 7 at (23,5) -> 1F warp 1 (9,15) | 1F warp 3 (6,4) -> 2F warp 1 | "Head up the stairs to Floor 2." | -| 2 | `MAP_SPROUT_TOWER_2F` | `maps/SproutTower2F.asm` | 2F warp 1 (6,4) | 2F warp 3 (17,3) -> 1F warp 5 | Sage Nico at (14,4), then "down the stairs" | -| 3 | `MAP_SPROUT_TOWER_1F` | `maps/SproutTower1F.asm` | 1F warp 5 (17,3) | 1F warp 4 (2,6) -> 2F warp 2 | Parlyz Heal ball at (16,7), then Sage Chow at (2,1) | -| 4 | `MAP_SPROUT_TOWER_2F` | `maps/SproutTower2F.asm` | 2F warp 2 (2,6) | 2F warp 4 (10,14) -> 3F warp 1 | X Defend ball at (3,1), Sage Edmond at (3,15) | -| 5 | `MAP_SPROUT_TOWER_3F` | `maps/SproutTower3F.asm` | 3F warp 1 (10,14) | 3F warp 1 (10,14) -> 2F warp 4 | Potion, Jin/Neal/Troy, rival cutscene, Sage Li, HM05, Escape Rope | -| 6 | `MAP_SPROUT_TOWER_2F` -> `MAP_SPROUT_TOWER_1F` | as above | 2F warp 4 / 1F warps 3-5 | 1F warp 1 (9,15) or warp 2 (10,15) -> `MAP_VIOLET_CITY` warp 7 | "Get out of the tower" (Escape Rope also legal: the tower is `DUNGEON`) | -| 7 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 7 (23,5) | city warp 5 (31,25) | "go back to the Pokemon Center of Violet City" | -| 8 | `MAP_VIOLET_POKECENTER_1F` | `maps/VioletPokecenter1F.asm` | PC warp 1 (3,7) | PC warp 1/2 (3,7)/(4,7) | Heal at the nurse before the gym | -| 9 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 5 (31,25) | city warp 2 (18,17) | "head northwest in the city to the Violet City Gym" | -| 10 | `MAP_VIOLET_GYM` | `maps/VioletGym.asm` | gym warp 1 (4,15) | gym warp 1/2 (4,15)/(5,15) -> `MAP_VIOLET_CITY` warp 2 | Abe, Rod, Falkner, ZEPHYRBADGE + TM31 | -| 11 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 2 (18,17) | (section ends) | Section closes with the badge in hand | - -Spills into the next section: beating Falkner runs `specialphonecall -SPECIALCALL_ASSISTANT` (`maps/VioletGym.asm`), whose Elm phone script -(`engine/phone/scripts/elm.asm`, `.assistant`) clears -`EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` so the aide appears in -`MAP_VIOLET_POKECENTER_1F` with the Togepi egg, and sets -`SCENE_ROUTE32_OFFER_SLOWPOKETAIL` when the egg is taken. That whole beat -belongs to the next section; it is listed here only because the trigger lives -in this section's gym script. - -## 2. Maps - -### MAP_SPROUT_TOWER_1F - -- Script: `maps/SproutTower1F.asm` -- Blocks: `maps/SproutTower1F.blk` (`data/maps/blocks.asm` -> `SproutTower1F_Blocks`) -- Header: `data/maps/maps.asm:79` -> `TILESET_TOWER`, `DUNGEON`, - `LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions / attributes: `constants/map_constants.asm:66` - (`map_const SPROUT_TOWER_1F, 10, 8`) = 20x16 cells; - `data/maps/attributes.asm:398` (`map_attributes SproutTower1F, SPROUT_TOWER_1F, $00`) -- Connections: none (indoor/dungeon) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `VIOLET_CITY` | 7 | -| 2 | 10 | 15 | `VIOLET_CITY` | 7 | -| 3 | 6 | 4 | `SPROUT_TOWER_2F` | 1 | -| 4 | 2 | 6 | `SPROUT_TOWER_2F` | 2 | -| 5 | 17 | 3 | `SPROUT_TOWER_2F` | 3 | - -**Coord events** (`def_coord_events`) - -None (`def_coord_events` is empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 15 | `BGEVENT_READ` | `SproutTower1FStatue` | -| 12 | 15 | `BGEVENT_READ` | `SproutTower1FStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SPROUTTOWER1F_SAGE1` | `SPRITE_SAGE` | 7 | 4 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FSage1Script` | -1 | -| `SPROUTTOWER1F_SAGE2` | `SPRITE_SAGE` | 6 | 7 | `SPRITEMOVEDATA_WANDER`, radius 1/1, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FSage2Script` | -1 | -| `SPROUTTOWER1F_GRANNY` | `SPRITE_GRANNY` | 11 | 12 | `SPRITEMOVEDATA_STANDING_LEFT`, radius 0/0 | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FGrannyScript` | -1 | -| `SPROUTTOWER1F_TEACHER` | `SPRITE_TEACHER` | 9 | 9 | `SPRITEMOVEDATA_STANDING_UP`, radius 0/0 | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FTeacherScript` | -1 | -| `SPROUTTOWER1F_SAGE3` | `SPRITE_SAGE` | 2 | 1 | `SPRITEMOVEDATA_STANDING_RIGHT`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerSageChow` | -1 | -| `SPROUTTOWER1F_POKE_BALL` | `SPRITE_POKE_BALL` | 16 | 7 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL`, sight 0 | `SproutTower1FParlyzHeal` | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` | - -**Scripts of interest** - -- `TrainerSageChow` (`42:400e`): `trainer SAGE, CHOW, EVENT_BEAT_SAGE_CHOW, - SageChowSeenText, SageChowBeatenText, 0, .Script`. Loss text is `0`. The - after-battle arm `.Script` (`42:401a`) is `endifjustbattled` then a single - `writetext SageChowAfterBattleText`. -- `SproutTower1FParlyzHeal` (`42:4022`): a bare `itemball PARLYZ_HEAL` - two raw - bytes, **not** bytecode. Pickup is engine-side (`FindItemInBallScript`), gated - on `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL`. -- Sage1/Sage2/Granny/Teacher are all `jumptextfaceplayer` one-liners; nothing to - gate on. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SAGE_CHOW` (1041) | `constants/event_flags.asm:518` | `TrainerSageChow` header word | Set once beaten; suppresses re-battle and the eyesight trigger | -| `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` (1607) | `constants/event_flags.asm:1000` | object-event flag on `SPROUTTOWER1F_POKE_BALL` | Set = ball is gone (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm`) | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `PARLYZ_HEAL` | Poke Ball object at (16,7) | `SproutTower1FParlyzHeal` (`itemball PARLYZ_HEAL`) | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SAGE`, `CHOW` | `SAGE` (38) | `CHOW` (1) | `SageGroup` "CHOW" (`0e:7324` group base): L3 Bellsprout x3, `TRAINERTYPE_NORMAL` | `TrainerSageChow` | none | - -Prize money: `data/trainers/attributes.asm` "Sage" base reward `8`; -`ComputeTrainerReward` x last party level (3) x4 in `WinTrainerBattle` = 96. - -**Wild encounters** - -None. `data/wild/johto_grass.asm` has **no** `def_grass_wildmons SPROUT_TOWER_1F` -entry; only 2F and 3F have tables. 1F has no grass/water/fish/headbutt data. - ---- - -### MAP_SPROUT_TOWER_2F - -- Script: `maps/SproutTower2F.asm` -- Blocks: `maps/SproutTower2F.blk` -- Header: `data/maps/maps.asm:80` -> `TILESET_TOWER`, `DUNGEON`, - `LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions / attributes: `constants/map_constants.asm:67` - (`map_const SPROUT_TOWER_2F, 10, 8`) = 20x16 cells; - `data/maps/attributes.asm:399` -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 4 | `SPROUT_TOWER_1F` | 3 | -| 2 | 2 | 6 | `SPROUT_TOWER_1F` | 4 | -| 3 | 17 | 3 | `SPROUT_TOWER_1F` | 5 | -| 4 | 10 | 14 | `SPROUT_TOWER_3F` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 12 | 15 | `BGEVENT_READ` | `SproutTower2FStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SPROUTTOWER2F_SAGE1` | `SPRITE_SAGE` | 14 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSageNico` | -1 | -| `SPROUTTOWER2F_SAGE2` | `SPRITE_SAGE` | 3 | 15 | `SPRITEMOVEDATA_STANDING_UP`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerSageEdmond` | -1 | -| `SPROUTTOWER2F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL`, sight 0 | `SproutTower2FXDefend` | `EVENT_SPROUT_TOWER_2F_X_DEFEND` | - -**Scripts of interest** - -- `TrainerSageNico` (`42:4280`) and `TrainerSageEdmond` (`42:4294`): identical - shape to Chow - `trainer` header, `.Script` = `endifjustbattled` + one - `writetext`. -- `SproutTower2FXDefend` (`42:42ab`): `itemball X_DEFEND`. Note the item is - **X Defend**, not X Accuracy. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SAGE_NICO` (1042) | `constants/event_flags.asm:519` | `TrainerSageNico` | Beaten once | -| `EVENT_BEAT_SAGE_EDMOND` (1047) | `constants/event_flags.asm:524` | `TrainerSageEdmond` | Beaten once | -| `EVENT_SPROUT_TOWER_2F_X_DEFEND` (1608) | `constants/event_flags.asm:1001` | object-event flag | Ball taken | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `X_DEFEND` | Poke Ball object at (3,1) | `SproutTower2FXDefend` | `EVENT_SPROUT_TOWER_2F_X_DEFEND` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `SAGE`, `NICO` | `SAGE` | `NICO` (2) | `SageGroup` "NICO": L3 Bellsprout x3 | `TrainerSageNico` | none | -| `SAGE`, `EDMOND` | `SAGE` | `EDMOND` (7) | `SageGroup` "EDMOND": L3 Bellsprout x3 | `TrainerSageEdmond` | none | - -Both pay 8 x 3 x 4 = 96. - -**Wild encounters** - -`data/wild/johto_grass.asm:5` `def_grass_wildmons SPROUT_TOWER_2F`, rates -`2 percent / 2 percent / 2 percent` (morn/day/nite): - -- morn: L3 Rattata, L4 Rattata, L5 Rattata, L3 Rattata, L6 Rattata, L5 Rattata, L5 Rattata -- day: identical to morn -- nite: L3 Gastly, L4 Gastly, L5 Gastly, L3 Rattata, L6 Gastly, L5 Rattata, L5 Rattata - -No water/fish/headbutt entry for this map. - ---- - -### MAP_SPROUT_TOWER_3F - -- Script: `maps/SproutTower3F.asm` -- Blocks: `maps/SproutTower3F.blk` -- Header: `data/maps/maps.asm:81` -> `TILESET_TOWER`, `DUNGEON`, - `LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions / attributes: `constants/map_constants.asm:68` - (`map_const SPROUT_TOWER_3F, 10, 8`) = 20x16 cells; - `data/maps/attributes.asm:400` -- Connections: none -- Scene variable: `data/maps/scenes.asm:47` -> `wSproutTower3FSceneID` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 10 | 14 | `SPROUT_TOWER_2F` | 4 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` (0) | 11 | 9 | `SproutTower3FRivalScene` | The elder/rival cutscene; ends with `setscene SCENE_SPROUTTOWER3F_NOOP` | - -The two `SCENE_SPROUTTOWER3F_*` constants are generated inline by the -`scene_script` macro (`macros/scripts/maps.asm`, `scene_const`) in -`maps/SproutTower3F.asm:12-13`: index 0 = `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER`, -index 1 = `SCENE_SPROUTTOWER3F_NOOP`. Both scene *scripts* are bare `end` -stubs; the scene id exists only to arm/disarm the coord event. The scene starts -at 0 on a new game, so the trip-wire is live on first entry. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 8 | 1 | `BGEVENT_READ` | `SproutTower3FStatue` | -| 11 | 1 | `BGEVENT_READ` | `SproutTower3FStatue` | -| 9 | 0 | `BGEVENT_READ` | `SproutTower3FPainting` | -| 10 | 0 | `BGEVENT_READ` | `SproutTower3FPainting` | -| 5 | 15 | `BGEVENT_READ` | `SproutTower3FStatue` | -| 14 | 15 | `BGEVENT_READ` | `SproutTower3FStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SPROUTTOWER3F_SAGE1` | `SPRITE_SAGE` | 8 | 13 | `SPRITEMOVEDATA_STANDING_RIGHT`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSageJin` | -1 | -| `SPROUTTOWER3F_SAGE2` | `SPRITE_SAGE` | 8 | 8 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSageTroy` | -1 | -| `SPROUTTOWER3F_SAGE3` | `SPRITE_SAGE` | 10 | 2 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SageLiScript` | -1 | -| `SPROUTTOWER3F_SAGE4` | `SPRITE_SAGE` | 11 | 11 | `SPRITEMOVEDATA_STANDING_LEFT`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSageNeal` | -1 | -| `SPROUTTOWER3F_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 14 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `SproutTower3FPotion` | `EVENT_SPROUT_TOWER_3F_POTION` | -| `SPROUTTOWER3F_POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `SproutTower3FEscapeRope` | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` | -| `SPROUTTOWER3F_RIVAL` | `SPRITE_RIVAL` | 10 | 4 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` (the shared ROM0 stub) | `EVENT_RIVAL_SPROUT_TOWER` | - -Object-flag polarity, confirmed in `engine/overworld/map_objects_2.asm` -(`CheckObjectFlag`): the object is **masked when its event flag is SET**. -`EVENT_RIVAL_SPROUT_TOWER` therefore starts *clear* (rival visible) and the -cutscene's `disappear SPROUTTOWER3F_RIVAL` sets it -(`Script_disappear` -> `ApplyEventActionAppearDisappear`, `b = 1`, -`engine/overworld/scripting.asm:887`). - -**Scripts of interest** - -- `SproutTower3FRivalScene` (`42:444b`), fired by the coord event at (11,9): - `turnobject PLAYER, UP`; `showemote EMOTE_SHOCK, PLAYER, 15`; - `special FadeOutMusic`; two rounds of `playsound SFX_TACKLE` + - `playsound SFX_ELEVATOR` + `earthquake 79` + `pause 15`; - `applymovement PLAYER, SproutTower3FPlayerApproachesRivalMovement` (four - `step UP`, so the player ends at (11,5)); - `applymovement SPROUTTOWER3F_RIVAL, SproutTower3FRivalApproachesElderMovement` - (one `step UP`, rival (10,4) -> (10,3)); - `writetext SproutTowerElderLecturesRivalText`; - `showemote EMOTE_SHOCK, SPROUTTOWER3F_RIVAL, 15`; - `turnobject SPROUTTOWER3F_RIVAL, DOWN`; - `applymovement SPROUTTOWER3F_RIVAL, SproutTower3FRivalLeavesElderMovement` - (`step RIGHT`, `step DOWN`); `playmusic MUSIC_RIVAL_ENCOUNTER`; - two more `writetext`s; `playsound SFX_WARP_TO`; `special FadeOutToBlack`; - `special ReloadSpritesNoPalettes`; `disappear SPROUTTOWER3F_RIVAL`; - `waitsfx`; `special FadeInFromBlack`; `setscene SCENE_SPROUTTOWER3F_NOOP`; - `special RestartMapMusic`; `end`. - **No battle.** This is a pure cutscene - the rival is not fought here. -- `SageLiScript` (`42:44aa`): `faceplayer`, `opentext`, - `checkevent EVENT_GOT_HM05_FLASH` / `iftrue .GotFlash`. First-time arm: - `writetext SageLiSeenText`, `winlosstext SageLiBeatenText, 0`, - `loadtrainer SAGE, LI`, `startbattle`, `reloadmapafterbattle`, - `writetext SageLiTakeThisFlashText`, `promptbutton`, - `verbosegiveitem HM_FLASH`, `setevent EVENT_GOT_HM05_FLASH`, - `setevent EVENT_BEAT_SAGE_LI`, `writetext SageLiFlashExplanationText`, `end`. - `.GotFlash` (`42:44d5`) is a single `writetext SageLiAfterBattleText`. - Note the flag order: the HM is given *before* both events are set, and - `verbosegiveitem`'s failure return is **not** checked here (unlike Falkner's - TM), so a full bag at this point loses the HM. -- `TrainerSageJin` (`42:44db`), `TrainerSageTroy` (`42:44ef`), - `TrainerSageNeal` (`42:4503`): standard `trainer` headers + `endifjustbattled` - after-battle text. -- `SproutTower3FPotion` (`42:451d`) = `itemball POTION`; - `SproutTower3FEscapeRope` (`42:451f`) = `itemball ESCAPE_ROPE`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SAGE_JIN` (1043) | `constants/event_flags.asm:520` | `TrainerSageJin` | Beaten | -| `EVENT_BEAT_SAGE_TROY` (1044) | `constants/event_flags.asm:521` | `TrainerSageTroy` | Beaten | -| `EVENT_BEAT_SAGE_NEAL` (1048) | `constants/event_flags.asm:525` | `TrainerSageNeal` | Beaten | -| `EVENT_BEAT_SAGE_LI` (1049) | `constants/event_flags.asm:526` | set by `SageLiScript` after the battle | Set only on the HM-giving pass; Li is an `OBJECTTYPE_SCRIPT`, not a trainer object, so this flag does not gate re-battle - `EVENT_GOT_HM05_FLASH` does | -| `EVENT_GOT_HM05_FLASH` (20) | `constants/event_flags.asm:27` | read+written by `SageLiScript` | The real "tower cleared" flag | -| `EVENT_RIVAL_SPROUT_TOWER` (1732) | `constants/event_flags.asm:1126` | object-event flag; set by `disappear` in the cutscene; also `setevent` in `maps/HallOfFame.asm:35` | Clear = rival still standing at (10,4) | -| `EVENT_SPROUT_TOWER_3F_POTION` (1609) | `constants/event_flags.asm:1002` | object-event flag | Ball taken | -| `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` (1610) | `constants/event_flags.asm:1003` | object-event flag | Ball taken | -| `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` = 0 | `maps/SproutTower3F.asm:12` (macro-generated) | `wSproutTower3FSceneID` (`data/maps/scenes.asm:47`) | Coord event live | -| `SCENE_SPROUTTOWER3F_NOOP` = 1 | `maps/SproutTower3F.asm:13` | `setscene` at the end of the cutscene; `setmapscene` in `maps/HallOfFame.asm:39` | Coord event dead | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `POTION` | Poke Ball at (6,14) | `SproutTower3FPotion` | `EVENT_SPROUT_TOWER_3F_POTION` | -| `ESCAPE_ROPE` | Poke Ball at (14,1) | `SproutTower3FEscapeRope` | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` | -| `HM_FLASH` (HM05) | `verbosegiveitem` from Sage Li after his battle | `SageLiScript` | `EVENT_GOT_HM05_FLASH` | - -`HM_FLASH` is the fifth `add_hm` in `constants/item_constants.asm` -(CUT, FLY, SURF, STRENGTH, **FLASH**, WHIRLPOOL, WATERFALL), i.e. HM05. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `SAGE`, `JIN` | `SAGE` | `JIN` (3) | `SageGroup` "JIN": L6 Bellsprout | `TrainerSageJin` | none | -| `SAGE`, `TROY` | `SAGE` | `TROY` (4) | `SageGroup` "TROY": L7 Bellsprout, L7 Hoothoot | `TrainerSageTroy` | none | -| `SAGE`, `NEAL` | `SAGE` | `NEAL` (8) | `SageGroup` "NEAL": L6 Bellsprout | `TrainerSageNeal` | none | -| `SAGE`, `LI` | `SAGE` | `LI` (9) | `SageGroup` "LI": L7 Bellsprout, L7 Bellsprout, L10 Hoothoot | `SageLiScript` (`loadtrainer`, not an `OBJECTTYPE_TRAINER`) | none | - -Prize money (base 8 x last party level x 4): Jin 192, Neal 192, Troy 224, -Li 320. All `TRAINERTYPE_NORMAL` (no explicit moves; movesets come from level-up -data). Class DVs `dn 9, 8, 8, 8` (`data/trainers/dvs.asm:61`). - -**Wild encounters** - -`data/wild/johto_grass.asm:33` `def_grass_wildmons SPROUT_TOWER_3F` - byte for -byte identical to the 2F table: rates 2/2/2 percent, morn+day all Rattata L3-L6, -nite Gastly L3-L6 mixed with Rattata L3/L5/L5. No water/fish/headbutt entry. - ---- - -### MAP_VIOLET_CITY - -- Script: `maps/VioletCity.asm` -- Blocks: `maps/VioletCity.blk` (`data/maps/blocks.asm:532`) -- Header: `data/maps/maps.asm:251` -> `TILESET_JOHTO`, `TOWN`, - `LANDMARK_VIOLET_CITY`, `MUSIC_VIOLET_CITY`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_POND` -- Dimensions / attributes: `constants/map_constants.asm:230` - (`map_const VIOLET_CITY, 20, 18`) = 40x36 cells; - `data/maps/attributes.asm:127` (`map_attributes VioletCity, VIOLET_CITY, $05`) -- Connections (`data/maps/attributes.asm:127-130`): - south `Route32`/`ROUTE_32` offset 0, west `Route36`/`ROUTE_36` offset 0, - east `Route31`/`ROUTE_31` offset 9. No north connection. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 17 | `VIOLET_MART` | 2 | -| 2 | 18 | 17 | `VIOLET_GYM` | 1 | -| 3 | 30 | 17 | `EARLS_POKEMON_ACADEMY` | 1 | -| 4 | 3 | 15 | `VIOLET_NICKNAME_SPEECH_HOUSE` | 1 | -| 5 | 31 | 25 | `VIOLET_POKECENTER_1F` | 1 | -| 6 | 21 | 29 | `VIOLET_KYLES_HOUSE` | 1 | -| 7 | 23 | 5 | `SPROUT_TOWER_1F` | 1 | -| 8 | 39 | 24 | `ROUTE_31_VIOLET_GATE` | 1 | -| 9 | 39 | 25 | `ROUTE_31_VIOLET_GATE` | 2 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 24 | 20 | `BGEVENT_READ` | `VioletCitySign` | -| 15 | 17 | `BGEVENT_READ` | `VioletGymSign` | -| 24 | 8 | `BGEVENT_READ` | `SproutTowerSign` | -| 27 | 17 | `BGEVENT_READ` | `EarlsPokemonAcademySign` | -| 32 | 25 | `BGEVENT_READ` | `VioletCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 10 | 17 | `BGEVENT_READ` | `VioletCityMartSign` (`jumpstd MartSignScript`) | -| 37 | 14 | `BGEVENT_ITEM` | `VioletCityHiddenHyperPotion` = `hiddenitem HYPER_POTION, EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIOLETCITY_EARL` | `SPRITE_FISHER` | 13 | 16 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityEarlScript` | `EVENT_VIOLET_CITY_EARL` | -| `VIOLETCITY_LASS` | `SPRITE_LASS` | 28 | 28 | `SPRITEMOVEDATA_WANDER`, radius 2/2, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityLassScript` | -1 | -| `VIOLETCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 24 | 14 | `SPRITEMOVEDATA_WANDER`, radius 1/2, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `VioletCitySuperNerdScript` | -1 | -| `VIOLETCITY_GRAMPS` | `SPRITE_GRAMPS` | 17 | 20 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT`, radius 1/0 | `OBJECTTYPE_SCRIPT` | `VioletCityGrampsScript` | -1 | -| `VIOLETCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 5 | 18 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityYoungsterScript` | -1 | -| `VIOLETCITY_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 14 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `VioletCityFruitTree` = `fruittree FRUITTREE_VIOLET_CITY` | -1 | -| `VIOLETCITY_POKE_BALL1` | `SPRITE_POKE_BALL` | 4 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityPPUp` | `EVENT_VIOLET_CITY_PP_UP` | -| `VIOLETCITY_POKE_BALL2` | `SPRITE_POKE_BALL` | 35 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityRareCandy` | `EVENT_VIOLET_CITY_RARE_CANDY` | - -**Scripts of interest** - -- `VioletCityFlypointCallback` (`48:4c6d`), registered as - `callback MAPCALLBACK_NEWMAP`: a single `setflag ENGINE_FLYPOINT_VIOLET` - then `endcallback`. Entering the map at all unlocks Fly to Violet. -- `VioletCityEarlScript`: spins, `faceplayer`, - `writetext Text_EarlAsksIfYouBeatFalkner`, `yesorno`. Answering **yes** is - the `.PointlessJump` arm (one line of text, no state change). Answering **no** - is `.FollowEarl`: `playmusic MUSIC_SHOW_ME_AROUND`, - `follow VIOLETCITY_EARL, PLAYER`, a long - `VioletCityFollowEarl_MovementData` walk to the academy door, - `stopfollow`, `disappear VIOLETCITY_EARL`, - `clearevent EVENT_EARLS_ACADEMY_EARL`. A bot that does not want to be - dragged across the map should answer **yes**, or simply not talk to him. -- The four remaining townsfolk are `jumptextfaceplayer` one-liners. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_VIOLET` (66) | `constants/engine_flags.asm:81` | `VioletCityFlypointCallback` | Fly destination unlocked on first map load | -| `EVENT_VIOLET_CITY_EARL` (1738) | `constants/event_flags.asm:1132` | object-event flag; set by `disappear` in `.FollowEarl` | Clear = Earl still outside | -| `EVENT_VIOLET_CITY_PP_UP` (1603) | `constants/event_flags.asm:996` | object-event flag | Ball taken | -| `EVENT_VIOLET_CITY_RARE_CANDY` (1604) | `constants/event_flags.asm:997` | object-event flag | Ball taken | -| `EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` (176) | `constants/event_flags.asm:186` | `hiddenitem` operand at bg_event (37,14) | Hidden item taken | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `PP_UP` | Poke Ball at (4,1) | `VioletCityPPUp` | `EVENT_VIOLET_CITY_PP_UP` | -| `RARE_CANDY` | Poke Ball at (35,5) | `VioletCityRareCandy` | `EVENT_VIOLET_CITY_RARE_CANDY` | -| `HYPER_POTION` | Hidden, A-press or Itemfinder on (37,14) | `VioletCityHiddenHyperPotion` | `EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` | -| berry | `fruittree FRUITTREE_VIOLET_CITY` at (14,29) | `VioletCityFruitTree` | daily reset, not an `EVENT_*` | - -None of these four are mentioned by the walkthrough section; they are listed -because a bot walking this map will pass them. - -**Trainers** - -None on the overworld map. - -**Wild encounters** - -- Grass: no `def_grass_wildmons VIOLET_CITY` entry in `data/wild/johto_grass.asm`. -- Water (`data/wild/johto_water.asm:225`): rate `2 percent`, L20 Poliwag, - L15 Poliwag, L20 Poliwhirl - Surf only, i.e. not reachable in this section. -- Fishing group `FISHGROUP_POND` (`data/maps/maps.asm:251`), table in - `data/wild/fish.asm`. - ---- - -### MAP_VIOLET_POKECENTER_1F - -- Script: `maps/VioletPokecenter1F.asm` -- Blocks: shared `VioletPokecenter1F_Blocks` label in `data/maps/blocks.asm:351` - (same blockset as the other Pokecenter 1Fs; no dedicated `.blk`) -- Header: `data/maps/maps.asm:256` -> `TILESET_POKECENTER`, `INDOOR`, - `LANDMARK_VIOLET_CITY`, `MUSIC_POKEMON_CENTER`, `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions / attributes: `constants/map_constants.asm:235` - (`map_const VIOLET_POKECENTER_1F, 5, 4`) = 10x8 cells; - `data/maps/attributes.asm:538` -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `VIOLET_CITY` | 5 | -| 2 | 4 | 7 | `VIOLET_CITY` | 5 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events** / **BG events**: both empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIOLETPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenterNurse` (`jumpstd PokecenterNurseScript`) | -1 | -| `VIOLETPOKECENTER1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT`, radius 1/0, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FSuperNerdScript` | -1 | -| `VIOLETPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 1 | 4 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FGentlemanScript` | -1 | -| `VIOLETPOKECENTER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 8 | 1 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FYoungsterScript` | -1 | -| `VIOLETPOKECENTER1F_ELMS_AIDE` | `SPRITE_SCIENTIST` | 4 | 3 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1F_ElmsAideScript` | `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` | - -**Scripts of interest** - -- `VioletPokecenterNurse` -> `jumpstd PokecenterNurseScript`: the only beat the - walkthrough uses here ("go back to the Pokemon Center to heal"). -- `VioletPokecenter1F_ElmsAideScript` is **not** reachable during this section: - `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` is set at new game - (`engine/events/std_scripts.asm:468`), which masks the object, and is only - cleared by Elm's `.assistant` phone call - (`engine/phone/scripts/elm.asm:84`) queued by beating Falkner. It gives - `giveegg TOGEPI, EGG_LEVEL` and belongs to the next section. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` (1792) | `constants/event_flags.asm:1186` | set in `engine/events/std_scripts.asm:468`, cleared in `engine/phone/scripts/elm.asm:84` | Set = aide hidden. Stays set for the whole of this section | - -**Items / Trainers / Wild encounters**: none. - ---- - -### MAP_VIOLET_GYM - -- Script: `maps/VioletGym.asm` -- Blocks: `maps/VioletGym.blk` (`data/maps/blocks.asm:734`) -- Header: `data/maps/maps.asm:253` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`, - `LANDMARK_VIOLET_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions / attributes: `constants/map_constants.asm:232` - (`map_const VIOLET_GYM, 5, 8`) = 10x16 cells; - `data/maps/attributes.asm:535` -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 15 | `VIOLET_CITY` | 2 | -| 2 | 5 | 15 | `VIOLET_CITY` | 2 | - -**Coord events** (`def_coord_events`) - -None. There is no scripted trip-wire in the gym; Abe and Rod fire off eyesight -alone. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 13 | `BGEVENT_READ` | `VioletGymStatue` | -| 6 | 13 | `BGEVENT_READ` | `VioletGymStatue` | - -`VioletGymStatue` branches on `checkflag ENGINE_ZEPHYRBADGE`: unbeaten -> -`jumpstd GymStatue1Script`; beaten -> `gettrainername STRING_BUFFER_4, FALKNER, -FALKNER1` + `jumpstd GymStatue2Script`. Reading it is a cheap way for a bot to -confirm the badge landed. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIOLETGYM_FALKNER` | `SPRITE_FALKNER` | 5 | 1 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `VioletGymFalknerScript` | -1 | -| `VIOLETGYM_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 7 | 6 | `SPRITEMOVEDATA_STANDING_LEFT`, radius 2/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperRod` | -1 | -| `VIOLETGYM_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 2 | 10 | `SPRITEMOVEDATA_STANDING_RIGHT`, radius 2/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperAbe` | -1 | -| `VIOLETGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, sight 0 | `VioletGymGuideScript` | -1 | - -Walking in at (4,15)/(5,15) and heading north, **Abe** (facing right at y=10) is -met before **Rod** (facing left at y=6), which is the order the walkthrough -gives even though it names Abe first and Rod second. - -**Scripts of interest** - -- `VioletGymFalknerScript` (`56:412f`): - `faceplayer`, `opentext`, `checkevent EVENT_BEAT_FALKNER` / - `iftrue .FightDone`. First pass: `writetext FalknerIntroText`, - `winlosstext FalknerWinLossText, 0`, `loadtrainer FALKNER, FALKNER1`, - `startbattle`, `reloadmapafterbattle`, `setevent EVENT_BEAT_FALKNER`, - `writetext ReceivedZephyrBadgeText`, `playsound SFX_GET_BADGE`, `waitsfx`, - **`setflag ENGINE_ZEPHYRBADGE`**, `readvar VAR_BADGES`, - `scall VioletGymActivateRockets`. - Then `.FightDone` (`56:4159`): `checkevent EVENT_GOT_TM31_MUD_SLAP` / - `iftrue .SpeechAfterTM`; otherwise - `setevent EVENT_BEAT_BIRD_KEEPER_ROD`, `setevent EVENT_BEAT_BIRD_KEEPER_ABE` - (the two gym trainers are force-flagged so they never re-trigger), - `setmapscene ELMS_LAB, SCENE_ELMSLAB_NOOP`, - `specialphonecall SPECIALCALL_ASSISTANT`, - `writetext FalknerZephyrBadgeText`, `promptbutton`, - `verbosegiveitem TM_MUD_SLAP`, `iffalse .NoRoomForMudSlap`, - `setevent EVENT_GOT_TM31_MUD_SLAP`, `writetext FalknerTMMudSlapText`. - Unlike Sage Li, this one **does** check `verbosegiveitem`'s return, so a full - bag leaves `EVENT_GOT_TM31_MUD_SLAP` clear and the TM is offered again on the - next talk. -- `VioletGymActivateRockets` (`56:4185`): `ifequal 7, .RadioTowerRockets`, - `ifequal 6, .GoldenrodRockets` against the `VAR_BADGES` count. With one badge - this is a no-op; it exists so that a late Violet clear still arms the Rocket - arcs. -- `TrainerBirdKeeperRod` (`56:4194`), `TrainerBirdKeeperAbe` (`56:41a8`): - standard `trainer` headers + `endifjustbattled` after-battle text. -- `VioletGymGuideScript`: branches on `EVENT_BEAT_FALKNER`; pure text. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_FALKNER` (1213) | `constants/event_flags.asm:706` | read+written by `VioletGymFalknerScript`; read by `VioletGymGuideScript` | The gym-cleared flag | -| `ENGINE_ZEPHYRBADGE` (26) | `constants/engine_flags.asm:38` | `setflag` in `VioletGymFalknerScript`; `checkflag` in `VioletGymStatue`; `CheckBadge` in `engine/events/overworld.asm` | The badge bit. Gates out-of-battle Flash and the Gen 2 attack boost (`engine/battle/core.asm:6566`) | -| `EVENT_GOT_TM31_MUD_SLAP` (8) | `constants/event_flags.asm:14` | `VioletGymFalknerScript` | TM31 collected | -| `EVENT_BEAT_BIRD_KEEPER_ROD` (1019) | `constants/event_flags.asm:494` | `TrainerBirdKeeperRod`; also force-set by `VioletGymFalknerScript.FightDone` | Beaten | -| `EVENT_BEAT_BIRD_KEEPER_ABE` (1020) | `constants/event_flags.asm:495` | `TrainerBirdKeeperAbe`; also force-set by `.FightDone` | Beaten | -| `SCENE_ELMSLAB_NOOP` | `maps/ElmsLab.asm` (macro-generated) | `setmapscene ELMS_LAB, ...` in `.FightDone` | Disarms the lab's remaining scene script | -| `SPECIALCALL_ASSISTANT` | `constants/phone_constants.asm:47` | `specialphonecall` in `.FightDone`; consumed by `ElmPhoneCallerScript` (`engine/phone/scripts/elm.asm:67`) | Queues the Togepi-egg phone call | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| ZEPHYRBADGE | Automatic on beating Falkner | `VioletGymFalknerScript` (`setflag ENGINE_ZEPHYRBADGE`) | `ENGINE_ZEPHYRBADGE` | -| `TM_MUD_SLAP` (TM31) | `verbosegiveitem` after the badge text | `VioletGymFalknerScript.FightDone` | `EVENT_GOT_TM31_MUD_SLAP` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `BIRD_KEEPER`, `ROD` | `BIRD_KEEPER` (18) | `ROD` (1) | `BirdKeeperGroup` "ROD" (`0e:5f05` group base): L7 Pidgey, L7 Pidgey, `TRAINERTYPE_NORMAL` | `TrainerBirdKeeperRod` | none | -| `BIRD_KEEPER`, `ABE` | `BIRD_KEEPER` | `ABE` (2) | `BirdKeeperGroup` "ABE": L9 Spearow, `TRAINERTYPE_NORMAL` | `TrainerBirdKeeperAbe` | none | -| `FALKNER`, `FALKNER1` | `FALKNER` (1) | `FALKNER1` (1) | `FalknerGroup` (`0e:59c2`), `TRAINERTYPE_MOVES`: L7 Pidgey (TACKLE, MUD_SLAP), L9 Pidgeotto (TACKLE, MUD_SLAP, GUST) | `VioletGymFalknerScript` (`loadtrainer`) | listed in `data/trainers/leaders.asm:8` | - -Prize money: Bird Keeper base 6 (`data/trainers/attributes.asm`) -> Rod -6 x 7 x 4 = 168, Abe 6 x 9 x 4 = 216. Falkner base 25 -> 25 x 9 x 4 = 900. -Falkner DVs `dn 9, 10, 7, 7` (`data/trainers/dvs.asm:5`); Bird Keeper -`dn 9, 8, 8, 8` (line 28). - -**Wild encounters** - -None (indoor gym). - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Sprout Tower 3F rival cutscene fires before you can reach Sage Li | `maps/SproutTower3F.asm` `coord_event 11, 9, SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER, SproutTower3FRivalScene` | Standing on (11,9) while `wSproutTower3FSceneID` == 0 | The scene's own `setscene SCENE_SPROUTTOWER3F_NOOP`. Not a hard block - the tile is on the only path north | -| HM05 Flash | `maps/SproutTower3F.asm:SageLiScript` | Beat `SAGE, LI` (`loadtrainer` + `startbattle`) | `verbosegiveitem HM_FLASH` + `setevent EVENT_GOT_HM05_FLASH` | -| Using Flash outside battle | `engine/events/overworld.asm` `FlashFunction.CheckUseFlash` (`03:48f1`): `ld de, ENGINE_ZEPHYRBADGE` / `farcall CheckBadge` / `jr c, .nozephyrbadge` | `ENGINE_ZEPHYRBADGE` set **and** `wTimeOfDayPalset == DARKNESS_PALSET` | Beat Falkner. Note the second half: even with the badge, Flash is refused on any map that is not a dark cave (`.notadarkcave` -> `FieldMoveFailed`) | -| ZEPHYRBADGE attack boost / obedience | `engine/battle/core.asm:6566` (the `and` mask that starts `(1 << ZEPHYRBADGE)`) | badge bit | Beat Falkner | -| Gym trainers Abe and Rod | `maps/VioletGym.asm` object events, `OBJECTTYPE_TRAINER` sight 3 on shared rows | eyesight only; they do **not** physically block the aisle | Beating them, or `EVENT_BEAT_BIRD_KEEPER_*` being force-set by `VioletGymFalknerScript.FightDone` | -| Falkner re-battle | `maps/VioletGym.asm:VioletGymFalknerScript` `checkevent EVENT_BEAT_FALKNER` / `iftrue .FightDone` | none | one-shot | -| Sage Li re-battle | `maps/SproutTower3F.asm:SageLiScript` `checkevent EVENT_GOT_HM05_FLASH` / `iftrue .GotFlash` | none | one-shot, keyed on the HM flag rather than on `EVENT_BEAT_SAGE_LI` | -| Sprout Tower is a `DUNGEON` | `data/maps/maps.asm:79-81` | - | `ESCAPE_ROPE` and Dig work here, which is what the walkthrough's "you may use that to get out of the tower" relies on | - -Nothing in this section is gated on a field move, a key item, or an NPC standing -on a tile. The only hard requirement is winning battles. - -## 4. Bot checklist - -Coordinates are asm map cells. Port driver form: -`world:setMap("<MAP_CONST>", x, y, facing)`, as in -`tests/drivers/gold_walk_smoke.lua:58`. - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `SPROUT_TOWER_1F` | warp 1 (9,15) | enter from `VIOLET_CITY` (23,5) | - | on 1F | -| 2 | `SPROUT_TOWER_1F` | (6,4) | walk onto warp 3 | - | on `SPROUT_TOWER_2F` at (6,4) | -| 3 | `SPROUT_TOWER_2F` | `SPROUTTOWER2F_SAGE1` at (14,4), sight 3 wide facing spin | step into his line, battle | `EVENT_BEAT_SAGE_NICO` clear | `EVENT_BEAT_SAGE_NICO` set | -| 4 | `SPROUT_TOWER_2F` | (17,3) | walk onto warp 3 | - | on `SPROUT_TOWER_1F` at (17,3) | -| 5 | `SPROUT_TOWER_1F` | ball at (16,7) | face + A | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` clear | `PARLYZ_HEAL` in bag, flag set | -| 6 | `SPROUT_TOWER_1F` | `SPROUTTOWER1F_SAGE3` at (2,1), facing RIGHT, sight 1 | step to (3,1), battle | `EVENT_BEAT_SAGE_CHOW` clear | `EVENT_BEAT_SAGE_CHOW` set | -| 7 | `SPROUT_TOWER_1F` | (2,6) | walk onto warp 4 | - | on `SPROUT_TOWER_2F` at (2,6) | -| 8 | `SPROUT_TOWER_2F` | ball at (3,1) | face + A | `EVENT_SPROUT_TOWER_2F_X_DEFEND` clear | `X_DEFEND` in bag, flag set | -| 9 | `SPROUT_TOWER_2F` | `SPROUTTOWER2F_SAGE2` at (3,15), facing UP, sight 4 | approach from above, battle | `EVENT_BEAT_SAGE_EDMOND` clear | `EVENT_BEAT_SAGE_EDMOND` set | -| 10 | `SPROUT_TOWER_2F` | (10,14) | walk onto warp 4 | - | on `SPROUT_TOWER_3F` at (10,14) | -| 11 | `SPROUT_TOWER_3F` | ball at (6,14) | face + A | `EVENT_SPROUT_TOWER_3F_POTION` clear | `POTION` in bag, flag set | -| 12 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE1` at (8,13), facing RIGHT, sight 3 | battle | `EVENT_BEAT_SAGE_JIN` clear | `EVENT_BEAT_SAGE_JIN` set | -| 13 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE4` at (11,11), facing LEFT, sight 3 | battle | `EVENT_BEAT_SAGE_NEAL` clear | `EVENT_BEAT_SAGE_NEAL` set | -| 14 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE2` at (8,8), facing DOWN, sight 2 | battle | `EVENT_BEAT_SAGE_TROY` clear | `EVENT_BEAT_SAGE_TROY` set | -| 15 | `SPROUT_TOWER_3F` | (11,9) | walk onto the coord event | scene id == `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` (0) | cutscene runs; player ends at (11,5); `EVENT_RIVAL_SPROUT_TOWER` set; scene id -> 1 | -| 16 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE3` at (10,2) | face + A, battle `SAGE, LI` | `EVENT_GOT_HM05_FLASH` clear; **bag must have room for an HM** | `HM_FLASH`, `EVENT_GOT_HM05_FLASH`, `EVENT_BEAT_SAGE_LI` | -| 17 | `SPROUT_TOWER_3F` | ball at (14,1) | face + A | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` clear | `ESCAPE_ROPE` in bag, flag set | -| 18 | `SPROUT_TOWER_3F` | (10,14) | warp 1, then 2F warp 1/2/3 down to 1F, then 1F warp 1 (9,15) | - | back on `VIOLET_CITY` at (23,5) | -| 19 | `VIOLET_CITY` | (31,25) | walk onto warp 5 | - | on `VIOLET_POKECENTER_1F` at (3,7) | -| 20 | `VIOLET_POKECENTER_1F` | `VIOLETPOKECENTER1F_NURSE` at (3,1) | face + A, accept heal | - | party healed | -| 21 | `VIOLET_POKECENTER_1F` | (3,7) | warp 1 | - | back on `VIOLET_CITY` at (31,25) | -| 22 | `VIOLET_CITY` | (18,17) | walk onto warp 2 | - | on `VIOLET_GYM` at (4,15) | -| 23 | `VIOLET_GYM` | `VIOLETGYM_YOUNGSTER2` at (2,10), facing RIGHT, sight 3 | enter his row, battle Abe | `EVENT_BEAT_BIRD_KEEPER_ABE` clear | flag set | -| 24 | `VIOLET_GYM` | `VIOLETGYM_YOUNGSTER1` at (7,6), facing LEFT, sight 3 | enter his row, battle Rod | `EVENT_BEAT_BIRD_KEEPER_ROD` clear | flag set | -| 25 | `VIOLET_GYM` | (4,15) -> `VIOLET_CITY` -> (31,25) PC | optional re-heal before the leader | - | party healed | -| 26 | `VIOLET_GYM` | `VIOLETGYM_FALKNER` at (5,1) | face + A, battle | `EVENT_BEAT_FALKNER` clear | `EVENT_BEAT_FALKNER`, `ENGINE_ZEPHYRBADGE`, Rod/Abe flags force-set, `SCENE_ELMSLAB_NOOP`, `SPECIALCALL_ASSISTANT` queued | -| 27 | `VIOLET_GYM` | same NPC, second talk if bag was full | face + A | `EVENT_GOT_TM31_MUD_SLAP` clear | `TM_MUD_SLAP`, `EVENT_GOT_TM31_MUD_SLAP` | -| 28 | `VIOLET_GYM` | bg_event (3,13) or (6,13) | face + A (verification only) | - | `GymStatue2Script` text = badge confirmed | - -Optional pickups on the way through, none of which the walkthrough mentions: -`VIOLET_CITY` PP Up (4,1), Rare Candy (35,5), hidden Hyper Potion (37,14), -berry tree (14,29). - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map load, block grid, warps for all six maps (data-driven from the ROM) | `src/world/gen2/Map.lua`, `src/import/RomExtractorGen2.lua` (`readMapGroupEntry`, `mapNameByIds`) | implemented - ids are the same `MAP_*` names, see `tools/rom_manifest_gold.json` -> `constants.mapGroups` | -| `MAPCALLBACK_NEWMAP` -> `setflag ENGINE_FLYPOINT_VIOLET` | `src/world/gen2/World.lua` (callback dispatch), driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| Fly point 66 = Violet | `src/world/gen2/FieldMoves.lua:346` (`{ landmark = "LANDMARK_VIOLET_CITY", spawn = "SPAWN_VIOLET", flag = 66 }`) | implemented | -| Script VM for every `maps/*.asm` script in this section | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented - scripts are disassembled from the ROM, not hand-ported, so `SageLiScript` / `VioletGymFalknerScript` run as-is | -| `coord_event` + scene ids (the 3F rival trip-wire) | `src/world/gen2/World.lua:5013` (coord event scan), `:1183` / `:5026` (scene scripts) | implemented | -| Cutscene opcodes used by `SproutTower3FRivalScene` (`showemote`, `earthquake`, `applymovement`, `turnobject`, `disappear`, `setscene`, `playmusic`) | `src/script/gen2/Opcodes.lua:122/125`, `src/world/gen2/World.lua:1006/1528/647`, `src/script/gen2/Movement.lua` | implemented | -| `special FadeOutMusic` / `FadeOutToBlack` / `ReloadSpritesNoPalettes` / `FadeInFromBlack` / `RestartMapMusic` | `src/script/gen2/Specials.lua` | implemented | -| Object masking by event flag (rival, item balls) | `src/world/gen2/Events.lua` (`objectVisible`), documented against `CheckObjectFlag` | implemented | -| Trainer objects: eyesight, approach walk, seen text, battle, beat flag | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua:5205-5253`, driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| `loadtrainer` / `startbattle` for Sage Li and Falkner | `src/script/gen2/Vm.lua`, `src/battle/gen2/Battle.lua` | implemented | -| Gym leader battle music | `src/battle/gen2/BattleMusic.lua:24` (`FALKNER = true`) | implemented | -| Prize money (Falkner 900 = 25 x 9 x 4) | `src/battle/gen2/Prize.lua`, `tests/gen2_prize_test.lua` | implemented | -| `setflag ENGINE_ZEPHYRBADGE` (id 26) and `readvar VAR_BADGES` | `src/script/gen2/Vm.lua:670`, `src/world/gen2/World.lua:117/1240`, `tests/gen2_world_test.lua:1783` | implemented | -| Flash badge gate (`FlashFunction.CheckUseFlash`) | `src/world/gen2/FieldMoves.lua:105` (`FLASH = "ZEPHYR"`), `tests/gen2_world_test.lua:755-781` | implemented, including the "refused before the darkness check" ordering | -| `verbosegiveitem` (HM05, TM31) incl. the `iffalse` no-room arm | `src/script/gen2/Vm.lua:490-500` | implemented | -| Hidden item at Violet City (37,14) | `src/world/gen2/HiddenItems.lua`, `src/world/gen2/World.lua:5285` | implemented | -| Berry tree `fruittree FRUITTREE_VIOLET_CITY` | `src/script/gen2/CallAsm.lua:75-77`, `src/core/gen2/Apricorns.lua:363` | implemented | -| `specialphonecall SPECIALCALL_ASSISTANT` | `src/script/gen2/Opcodes.lua:161`, `src/core/gen2/Phone.lua:390` | implemented | -| `giveegg TOGEPI` (Elm's aide, next section's payoff) | `src/script/gen2/Vm.lua:453`, driver `tests/drivers/gold_egg_hatch.lua` | implemented | -| Earl's `follow` / `stopfollow` escort | `src/script/gen2/Vm.lua:982` | implemented | -| **Poke Ball item pickup (`OBJECTTYPE_ITEMBALL`)** - Parlyz Heal, X Defend, Potion, Escape Rope, PP Up, Rare Candy | `src/import/RomExtractorGen2.lua:2969` writes `obj.itemball`; **nothing in `src/` reads it**, and `World:interact()` (`src/world/gen2/World.lua:5257`) has no itemball branch (trainer / strength boulder / `scriptKey` / bg sign / hidden item only). `src/script/gen2/CallAsm.lua:550` stubs `TryReceiveItem` out on purpose | **missing** - the balls render and are masked correctly, but pressing A on one does nothing. Every item ball in this section is unobtainable in the port today | -| Hand-ported per-map Lua for Sprout Tower / Violet Gym | none - by design | n/a (the port runs the ROM's own bytecode) | - -## 6. Unresolved / verify by hand - -1. **"X Accuracy" in the Sprout Tower item list.** The walkthrough's header - lists Escape Rope, HM05, Parlyz Heal, Potion, **X Accuracy**, and the prose - says "grab that X Defense above you". The asm has exactly one such ball, - `SproutTower2FXDefend` = `itemball X_DEFEND` at 2F (3,1). There is no - `X_ACCURACY` anywhere in `maps/SproutTower*.asm`. The header list is wrong; - the prose is right. - -2. **"Go up to the item to the right of the monk. In reality, it is a... - Level 3 Rattata"** and the follow-up "Go back to the Rattata you faced - before, and now it's an Escape Rope." Nothing in `maps/SproutTower3F.asm` - supports this. The object at (14,1) is a plain - `object_event ... OBJECTTYPE_ITEMBALL, 0, SproutTower3FEscapeRope, - EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` whose script label is a bare - `itemball ESCAPE_ROPE`. There is no `wildbattle`, no `loadwildmon`, no - second object at that cell, and no branch on `EVENT_GOT_HM05_FLASH` anywhere - near it. This looks like the walkthrough importing the Gen 1 Rocket Hideout - Voltorb trick. **Treat the ball at (14,1) as an ordinary Escape Rope.** - -3. **"Pokemon found in Sprout Tower: Rattata, Bellsprout, Gastly."** - `data/wild/johto_grass.asm` gives Rattata (morn/day) and Gastly + Rattata - (nite) for `SPROUT_TOWER_2F` and `SPROUT_TOWER_3F`, and **no table at all** - for `SPROUT_TOWER_1F`. Bellsprout is not a Sprout Tower wild encounter; the - Bellsprouts are the sages' party members. - -4. **Sage Li's party order.** The walkthrough lists L7 Bellsprout, L10 Hoothoot, - L7 Bellsprout. `data/trainers/parties.asm` `SageGroup` "LI" is - `db 7, BELLSPROUT / db 7, BELLSPROUT / db 10, HOOTHOOT` - the Hoothoot is - last, not second. The 320G figure the walkthrough quotes only works with the - asm order, since `ComputeTrainerReward` uses the level of the **last** party - row (8 x 10 x 4 = 320). - -5. **Walkthrough trainer order in the gym.** The text says "Head up to fight - your trainer battles first. Bird Keeper Abe ... Head up the path to the - second trainer. Bird Keeper Rod." Geometrically that is correct (Abe at - y=10 is south of Rod at y=6), but be aware the *object const* order in the - file is Rod first (`VIOLETGYM_YOUNGSTER1`), Abe second - (`VIOLETGYM_YOUNGSTER2`), which is the opposite of the walk order. Do not - index trainers by object const when following the prose. - -6. **"You'll need the 1st badge to use it [Flash]".** True but incomplete: - `FlashFunction.CheckUseFlash` (`engine/events/overworld.asm`, `03:48f1`) - requires `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET`. - With the badge, on any non-dark map, the move still fails through - `.notadarkcave` -> `FieldMoveFailed`. A bot should not treat "have badge" as - "Flash will work here". - -7. **EXP values.** The walkthrough quotes per-Pokemon EXP figures (54, 108, 126, - 217, ...). Those are computed from base experience and level at runtime - (`engine/battle/experience.asm` / `data/pokemon/base_stats/`), not stored - anywhere as a per-trainer constant, so they were not verified against a - single asm line here. The **money** figures were: all seven quoted values - match `base reward x last party level x 4`. - -8. **"you should be around an hour into the game now"** and the video link - - prose, nothing to resolve. diff --git a/docs/gold-walkthrough/asm-walk/section-03-ruins-of-alph-and-union-cave.md b/docs/gold-walkthrough/asm-walk/section-03-ruins-of-alph-and-union-cave.md deleted file mode 100644 index 43de9070..00000000 --- a/docs/gold-walkthrough/asm-walk/section-03-ruins-of-alph-and-union-cave.md +++ /dev/null @@ -1,1280 +0,0 @@ -# Section 03 - Ruins of Alph and Union Cave - -Source: `../section-03-ruins-of-alph-and-union-cave.txt` -Maps covered: `MAP_VIOLET_POKECENTER_1F`, `MAP_ROUTE_36_RUINS_OF_ALPH_GATE`, -`MAP_RUINS_OF_ALPH_OUTSIDE`, `MAP_RUINS_OF_ALPH_KABUTO_CHAMBER`, -`MAP_RUINS_OF_ALPH_INNER_CHAMBER`, `MAP_RUINS_OF_ALPH_RESEARCH_CENTER`, -`MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER` (Surf-gated), `MAP_ROUTE_32_RUINS_OF_ALPH_GATE`, -`MAP_ROUTE_32`, `MAP_ROUTE_32_POKECENTER_1F`, `MAP_UNION_CAVE_1F`, -`MAP_UNION_CAVE_B1F`, `MAP_UNION_CAVE_B2F` (Strength/Surf-gated), `MAP_ROUTE_33` - -Badges / key milestones in this section: - -- Togepi EGG from Elm's Aide (`EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE`) - this is the - real unlock for the section: it flips Route 32's scene off the blocking one. -- First Unown puzzle solved (`EVENT_SOLVED_KABUTO_PUZZLE`, - `ENGINE_UNLOCKED_UNOWNS_A_TO_K`) - makes Unown spawnable at all. -- UNOWN #DEX mode (`ENGINE_UNOWN_DEX`) from the Research Center. -- MIRACLE_SEED, TM05 ROAR, OLD_ROD, POISON_BARB (Friday), TM39 SWIFT. -- No badge is earned in this stretch. The section ends walking west out of - Route 33 into Azalea Town. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | leaving Violet Gym (warp 2 at 18,17) | warp 5 at `31, 25` -> `VIOLET_POKECENTER_1F` 1 | Elm phones about "something important at the Pokemon Center" | -| 2 | `MAP_VIOLET_POKECENTER_1F` | `maps/VioletPokecenter1F.asm` | warp 1/2 at `3, 7` / `4, 7` | same warps back to `VIOLET_CITY` 5 | take the Togepi EGG from Elm's Aide | -| 3 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | back out of the Center | **west connection** -> `Route36` (offset 0) | "head west ... until you enter Route 36" | -| 4 | `MAP_ROUTE_36` | `maps/Route36.asm` | Violet City west connection | warp 3/4 at `47, 13` / `48, 13` -> `ROUTE_36_RUINS_OF_ALPH_GATE` 1/2 | pass through; the Sudowoodo tree at `35, 9` is a later section | -| 5 | `MAP_ROUTE_36_RUINS_OF_ALPH_GATE` | `maps/Route36RuinsOfAlphGate.asm` | warp 1/2 at `4, 0` / `5, 0` | warp 3/4 at `4, 7` / `5, 7` -> `RUINS_OF_ALPH_OUTSIDE` 9 | "the route-changing station" | -| 6 | `MAP_RUINS_OF_ALPH_OUTSIDE` | `maps/RuinsOfAlphOutside.asm` | warp 9 at `7, 5` | warp 2 at `14, 7` -> `RUINS_OF_ALPH_KABUTO_CHAMBER` 1 | "enter the first cave on the right" | -| 7 | `MAP_RUINS_OF_ALPH_KABUTO_CHAMBER` | `maps/RuinsOfAlphKabutoChamber.asm` | warp 1/2 at `3, 9` / `4, 9` | warp 3/4 at `3, 3` / `4, 3` -> `RUINS_OF_ALPH_INNER_CHAMBER` 4/5 (the floor holes) | solve the Kabuto puzzle, fall through | -| 8 | `MAP_RUINS_OF_ALPH_INNER_CHAMBER` | `maps/RuinsOfAlphInnerChamber.asm` | warps 4/5 at `15, 3` / `16, 3` | warp 1 at `10, 13` -> `RUINS_OF_ALPH_OUTSIDE` 5 | catch Unown; "the lower passageway eventually leads up to a ladder" | -| 9 | `MAP_RUINS_OF_ALPH_OUTSIDE` | `maps/RuinsOfAlphOutside.asm` | warp 5 at `10, 13` | coord_event at `11, 14` or `10, 15` -> scientist cutscene -> warp 6 at `17, 11` | 3+ Unown letters caught -> assistant walks you to the lab | -| 10 | `MAP_RUINS_OF_ALPH_RESEARCH_CENTER` | `maps/RuinsOfAlphResearchCenter.asm` | warp 1/2 at `2, 7` / `3, 7` | same warps -> `RUINS_OF_ALPH_OUTSIDE` 6 | UNOWN #DEX upgrade (`ENGINE_UNOWN_DEX`) | -| 11 | `MAP_RUINS_OF_ALPH_OUTSIDE` | `maps/RuinsOfAlphOutside.asm` | warp 6 at `17, 11` | warp 10/11 at `13, 20` / `13, 21` -> `ROUTE_32_RUINS_OF_ALPH_GATE` 1/2 | "go south, and then east ... another route changing house" | -| 12 | `MAP_ROUTE_32_RUINS_OF_ALPH_GATE` | `maps/Route32RuinsOfAlphGate.asm` | warp 1/2 at `0, 4` / `0, 5` | warp 3/4 at `9, 4` / `9, 5` -> `ROUTE_32` 2/3 | "Welcome to Route 32" | -| 13 | `MAP_ROUTE_32` | `maps/Route32.asm` | warp 2/3 at `4, 2` / `4, 3` | warp 4 at `6, 79` -> `UNION_CAVE_1F` 4 | the whole trainer gauntlet; detours north into Violet City (connection) for the PRZCureBerry tree and to warp 1 at `11, 73` for the Pokemon Center | -| 14 | `MAP_ROUTE_32_POKECENTER_1F` | `maps/Route32Pokecenter1F.asm` | warp 1/2 at `3, 7` / `4, 7` | same warps -> `ROUTE_32` 1 | heal; Fishing Guru hands over the OLD_ROD | -| 15 | `MAP_UNION_CAVE_1F` | `maps/UnionCave1F.asm` | warp 4 at `17, 3` | warp 1 at `5, 19` -> `UNION_CAVE_B1F` 3 | "go up and down the ladder" | -| 16 | `MAP_UNION_CAVE_B1F` | `maps/UnionCaveB1F.asm` | warp 3 at `7, 19` | warp 4 at `3, 33` -> `UNION_CAVE_1F` 2 | TM39 SWIFT at `2, 16`, X DEFEND at `17, 23` | -| 17 | `MAP_UNION_CAVE_1F` | `maps/UnionCave1F.asm` | warp 2 at `3, 33` | warp 3 at `17, 31` -> `ROUTE_33` 1 | Awakening at `12, 33`, Firebreather Ray at `16, 31`, out the east door | -| 18 | `MAP_ROUTE_33` | `maps/Route33.asm` | warp 1 at `11, 9` | **west connection** -> `AzaleaTown` (offset 0) | PSNCureBerry tree, Hiker Anthony, then into Azalea Town | - -Deferred re-visits the walkthrough flags but does not do yet: - -- `RUINS_OF_ALPH_AERODACTYL_CHAMBER` (outside warp 4 at `16, 33`) needs Surf across - the outside water. -- `UNION_CAVE_B2F` (B1F warp 5 at `17, 31`) needs the Strength boulder at B1F - `7, 10` and gives the Friday-only Lapras. -- `UNION_CAVE_B1F` warps 1/2 at `3, 3` / `3, 11` lead back out to - `RUINS_OF_ALPH_OUTSIDE` warps 7/8 (`6, 19`, `6, 27`) - "the entrance to more of - the Ruins of Alph" the walkthrough mentions. - -Spill into the next section: Route 33's west connection lands in Azalea Town; stop -there. - ---- - -## 2. Maps - -### MAP_VIOLET_POKECENTER_1F - -Only the EGG beat is in scope here; Violet City itself belongs to section 02. - -- Script: `maps/VioletPokecenter1F.asm` -- Header: `data/maps/maps.asm:256` -> `TILESET_POKECENTER, INDOOR, - LANDMARK_VIOLET_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm` group `VIOLET` (10), id 6, `5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `VIOLET_CITY` | 5 | -| 2 | 4 | 7 | `VIOLET_CITY` | 5 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIOLETPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenterNurse` | -1 | -| `VIOLETPOKECENTER1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 7 | 6 | `WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FSuperNerdScript` | -1 | -| `VIOLETPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 1 | 4 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FGentlemanScript` | -1 | -| `VIOLETPOKECENTER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 8 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FYoungsterScript` | -1 | -| `VIOLETPOKECENTER1F_ELMS_AIDE` | `SPRITE_SCIENTIST` | 4 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1F_ElmsAideScript` | `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` | - -**Scripts of interest** - -- `VioletPokecenter1F_ElmsAideScript` - `faceplayer` / `opentext`; if - `EVENT_REFUSED_TO_TAKE_EGG_FROM_ELMS_AIDE` it re-asks with a different text. - `yesorno`; on yes it reads `VAR_PARTYCOUNT` and bails to `.PartyFull` at - `PARTY_LENGTH`, otherwise `giveegg TOGEPI, EGG_LEVEL`, then - `setevent EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE`, - `clearevent EVENT_ELMS_AIDE_IN_LAB`, `clearevent EVENT_TOGEPI_HATCHED`, and - crucially `setmapscene ROUTE_32, SCENE_ROUTE32_OFFER_SLOWPOKETAIL`. It then - branches on `VAR_FACING` (`UP` -> walk around the player) and `disappear`s. -- The aide only exists because `engine/phone/scripts/elm.asm:84` (`ElmPhoneCallerScript` - `.assistant`) does `clearevent EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` / - `setevent EVENT_ELMS_AIDE_IN_LAB`. That call is armed by - `maps/VioletGym.asm:38` `specialphonecall SPECIALCALL_ASSISTANT` after Falkner. - `engine/events/std_scripts.asm:468` sets `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` - at new game, and a set object event flag means the object is hidden - (`Script_appear` clears, `Script_disappear` sets - `engine/overworld/scripting.asm:879-898`). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` | `constants/event_flags.asm:1186` | set by `InitializeEventsScript`, cleared by Elm's `.assistant` phone call | aide object visible only while CLEAR | -| `EVENT_REFUSED_TO_TAKE_EGG_FROM_ELMS_AIDE` | `constants/event_flags.asm:53` | `VioletPokecenter1F_ElmsAideScript` | second-ask text | -| `EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE` | `constants/event_flags.asm:54` | set here; read by `Route32CooltrainerMScript` | precondition for the Miracle Seed | -| `EVENT_TOGEPI_HATCHED` | `constants/event_flags.asm:93` | cleared here | egg-hatch bookkeeping | -| `SPECIALCALL_ASSISTANT` | `constants/phone_constants.asm:47` | `maps/VioletGym.asm:38` | the phone call that spawns the aide | - ---- - -### MAP_ROUTE_36_RUINS_OF_ALPH_GATE - -- Script: `maps/Route36RuinsOfAlphGate.asm` -- Header: `data/maps/maps.asm:262` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_36, - MUSIC_ROUTE_36, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm` group `VIOLET` (10), id 16, `5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_36` | 3 | -| 2 | 5 | 0 | `ROUTE_36` | 4 | -| 3 | 4 | 7 | `RUINS_OF_ALPH_OUTSIDE` | 9 | -| 4 | 5 | 7 | `RUINS_OF_ALPH_OUTSIDE` | 9 | - -**BG events**: none. **Coord events**: none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE36RUINSOFALPHGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route36RuinsOfAlphGateOfficerScript` | -1 | -| `ROUTE36RUINSOFALPHGATE_GRAMPS` | `SPRITE_GRAMPS` | 7 | 5 | `WANDER` (r 1,2) | `OBJECTTYPE_SCRIPT` | `Route36RuinsOfAlphGateGrampsScript` | -1 | - -Both are `jumptextfaceplayer` only - no flags, no gate. The gate is walk-through. - ---- - -### MAP_RUINS_OF_ALPH_OUTSIDE - -- Script: `maps/RuinsOfAlphOutside.asm` -- Blocks: `maps/RuinsOfAlphOutside.blk` -- Header: `data/maps/maps.asm:100` -> `TILESET_JOHTO, ROUTE, LANDMARK_RUINS_OF_ALPH, - MUSIC_UNION_CAVE, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:87` group `DUNGEONS` (3), id 22, - `map_const RUINS_OF_ALPH_OUTSIDE, 10, 18` (10x18 blocks = 20x36 cells) -- Connections: none (`data/maps/attributes.asm` `map_attributes RuinsOfAlphOutside, - RUINS_OF_ALPH_OUTSIDE, $05` has no `connection` rows). Every exit is a warp. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 17 | `RUINS_OF_ALPH_HO_OH_CHAMBER` | 1 | -| 2 | 14 | 7 | `RUINS_OF_ALPH_KABUTO_CHAMBER` | 1 | -| 3 | 2 | 29 | `RUINS_OF_ALPH_OMANYTE_CHAMBER` | 1 | -| 4 | 16 | 33 | `RUINS_OF_ALPH_AERODACTYL_CHAMBER` | 1 | -| 5 | 10 | 13 | `RUINS_OF_ALPH_INNER_CHAMBER` | 1 | -| 6 | 17 | 11 | `RUINS_OF_ALPH_RESEARCH_CENTER` | 1 | -| 7 | 6 | 19 | `UNION_CAVE_B1F` | 1 | -| 8 | 6 | 27 | `UNION_CAVE_B1F` | 2 | -| 9 | 7 | 5 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 3 | -| 10 | 13 | 20 | `ROUTE_32_RUINS_OF_ALPH_GATE` | 1 | -| 11 | 13 | 21 | `ROUTE_32_RUINS_OF_ALPH_GATE` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_RUINSOFALPHOUTSIDE_GET_UNOWN_DEX` (1) | 11 | 14 | `RuinsOfAlphOutsideScientistScene1` | scientist turns UP, player DOWN, then the shared cutscene | -| `SCENE_RUINSOFALPHOUTSIDE_GET_UNOWN_DEX` (1) | 10 | 15 | `RuinsOfAlphOutsideScientistScene2` | scientist turns LEFT, player RIGHT, then the shared cutscene | - -Scene ids are declared inline by `scene_script` (see `macros/scripts/maps.asm:12-33`): -`SCENE_RUINSOFALPHOUTSIDE_NOOP` = 0, `SCENE_RUINSOFALPHOUTSIDE_GET_UNOWN_DEX` = 1. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 8 | `BGEVENT_READ` | `RuinsOfAlphOutsideMysteryChamberSign` | -| 12 | 16 | `BGEVENT_READ` | `RuinsOfAlphSign` | -| 18 | 12 | `BGEVENT_READ` | `RuinsOfAlphResearchCenterSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RUINSOFALPHOUTSIDE_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 4 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerPsychicNathan` | -1 | -| `RUINSOFALPHOUTSIDE_SCIENTIST` | `SPRITE_SCIENTIST` | 11 | 15 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphOutsideScientistScript` | `EVENT_RUINS_OF_ALPH_OUTSIDE_SCIENTIST` | - -Note: the `object_const_def` block declares five consts (`YOUNGSTER1`, `SCIENTIST`, -`FISHER`, `YOUNGSTER2`, `YOUNGSTER3`) but only two `object_event` rows exist. The -last three consts are dead in Gold. - -**Scripts of interest** - -- `RuinsOfAlphOutsideScientistCallback` (`callback MAPCALLBACK_OBJECTS`) - the - whole gate for the #DEX upgrade: - `checkflag ENGINE_UNOWN_DEX` -> if true, `.NoScientist`; - `checkevent EVENT_MADE_UNOWN_APPEAR_IN_RUINS` -> if false, `.NoScientist`; - otherwise `readvar VAR_UNOWNCOUNT` / `ifgreater 2, .YesScientist`. - `.YesScientist` does `appear RUINSOFALPHOUTSIDE_SCIENTIST` + - `setscene SCENE_RUINSOFALPHOUTSIDE_GET_UNOWN_DEX`; `.NoScientist` does - `disappear` + `setscene SCENE_RUINSOFALPHOUTSIDE_NOOP`. - **"At least three different Unown" in the walkthrough is exactly `ifgreater 2`.** -- `RuinsOfAlphOutsideScientistSceneContinue` - text, `playmusic MUSIC_SHOW_ME_AROUND`, - `follow RUINSOFALPHOUTSIDE_SCIENTIST, PLAYER`, `applymovement` along - `RuinsOfAlphOutsideScientistWalkToLabMovement` - (`RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT UP UP`), `disappear`, `stopfollow`, - `applymovement PLAYER, RuinsOfAlphOutsidePlayerEnterLabMovement` (`UP`), - `setmapscene RUINS_OF_ALPH_RESEARCH_CENTER, SCENE_RUINSOFALPHRESEARCHCENTER_GET_UNOWN_DEX`, - `warpcheck`. The player is walked into warp 6 by the script; a bot does not - need to drive that step itself. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RUINS_OF_ALPH_OUTSIDE_SCIENTIST` | `constants/event_flags.asm:1189` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:470`), toggled by the map callback | scientist hidden while SET | -| `EVENT_MADE_UNOWN_APPEAR_IN_RUINS` | `constants/event_flags.asm:55` | set by `RuinsOfAlphInnerChamberStrangePresenceScript` | precondition for the scientist | -| `ENGINE_UNOWN_DEX` | `constants/engine_flags.asm:21` | set by `RuinsOfAlphResearchCenterGetUnownDexScript` | once set the scientist never reappears | -| `VAR_UNOWNCOUNT` | `constants/script_constants.asm:62` (`0e`) | read here and in the Research Center | distinct Unown forms recorded | -| `SCENE_RUINSOFALPHOUTSIDE_*` | inline `scene_script` rows | callback / coord events | 0 = noop, 1 = run the scientist scene | - -**Wild encounters** - -`data/wild/johto_grass.asm:388` `def_grass_wildmons RUINS_OF_ALPH_OUTSIDE`, rate -`4 percent` for all three windows; morn/day/nite are identical: -`20 NATU, 22 NATU, 18 NATU, 24 NATU, 20 SMEARGLE, 22 SMEARGLE, 22 SMEARGLE`. - -`data/wild/johto_water.asm:5` `def_water_wildmons RUINS_OF_ALPH_OUTSIDE`, rate -`2 percent`: `15 WOOPER, 20 QUAGSIRE, 15 QUAGSIRE`. This is the Wooper the -walkthrough points at, and it needs Surf. - -Fishing group is `FISHGROUP_POND` (`data/wild/fish.asm:15` -> `.Pond_Old`: -`MAGIKARP 10, MAGIKARP 10, POLIWAG 10`). - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `NATHAN` | `PSYCHIC_T` | 1 | `PsychicGroup` `; PSYCHIC_T (1)` (line 2512): `26 GIRAFARIG` | `TrainerPsychicNathan` (`EVENT_BEAT_PSYCHIC_NATHAN`) | none | - -Level 26 at this point in the game - the walkthrough silently skips him, and a bot -routed through `4, 20` will get pulled into a very lopsided fight. Sight range is 1. - -`TrainerSuperNerdEricUnused` is present in the file but marked `; unreferenced`. - ---- - -### MAP_RUINS_OF_ALPH_KABUTO_CHAMBER - -- Script: `maps/RuinsOfAlphKabutoChamber.asm` -- Blocks: `maps/RuinsOfAlphPuzzleChamber.blk` (shared by all four chambers) -- Header: `data/maps/maps.asm:102` -> `TILESET_RUINS_OF_ALPH, DUNGEON, - LANDMARK_RUINS_OF_ALPH, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE` - (phone service flag TRUE = phone calls suppressed) -- Dimensions: `constants/map_constants.asm:89` group `DUNGEONS` (3), id 24, `4, 5` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 9 | `RUINS_OF_ALPH_OUTSIDE` | 2 | -| 2 | 4 | 9 | `RUINS_OF_ALPH_OUTSIDE` | 2 | -| 3 | 3 | 3 | `RUINS_OF_ALPH_INNER_CHAMBER` | 4 | -| 4 | 4 | 3 | `RUINS_OF_ALPH_INNER_CHAMBER` | 5 | - -Warps 3/4 are the holes in the floor; they are only reachable after the puzzle. - -**Coord events**: none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 3 | `BGEVENT_READ` | `RuinsOfAlphKabutoChamberAncientReplica` | -| 5 | 3 | `BGEVENT_READ` | `RuinsOfAlphKabutoChamberAncientReplica` | -| 3 | 2 | `BGEVENT_UP` | `RuinsOfAlphKabutoChamberPuzzle` | -| 4 | 2 | `BGEVENT_UP` | `RuinsOfAlphKabutoChamberDescriptionSign` | - -`BGEVENT_UP` means the player must be standing below the tile and facing UP. The -puzzle panel is the LEFT of the two (`3, 2`) - the walkthrough's "go up to the left -panel" is literal. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RUINSOFALPHKABUTOCHAMBER_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 5 | 5 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphKabutoChamberReceptionistScript` | `EVENT_RUINS_OF_ALPH_KABUTO_CHAMBER_RECEPTIONIST` | - -Only ONE NPC is defined here. The walkthrough says "talk to the two people inside"; -the asm has a single receptionist. - -**Scripts of interest** - -- `RuinsOfAlphKabutoChamberHiddenDoorsCallback` (`callback MAPCALLBACK_TILES`) - - `checkevent EVENT_SOLVED_KABUTO_PUZZLE`; if false it patches the floor closed with - `changeblock 2, 2, $01` (left floor) and `changeblock 4, 2, $02` (right floor). - The `.blk` itself already contains the holes. -- `RuinsOfAlphKabutoChamberPuzzle` - `reanchormap`, `setval UNOWNPUZZLE_KABUTO`, - `special UnownPuzzle`, `closetext`, `iftrue .PuzzleComplete`. - `.PuzzleComplete` runs, in order: - `setevent EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS` (this HIDES the three - tourists - a set object flag means hidden), - `setevent EVENT_SOLVED_KABUTO_PUZZLE`, - `setflag ENGINE_UNLOCKED_UNOWNS_A_TO_K`, - `setevent EVENT_RUINS_OF_ALPH_KABUTO_CHAMBER_RECEPTIONIST` (hides the receptionist), - `setmapscene RUINS_OF_ALPH_INNER_CHAMBER, SCENE_RUINSOFALPHINNERCHAMBER_STRANGE_PRESENCE`, - `earthquake 30`, `showemote EMOTE_SHOCK, PLAYER, 15`, - `changeblock 2, 2, $18` / `changeblock 4, 2, $19` (the holes), - `refreshmap`, `playsound SFX_STRENGTH`, `earthquake 80`, `warpcheck`. - `warpcheck` is what drops the player through - no manual walk needed. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_SOLVED_KABUTO_PUZZLE` | `constants/event_flags.asm:334` | this script / the tiles callback | floor stays open once set | -| `ENGINE_UNLOCKED_UNOWNS_A_TO_K` | `constants/engine_flags.asm:56` | this script; read by `CheckUnownLetter` (`engine/battle/core.asm:6219`) and `ChooseWildEncounter` (`engine/overworld/wildmons.asm:341`) | letters A-K become catchable (`data/wild/unlocked_unowns.asm` `.Set_A_K`) | -| `EVENT_RUINS_OF_ALPH_KABUTO_CHAMBER_RECEPTIONIST` | `constants/event_flags.asm:1264` | this script | receptionist hidden once SET | -| `EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS` | `constants/event_flags.asm:1191` | this script | the three Inner Chamber NPCs hidden once SET | - -Sibling puzzles, for completeness (same shape, different flags): - -| chamber | asm | puzzle const | sets | unlocks | -|---|---|---|---|---| -| Ho-Oh | `maps/RuinsOfAlphHoOhChamber.asm` | `UNOWNPUZZLE_HO_OH` | `EVENT_SOLVED_HO_OH_PUZZLE` | `ENGINE_UNLOCKED_UNOWNS_X_TO_Z` | -| Omanyte | `maps/RuinsOfAlphOmanyteChamber.asm` | `UNOWNPUZZLE_OMANYTE` | `EVENT_SOLVED_OMANYTE_PUZZLE` | `ENGINE_UNLOCKED_UNOWNS_L_TO_R` | -| Aerodactyl | `maps/RuinsOfAlphAerodactylChamber.asm` | `UNOWNPUZZLE_AERODACTYL` | `EVENT_SOLVED_AERODACTYL_PUZZLE` (`constants/event_flags.asm:336`) | `ENGINE_UNLOCKED_UNOWNS_S_TO_W` | - ---- - -### MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER - -The Surf-gated puzzle the walkthrough defers. Included because it is the one place -the section names a field-move requirement. - -- Script: `maps/RuinsOfAlphAerodactylChamber.asm` -- Header: `data/maps/maps.asm:104` -> `TILESET_RUINS_OF_ALPH, DUNGEON, - LANDMARK_RUINS_OF_ALPH, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:91` group `DUNGEONS` (3), id 26, `4, 5` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 9 | `RUINS_OF_ALPH_OUTSIDE` | 4 | -| 2 | 4 | 9 | `RUINS_OF_ALPH_OUTSIDE` | 4 | -| 3 | 3 | 3 | `RUINS_OF_ALPH_INNER_CHAMBER` | 8 | -| 4 | 4 | 3 | `RUINS_OF_ALPH_INNER_CHAMBER` | 9 | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 3 | `BGEVENT_READ` | `RuinsOfAlphAerodactylChamberAncientReplica` | -| 5 | 3 | `BGEVENT_READ` | `RuinsOfAlphAerodactylChamberAncientReplica` | -| 3 | 2 | `BGEVENT_UP` | `RuinsOfAlphAerodactylChamberPuzzle` | -| 4 | 2 | `BGEVENT_UP` | `RuinsOfAlphAerodactylChamberDescriptionSign` | - -**Object events**: `def_object_events` is empty. No receptionist here. - ---- - -### MAP_RUINS_OF_ALPH_INNER_CHAMBER - -- Script: `maps/RuinsOfAlphInnerChamber.asm` -- Blocks: `maps/RuinsOfAlphInnerChamber.blk` -- Header: `data/maps/maps.asm:105` -> `TILESET_RUINS_OF_ALPH, DUNGEON, - LANDMARK_RUINS_OF_ALPH, MUSIC_RUINS_OF_ALPH_INTERIOR, TRUE, PALETTE_DAY, - FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:92` group `DUNGEONS` (3), id 27, `10, 14` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 10 | 13 | `RUINS_OF_ALPH_OUTSIDE` | 5 | -| 2 | 3 | 15 | `RUINS_OF_ALPH_HO_OH_CHAMBER` | 3 | -| 3 | 4 | 15 | `RUINS_OF_ALPH_HO_OH_CHAMBER` | 4 | -| 4 | 15 | 3 | `RUINS_OF_ALPH_KABUTO_CHAMBER` | 3 | -| 5 | 16 | 3 | `RUINS_OF_ALPH_KABUTO_CHAMBER` | 4 | -| 6 | 3 | 21 | `RUINS_OF_ALPH_OMANYTE_CHAMBER` | 3 | -| 7 | 4 | 21 | `RUINS_OF_ALPH_OMANYTE_CHAMBER` | 4 | -| 8 | 15 | 24 | `RUINS_OF_ALPH_AERODACTYL_CHAMBER` | 3 | -| 9 | 16 | 24 | `RUINS_OF_ALPH_AERODACTYL_CHAMBER` | 4 | - -Falling through the Kabuto holes lands you at `15, 3` / `16, 3`; the ladder out is -warp 1 at `10, 13`. - -**Coord events**: none. The "strange presence" fires from the scene script, not a -coord event. - -**BG events** (`def_bg_events`) - 26 identical `BGEVENT_READ, -RuinsOfAlphInnerChamberStatue` rows at: -`(2,3) (5,3) (8,3) (11,3) (14,3) (17,3) (2,8) (5,8) (8,8) (11,8) (14,8) (17,8) -(2,13) (17,13) (2,18) (5,18) (8,18) (11,18) (14,18) (17,18) (2,24) (5,24) (8,24) -(11,24) (14,24) (17,24)`. All flavour text; none are items. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RUINSOFALPHINNERCHAMBER_FISHER` | `SPRITE_FISHER` | 3 | 7 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphInnerChamberFisherScript` | `EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS` | -| `RUINSOFALPHINNERCHAMBER_TEACHER` | `SPRITE_TEACHER` | 14 | 13 | `WANDER` (r 1,1) | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphInnerChamberTeacherScript` | `EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS` | -| `RUINSOFALPHINNERCHAMBER_GRAMPS` | `SPRITE_GRAMPS` | 11 | 19 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphInnerChamberGrampsScript` | `EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS` | - -All three share one flag, so they are all present before any puzzle is solved and -all vanish the moment one is. - -**Scripts of interest** - -- `RuinsOfAlphInnerChamberStrangePresenceScene` - `sdefer - RuinsOfAlphInnerChamberStrangePresenceScript`. The deferred script prints - "There is a strange presence here...", then - `setscene SCENE_RUINSOFALPHINNERCHAMBER_NOOP` and - `setevent EVENT_MADE_UNOWN_APPEAR_IN_RUINS`. Scene ids: `NOOP` = 0, - `STRANGE_PRESENCE` = 1. - -**Wild encounters** - -`data/wild/johto_grass.asm:416` `def_grass_wildmons RUINS_OF_ALPH_INNER_CHAMBER`, -rate `6 percent` all windows, all 21 slots `db 5, UNOWN`. The level is always 5, -identical morn/day/nite. - -Two gates sit on top of that table: - -- `ChooseWildEncounter` (`engine/overworld/wildmons.asm:337-347`): after the species - is picked, `cp UNOWN` / `ld a, [wUnlockedUnowns]` / `and a` / `jr z, .nowildbattle` - - with no puzzle solved, `wUnlockedUnowns` is 0 and Unown encounters are silently - cancelled, so the Inner Chamber has zero encounters before a puzzle. -- `CheckUnownLetter` (`engine/battle/core.asm:6219`) re-rolls the form against - `UnlockedUnownLetterSets` (`data/wild/unlocked_unowns.asm`), so with only the - Kabuto puzzle solved you can only ever meet A-K (11 letters), which is more than - the 3 the scientist needs. - -`NUM_UNOWN EQU 26` (`constants/pokemon_constants.asm:308`) - see -"Unresolved" for the walkthrough's "28 forms" claim. - ---- - -### MAP_RUINS_OF_ALPH_RESEARCH_CENTER - -- Script: `maps/RuinsOfAlphResearchCenter.asm` -- Blocks: `maps/RuinsOfAlphResearchCenter.blk` -- Header: `data/maps/maps.asm:106` -> `TILESET_FACILITY, INDOOR, - LANDMARK_RUINS_OF_ALPH, MUSIC_UNION_CAVE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:93` group `DUNGEONS` (3), id 28, `4, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `RUINS_OF_ALPH_OUTSIDE` | 6 | -| 2 | 3 | 7 | `RUINS_OF_ALPH_OUTSIDE` | 6 | - -**Coord events**: none (the cutscene is a `scene_script` + `sdefer`). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 6 | 5 | `BGEVENT_READ` | `RuinsOfAlphResearchCenterBookshelf` | -| 3 | 4 | `BGEVENT_READ` | `RuinsOfAlphResearchCenterComputer` | -| 7 | 1 | `BGEVENT_READ` | `RuinsOfAlphResearchCenterPrinter` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RUINSOFALPHRESEARCHCENTER_SCIENTIST1` | `SPRITE_SCIENTIST` | 4 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphResearchCenterScientist1Script` | -1 | -| `RUINSOFALPHRESEARCHCENTER_SCIENTIST2` | `SPRITE_SCIENTIST` | 5 | 2 | `WANDER` (r 2,1) | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphResearchCenterScientist2Script` | -1 | -| `RUINSOFALPHRESEARCHCENTER_SCIENTIST3` | `SPRITE_SCIENTIST` | 2 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RuinsOfAlphResearchCenterScientist3Script` | `EVENT_RUINS_OF_ALPH_RESEARCH_CENTER_SCIENTIST` | - -**Scripts of interest** - -- `RuinsOfAlphResearchCenterScientistCallback` (`callback MAPCALLBACK_OBJECTS`) - - `checkscene` / `ifequal SCENE_RUINSOFALPHRESEARCHCENTER_GET_UNOWN_DEX, .ShowScientist`; - `.ShowScientist` does `moveobject RUINSOFALPHRESEARCHCENTER_SCIENTIST3, 3, 7` (onto - the doorway) and `appear`. So scientist 3 is teleported onto the entrance tile for - the cutscene, not spawned at his listed `2, 5`. -- `RuinsOfAlphResearchCenterGetUnownDexScript` (`sdefer` from the scene) - - `applymovement` to the computer (`UP UP LEFT`, `turn_head UP`), a run of - `playsound`/`pause` beats, text, then `setflag ENGINE_UNOWN_DEX`, more text, - `applymovement` away, `setscene SCENE_RUINSOFALPHRESEARCHCENTER_NOOP`, - `special RestartMapMusic`. **No item is added to the bag** - the "Unown Pokedex" - the walkthrough lists is an engine flag, not an inventory item. -- `RuinsOfAlphResearchCenterPrinter` / `...Computer` / `...Scientist3Script` - all - gate their good branch on `readvar VAR_UNOWNCOUNT` / `ifequal NUM_UNOWN`, i.e. all - 26 forms. `EVENT_RUINS_OF_ALPH_RESEARCH_CENTER_SCIENTIST` short-circuits the check - (`.SkipChecking`) while scientist 3 is still hidden. -- `RuinsOfAlphResearchCenterScientist1Script` / `...Scientist2Script` - pure text, - branching on `ENGINE_UNOWN_DEX` and `EVENT_MADE_UNOWN_APPEAR_IN_RUINS`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_UNOWN_DEX` | `constants/engine_flags.asm:21` | set here; read by the outside callback and the Pokedex | the section's "Unown Pokedex" reward | -| `EVENT_RUINS_OF_ALPH_RESEARCH_CENTER_SCIENTIST` | `constants/event_flags.asm:1190` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:471`), cleared by `appear` in the callback | scientist 3 hidden while SET | -| `SCENE_RUINSOFALPHRESEARCHCENTER_GET_UNOWN_DEX` | inline `scene_script` (= 1) | set remotely by `RuinsOfAlphOutsideScientistSceneContinue` | arms the cutscene on entry | - ---- - -### MAP_ROUTE_32_RUINS_OF_ALPH_GATE - -- Script: `maps/Route32RuinsOfAlphGate.asm` -- Header: `data/maps/maps.asm:258` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_32, - MUSIC_ROUTE_30, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:237` group `VIOLET` (10), id 12, `5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `RUINS_OF_ALPH_OUTSIDE` | 10 | -| 2 | 0 | 5 | `RUINS_OF_ALPH_OUTSIDE` | 11 | -| 3 | 9 | 4 | `ROUTE_32` | 2 | -| 4 | 9 | 5 | `ROUTE_32` | 3 | - -**Coord events / BG events**: none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE32RUINSOFALPHGATE_OFFICER` | `SPRITE_OFFICER` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route32RuinsOfAlphGateOfficerScript` | -1 | -| `ROUTE32RUINSOFALPHGATE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 8 | 2 | `WALK_UP_DOWN` (r 0,1) | `OBJECTTYPE_SCRIPT` | `Route32RuinsOfAlphGatePokefanMScript` | -1 | -| `ROUTE32RUINSOFALPHGATE_YOUNGSTER` | `SPRITE_YOUNGSTER` | 1 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `Route32RuinsOfAlphGateYoungsterScript` | -1 | - -All `jumptextfaceplayer`. No gate. - ---- - -### MAP_ROUTE_32 - -- Script: `maps/Route32.asm` -- Blocks: `maps/Route32.blk` -- Header: `data/maps/maps.asm:247` -> `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_32, - MUSIC_ROUTE_30, FALSE, PALETTE_AUTO, FISHGROUP_QWILFISH` -- Dimensions: `constants/map_constants.asm:226` group `VIOLET` (10), id 1, - `map_const ROUTE_32, 10, 45` (10x45 blocks = 20x90 cells - by far the longest map - in this section) -- Connections (`data/maps/attributes.asm`): `connection north, VioletCity, - VIOLET_CITY, 0` and `connection south, Route33, ROUTE_33, 0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 73 | `ROUTE_32_POKECENTER_1F` | 1 | -| 2 | 4 | 2 | `ROUTE_32_RUINS_OF_ALPH_GATE` | 3 | -| 3 | 4 | 3 | `ROUTE_32_RUINS_OF_ALPH_GATE` | 4 | -| 4 | 6 | 79 | `UNION_CAVE_1F` | 4 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE32_COOLTRAINER_M_BLOCKS` (0) | 18 | 8 | `Route32CooltrainerMStopsYouScene` | turns both parties, text, `follow PLAYER, ROUTE32_COOLTRAINER_M`, `applymovement PLAYER, Movement_Route32CooltrainerMPushesYouBackToViolet` (`UP UP`), `stopfollow`, `turnobject PLAYER, DOWN`, `scall Route32CooltrainerMContinueScene`, then resets him with `DOWN` then `RIGHT` | -| `SCENE_ROUTE32_OFFER_SLOWPOKETAIL` (1) | 7 | 71 | `Route32WannaBuyASlowpokeTailScript` | `turnobject ROUTE32_FISHER4, DOWN` / `turnobject PLAYER, UP` then falls into `_OfferToSellSlowpokeTail` | - -Scene ids from the inline `scene_script` rows: `SCENE_ROUTE32_COOLTRAINER_M_BLOCKS` -= 0 (the default, since scene bytes start at 0), -`SCENE_ROUTE32_OFFER_SLOWPOKETAIL` = 1, `SCENE_ROUTE32_NOOP` = 2. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 13 | 5 | `BGEVENT_READ` | `Route32Sign` | -| 9 | 1 | `BGEVENT_READ` | `Route32RuinsSign` | -| 10 | 84 | `BGEVENT_READ` | `Route32UnionCaveSign` | -| 12 | 73 | `BGEVENT_READ` | `Route32PokecenterSign` (`jumpstd PokecenterSignScript`) | -| 12 | 67 | `BGEVENT_ITEM` | `Route32HiddenGreatBall` -> `hiddenitem GREAT_BALL, EVENT_ROUTE_32_HIDDEN_GREAT_BALL` | -| 11 | 40 | `BGEVENT_ITEM` | `Route32HiddenSuperPotion` -> `hiddenitem SUPER_POTION, EVENT_ROUTE_32_HIDDEN_SUPER_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE32_FISHER1` | `SPRITE_FISHER` | 8 | 49 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerFisherJustin` | -1 | -| `ROUTE32_FISHER2` | `SPRITE_FISHER` | 12 | 56 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerFisherRalph1` | -1 | -| `ROUTE32_FISHER3` | `SPRITE_FISHER` | 6 | 48 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerFisherHenry` | -1 | -| `ROUTE32_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 13 | 23 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerYoungsterAlbert` | -1 | -| `ROUTE32_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 4 | 65 | `SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerYoungsterGordon` | -1 | -| `ROUTE32_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 1 | 56 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerCamperRoland` | -1 | -| `ROUTE32_LASS1` | `SPRITE_LASS` | 10 | 30 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerPicnickerLiz1` | -1 | -| `ROUTE32_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 19 | 8 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route32CooltrainerMScript` | -1 | -| `ROUTE32_YOUNGSTER4` | `SPRITE_YOUNGSTER` | 11 | 82 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerBirdKeeperPeter` | -1 | -| `ROUTE32_FISHER4` | `SPRITE_FISHER` | 7 | 70 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SlowpokeTailSalesmanScript` | `EVENT_SLOWPOKE_WELL_ROCKETS` | -| `ROUTE32_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 53 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route32GreatBall` (`itemball GREAT_BALL`) | `EVENT_ROUTE_32_GREAT_BALL` | -| `ROUTE32_FISHER5` | `SPRITE_FISHER` | 15 | 13 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route32RoarTMGuyScript` | -1 | -| `ROUTE32_FRIEDA` | `SPRITE_LASS` | 12 | 67 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `FriedaScript` | `EVENT_ROUTE_32_FRIEDA_OF_FRIDAY` | -| `ROUTE32_POKE_BALL2` | `SPRITE_POKE_BALL` | 3 | 30 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route32Potion` (`itemball POTION`) | `EVENT_ROUTE_32_POTION` | - -**Scripts of interest** - -- `Route32FriedaCallback` (`callback MAPCALLBACK_OBJECTS`) - `readvar VAR_WEEKDAY` / - `ifequal FRIDAY, .FriedaAppears`; otherwise `disappear ROUTE32_FRIEDA`. She is a - hard weekday gate, and she stands on the same cell (`12, 67`) as the hidden Great - Ball. -- `FriedaScript` - if `EVENT_GOT_POISON_BARB_FROM_FRIEDA` -> Friday chat; else - `readvar VAR_WEEKDAY` / `ifnotequal FRIDAY, .NotFriday`; else - `setevent EVENT_MET_FRIEDA_OF_FRIDAY`, `verbosegiveitem POISON_BARB`, - `iffalse .Done` (bag full), `setevent EVENT_GOT_POISON_BARB_FROM_FRIEDA`. -- `Route32CooltrainerMScript` / `Route32CooltrainerMContinueScene` - the Miracle Seed - decision tree, in asm order: - `checkevent EVENT_GOT_MIRACLE_SEED_IN_ROUTE_32` -> `.GotMiracleSeed` (thanks text); - `checkflag ENGINE_ZEPHYRBADGE` -> if false `.DontHaveZephyrBadge` ("have you gone - to the GYM"); - `checkevent EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE` -> if true `.GiveMiracleSeed`, - else "the aide is waiting at the #MON CENTER". - `.GiveMiracleSeed` -> `verbosegiveitem MIRACLE_SEED`, `iffalse .BagFull`, - `setevent EVENT_GOT_MIRACLE_SEED_IN_ROUTE_32`. - So the Miracle Seed needs BOTH `ENGINE_ZEPHYRBADGE` and the Togepi egg. -- `Route32RoarTMGuyScript` - `checkevent EVENT_GOT_TM05_ROAR`, - `verbosegiveitem TM_ROAR`, `setevent EVENT_GOT_TM05_ROAR`. Unconditional otherwise. -- `SlowpokeTailSalesmanScript` / `_OfferToSellSlowpokeTail` - first thing it does is - `setscene SCENE_ROUTE32_NOOP`, so the `7, 71` trip-wire fires exactly once. - `yesorno`: "yes" gives the mocking `Text_ThoughtKidsWereLoaded`, nothing is bought - or given either way. There is no purchase path in the asm. -- `TrainerFisherRalph1` / `TrainerPicnickerLiz1` - both are phone-number trainers. - After the battle: `checkevent EVENT_*_READY_FOR_REMATCH` -> `.Rematch`; - `checkcellnum PHONE_FISHER_RALPH` / `PHONE_PICNICKER_LIZ` -> already registered; - `checkevent EVENT_*_ASKED_FOR_PHONE_NUMBER` -> ask again; otherwise - `setevent EVENT_*_ASKED_FOR_PHONE_NUMBER` then - `askforphonenumber PHONE_FISHER_RALPH` with `PHONE_CONTACTS_FULL` / - `PHONE_CONTACT_REFUSED` branches. **You must talk to them a second time after the - battle** to get the number, exactly as the walkthrough says. - Rematch parties escalate: Ralph -> `RALPH2` after `ENGINE_FLYPOINT_ECRUTEAK`, - `RALPH3` after `ENGINE_FLYPOINT_LAKE_OF_RAGE`; Liz -> `LIZ2` after - `ENGINE_FLYPOINT_ECRUTEAK`, `LIZ3` after `EVENT_CLEARED_ROCKET_HIDEOUT`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_MIRACLE_SEED_IN_ROUTE_32` | `constants/event_flags.asm:102` | `Route32CooltrainerMScript` | one-time | -| `ENGINE_ZEPHYRBADGE` | `constants/engine_flags.asm:38` | read by the same script | Miracle Seed precondition | -| `EVENT_GOT_TM05_ROAR` | `constants/event_flags.asm:87` | `Route32RoarTMGuyScript` | one-time | -| `EVENT_MET_FRIEDA_OF_FRIDAY` | `constants/event_flags.asm:107` | `FriedaScript` | intro-text-only | -| `EVENT_GOT_POISON_BARB_FROM_FRIEDA` | `constants/event_flags.asm:108` | `FriedaScript` | one-time | -| `EVENT_ROUTE_32_FRIEDA_OF_FRIDAY` | `constants/event_flags.asm:1274` | `Route32FriedaCallback` | object hidden while SET (non-Friday) | -| `EVENT_ROUTE_32_GREAT_BALL` | `constants/event_flags.asm:1105` | itemball at `6, 53` | one-time | -| `EVENT_ROUTE_32_POTION` | `constants/event_flags.asm:1106` | itemball at `3, 30` | one-time | -| `EVENT_ROUTE_32_HIDDEN_GREAT_BALL` | `constants/event_flags.asm:175` | hidden item at `12, 67` | one-time | -| `EVENT_ROUTE_32_HIDDEN_SUPER_POTION` | `constants/event_flags.asm:176` | hidden item at `11, 40` | one-time | -| `EVENT_SLOWPOKE_WELL_ROCKETS` | `constants/event_flags.asm:1182` | gates `ROUTE32_FISHER4` | salesman disappears once Slowpoke Well is cleared | -| `PHONE_FISHER_RALPH` / `PHONE_PICNICKER_LIZ` | `constants/phone_constants.asm:20-21` | `askforphonenumber` | contact slots 17 / 18 | -| `VAR_WEEKDAY` | `constants/script_constants.asm:59` (`0b`) | Frieda callback and script | `FRIDAY` | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `MIRACLE_SEED` | talk to `ROUTE32_COOLTRAINER_M` at `19, 8` | `Route32CooltrainerMScript` `.GiveMiracleSeed` | `EVENT_GOT_MIRACLE_SEED_IN_ROUTE_32` | -| `TM_ROAR` (TM05) | talk to `ROUTE32_FISHER5` at `15, 13` | `Route32RoarTMGuyScript` | `EVENT_GOT_TM05_ROAR` | -| `POISON_BARB` | talk to Frieda at `12, 67` on a Friday | `FriedaScript` | `EVENT_GOT_POISON_BARB_FROM_FRIEDA` | -| `GREAT_BALL` | itemball at `6, 53` | `Route32GreatBall` | `EVENT_ROUTE_32_GREAT_BALL` | -| `POTION` | itemball at `3, 30` | `Route32Potion` | `EVENT_ROUTE_32_POTION` | -| `GREAT_BALL` (hidden) | hidden at `12, 67` | `Route32HiddenGreatBall` | `EVENT_ROUTE_32_HIDDEN_GREAT_BALL` | -| `SUPER_POTION` (hidden) | hidden at `11, 40` | `Route32HiddenSuperPotion` | `EVENT_ROUTE_32_HIDDEN_SUPER_POTION` | -| `OLD_ROD` | `ROUTE_32_POKECENTER_1F`, see below | `Route32Pokecenter1FFishingGuruScript` | `EVENT_GOT_OLD_ROD` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `ALBERT` | `YOUNGSTER` | 3 | `YoungsterGroup` `; YOUNGSTER (3)` line 351: `6 RATTATA`, `8 ZUBAT` | `TrainerYoungsterAlbert` (`EVENT_BEAT_YOUNGSTER_ALBERT`) | none | -| `LIZ1` | `PICNICKER` | 1 | `PicnickerGroup` `; PICNICKER (1)` line 2584: `9 NIDORAN_F` | `TrainerPicnickerLiz1` (`EVENT_BEAT_PICNICKER_LIZ`) | `PHONE_PICNICKER_LIZ`; `LIZ2` `15 WEEPINBELL / 15 NIDORINA`, `LIZ3` `19 WEEPINBELL / 19 NIDORINO / 21 NIDOQUEEN` | -| `JUSTIN` | `FISHER` | 1 | `FisherGroup` `; FISHER (1)` line 1498: `5 MAGIKARP`, `5 MAGIKARP`, `15 MAGIKARP`, `5 MAGIKARP` | `TrainerFisherJustin` (`EVENT_BEAT_FISHER_JUSTIN`) | none | -| `HENRY` | `FISHER` | 5 | `FisherGroup` `; FISHER (5)` line 1523: `8 POLIWAG`, `8 POLIWAG` | `TrainerFisherHenry` (`EVENT_BEAT_FISHER_HENRY`) | none | -| `RALPH1` | `FISHER` | 2 | `FisherGroup` `; FISHER (2)` line 1506: `10 GOLDEEN` | `TrainerFisherRalph1` (`EVENT_BEAT_FISHER_RALPH`) | `PHONE_FISHER_RALPH`; `RALPH2` `17 GOLDEEN`, `RALPH3` `17 QWILFISH / 19 GOLDEEN` | -| `ROLAND` | `CAMPER` | 1 | `CamperGroup` `; CAMPER (1)` line 2705: `9 NIDORAN_M` | `TrainerCamperRoland` (`EVENT_BEAT_CAMPER_ROLAND`) | none | -| `GORDON` | `YOUNGSTER` | 4 | `YoungsterGroup` `; YOUNGSTER (4)` line 357: `10 WOOPER` | `TrainerYoungsterGordon` (`EVENT_BEAT_YOUNGSTER_GORDON`) | none | -| `PETER` | `BIRD_KEEPER` | 13 | `BirdKeeperGroup` `; BIRD_KEEPER (13)` line 598: `6 PIDGEY`, `6 PIDGEY`, `8 SPEAROW` | `TrainerBirdKeeperPeter` (`EVENT_BEAT_BIRD_KEEPER_PETER`) | none | - -All eight parties are `TRAINERTYPE_NORMAL` (level + species only, no custom moves or -held items). - -**Wild encounters** - -`data/wild/johto_grass.asm:1711` `def_grass_wildmons ROUTE_32`, rate -`10 percent` all windows. **Gold** branch (`IF DEF(_GOLD)`): - -- morn: `6 BELLSPROUT, 4 RATTATA, 6 MAREEP, 6 HOPPIP, 6 RATTATA, 4 WOOPER, 4 ZUBAT` -- day: `6 BELLSPROUT, 4 RATTATA, 6 MAREEP, 6 HOPPIP, 6 RATTATA, 8 RATTATA, 8 RATTATA` -- nite: `6 WOOPER, 4 RATTATA, 6 BELLSPROUT, 6 MAREEP, 8 WOOPER, 8 ZUBAT, 8 ZUBAT` - -Silver swaps the `RATTATA` in slot 2 for `EKANS`. **Wooper is a morn-or-nite mon in -Gold** - it does not appear in the day table at all, which is why the walkthrough -says "Wooper will be a bit harder to find than Bellsprout". - -`data/wild/johto_water.asm:142` `def_water_wildmons ROUTE_32`, rate `6 percent`: -`15 TENTACOOL, 20 QUAGSIRE, 20 TENTACRUEL` (Surf only). - -Fishing group `FISHGROUP_QWILFISH` (`data/wild/fish.asm:22`); `.Qwilfish_Old` -(line 178) is `MAGIKARP 10, MAGIKARP 10, TENTACOOL 10` - the Old Rod here gives no -Qwilfish. - ---- - -### MAP_ROUTE_32_POKECENTER_1F - -- Script: `maps/Route32Pokecenter1F.asm` -- Header: `data/maps/maps.asm:259` -> `TILESET_POKECENTER, INDOOR, - LANDMARK_ROUTE_32, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:238` group `VIOLET` (10), id 13, `5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ROUTE_32` | 1 | -| 2 | 4 | 7 | `ROUTE_32` | 1 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events / BG events**: none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE32POKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route32Pokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 | -| `ROUTE32POKECENTER1F_FISHING_GURU` | `SPRITE_FISHING_GURU` | 1 | 4 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route32Pokecenter1FFishingGuruScript` | -1 | -| `ROUTE32POKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 6 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route32Pokecenter1FCooltrainerFScript` | -1 | - -**Scripts of interest** - -- `Route32Pokecenter1FFishingGuruScript` - `checkevent EVENT_GOT_OLD_ROD` -> - `.GotOldRod`; else text, `yesorno`, `iffalse .Refused`, then - `verbosegiveitem OLD_ROD`, text, `setevent EVENT_GOT_OLD_ROD`. - Note: unlike most gift NPCs there is **no `iffalse` bag-full guard after the - `verbosegiveitem`** - the flag is set unconditionally on the yes path. - Answering "no" is recoverable (`.Refused` sets nothing). - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `OLD_ROD` | talk to the Fishing Guru at `1, 4`, answer YES | `Route32Pokecenter1FFishingGuruScript` | `EVENT_GOT_OLD_ROD` (`constants/event_flags.asm:31`) | - ---- - -### MAP_UNION_CAVE_1F - -- Script: `maps/UnionCave1F.asm` -- Blocks: `maps/UnionCave1F.blk` -- Header: `data/maps/maps.asm:107` -> `TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, - MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` - The palette is `PALETTE_NITE`, not `PALETTE_DARK` - **Union Cave is not a dark - cave and never needs FLASH**, despite Firebreather Ray's "if it's light, a cave - isn't scary" line. -- Dimensions: `constants/map_constants.asm:94` group `DUNGEONS` (3), id 29, `10, 18` -- Connections: none (`map_attributes UnionCave1F, UNION_CAVE_1F, $09`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 19 | `UNION_CAVE_B1F` | 3 | -| 2 | 3 | 33 | `UNION_CAVE_B1F` | 4 | -| 3 | 17 | 31 | `ROUTE_33` | 1 | -| 4 | 17 | 3 | `ROUTE_32` | 4 | - -**Coord events**: none. **BG events**: none (`UnionCave1FUnusedSign` exists in the -file but is marked `; unreferenced`). - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVE1F_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 4 | 4 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerHikerDaniel` | -1 | -| `UNIONCAVE1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 4 | 21 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerPokemaniacLarry` | -1 | -| `UNIONCAVE1F_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 15 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerHikerRussell` | -1 | -| `UNIONCAVE1F_FISHER1` | `SPRITE_FISHER` | 16 | 31 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerFirebreatherRay` | -1 | -| `UNIONCAVE1F_FISHER2` | `SPRITE_FISHER` | 15 | 15 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerFirebreatherBill` | -1 | -| `UNIONCAVE1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 17 | 21 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FGreatBall` (`itemball GREAT_BALL`) | `EVENT_UNION_CAVE_1F_GREAT_BALL` | -| `UNIONCAVE1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 4 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FPotion` (`itemball POTION`) | `EVENT_UNION_CAVE_1F_POTION` | -| `UNIONCAVE1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 4 | 17 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FXAttack` (`itemball X_ATTACK`) | `EVENT_UNION_CAVE_1F_X_ATTACK` | -| `UNIONCAVE1F_POKE_BALL4` | `SPRITE_POKE_BALL` | 12 | 33 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FAwakening` (`itemball AWAKENING`) | `EVENT_UNION_CAVE_1F_AWAKENING` | - -**Scripts of interest** - -Every trainer script here is the plain `endifjustbattled / opentext / writetext / -waitbutton / closetext / end` shape. No flags beyond the `EVENT_BEAT_*` in the -`trainer` macro, no items, no rematch. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `GREAT_BALL` | itemball at `17, 21` | `UnionCave1FGreatBall` | `EVENT_UNION_CAVE_1F_GREAT_BALL` (`constants/event_flags.asm:1019`) | -| `POTION` | itemball at `4, 2` | `UnionCave1FPotion` | `EVENT_UNION_CAVE_1F_POTION` (:1020) | -| `X_ATTACK` | itemball at `4, 17` | `UnionCave1FXAttack` | `EVENT_UNION_CAVE_1F_X_ATTACK` (:1021) | -| `AWAKENING` | itemball at `12, 33` | `UnionCave1FAwakening` | `EVENT_UNION_CAVE_1F_AWAKENING` (:1022) | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `DANIEL` | `HIKER` | 18 | `HikerGroup` `; HIKER (18)` line 2229: `11 ONIX` | `TrainerHikerDaniel` (`EVENT_BEAT_HIKER_DANIEL`) | none | -| `RUSSELL` | `HIKER` | 2 | `HikerGroup` `; HIKER (2)` line 2123: `4 GEODUDE`, `6 GEODUDE`, `8 GEODUDE` | `TrainerHikerRussell` (`EVENT_BEAT_HIKER_RUSSELL`) | none | -| `BILL` | `FIREBREATHER` | 5 | `FirebreatherGroup` `; FIREBREATHER (5)` line 2359: `6 KOFFING`, `6 KOFFING` | `TrainerFirebreatherBill` (`EVENT_BEAT_FIREBREATHER_BILL`) | none | -| `LARRY` | `POKEMANIAC` | 1 | `PokemaniacGroup` `; POKEMANIAC (1)` line 1081: `10 SLOWPOKE` | `TrainerPokemaniacLarry` (`EVENT_BEAT_POKEMANIAC_LARRY`) | none | -| `RAY` | `FIREBREATHER` | 7 | `FirebreatherGroup` `; FIREBREATHER (7)` line 2371: `9 VULPIX` | `TrainerFirebreatherRay` (`EVENT_BEAT_FIREBREATHER_RAY`) | none | - -**Wild encounters** - -`data/wild/johto_grass.asm:444` `def_grass_wildmons UNION_CAVE_1F`, rate -`6 percent` all windows. **Gold** branch, identical morn/day/nite: -`6 GEODUDE, 6 SANDSHREW, 5 ZUBAT, 4 RATTATA, 7 ZUBAT, 6 ONIX, 6 ONIX`. -(Silver replaces `SANDSHREW` with a second `RATTATA`.) -Note `SANDSHREW` and `ONIX` are in the table but not in the walkthrough's list. - -`data/wild/johto_water.asm:12` `def_water_wildmons UNION_CAVE_1F`, rate -`2 percent`: `15 WOOPER, 20 QUAGSIRE, 15 QUAGSIRE`. - -Fishing group `FISHGROUP_LAKE` (`data/wild/fish.asm:14`); `.Lake_Old` (line 57) -`MAGIKARP 10, MAGIKARP 10, GOLDEEN 10`. - ---- - -### MAP_UNION_CAVE_B1F - -- Script: `maps/UnionCaveB1F.asm` -- Blocks: `maps/UnionCaveB1F.blk` -- Header: `data/maps/maps.asm:108` -> `TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, - MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:95` group `DUNGEONS` (3), id 30, `10, 18` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `RUINS_OF_ALPH_OUTSIDE` | 7 | -| 2 | 3 | 11 | `RUINS_OF_ALPH_OUTSIDE` | 8 | -| 3 | 7 | 19 | `UNION_CAVE_1F` | 1 | -| 4 | 3 | 33 | `UNION_CAVE_1F` | 2 | -| 5 | 17 | 31 | `UNION_CAVE_B2F` | 1 | - -**Coord events / BG events**: none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVEB1F_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 10 | 4 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerHikerPhillip` | -1 | -| `UNIONCAVEB1F_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 17 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerHikerLeonard` | -1 | -| `UNIONCAVEB1F_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 32 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPokemaniacAndrew` | -1 | -| `UNIONCAVEB1F_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 17 | 30 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPokemaniacCalvin` | -1 | -| `UNIONCAVEB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 2 | 16 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB1FTMSwift` (`itemball TM_SWIFT`) | `EVENT_UNION_CAVE_B1F_TM_SWIFT` | -| `UNIONCAVEB1F_BOULDER` | `SPRITE_BOULDER` | 7 | 10 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `UnionCaveB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 | -| `UNIONCAVEB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 17 | 23 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB1FXDefend` (`itemball X_DEFEND`) | `EVENT_UNION_CAVE_B1F_X_DEFEND` | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_SWIFT` (TM39) | itemball at `2, 16` | `UnionCaveB1FTMSwift` | `EVENT_UNION_CAVE_B1F_TM_SWIFT` (`constants/event_flags.asm:1023`) | -| `X_DEFEND` | itemball at `17, 23` | `UnionCaveB1FXDefend` | `EVENT_UNION_CAVE_B1F_X_DEFEND` (:1024) | - -**Trainers** (all beyond the walkthrough's current route; they sit on the B2F path) - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `PHILLIP` | `HIKER` | 3 | `; HIKER (3)` line 2130: `23 GEODUDE`, `23 GEODUDE`, `23 GRAVELER` | `TrainerHikerPhillip` | none | -| `LEONARD` | `HIKER` | 4 | `; HIKER (4)` line 2137: `23 GEODUDE`, `25 MACHOP` | `TrainerHikerLeonard` | none | -| `ANDREW` | `POKEMANIAC` | 2 | `; POKEMANIAC (2)` line 1086: `24 MAROWAK`, `24 MAROWAK` | `TrainerPokemaniacAndrew` | none | -| `CALVIN` | `POKEMANIAC` | 3 | `; POKEMANIAC (3)` line 1092: `26 KANGASKHAN` | `TrainerPokemaniacCalvin` | none | - -Level 23-26 - a bot at the walkthrough's level 15-17 must NOT wander into these. The -TM Swift / X Defend route (`7, 19` -> `2, 16` -> `17, 23` -> `3, 33`) keeps clear of -Phillip (`10, 4`) and Leonard (`17, 10`) but passes near Calvin (`17, 30`, sight 3) -on the way to X Defend at `17, 23`. - -**Wild encounters** - -`data/wild/johto_grass.asm:499` `def_grass_wildmons UNION_CAVE_B1F`, rate -`6 percent`. Gold branch, identical morn/day/nite: -`8 GEODUDE, 8 SANDSHREW, 7 ZUBAT, 8 ONIX, 9 ZUBAT, 6 RATTATA, 6 RATTATA`. -`data/wild/johto_water.asm:19`, rate `2 percent`: -`15 WOOPER, 20 QUAGSIRE, 15 QUAGSIRE`. - ---- - -### MAP_UNION_CAVE_B2F - -Out of the walkthrough's current route (needs Strength for the B1F boulder), but it -is the map the section explicitly says to come back to. - -- Script: `maps/UnionCaveB2F.asm` -- Blocks: `maps/UnionCaveB2F.blk` -- Header: `data/maps/maps.asm:109` -> `TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, - MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:96` group `DUNGEONS` (3), id 31, `10, 18` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 3 | `UNION_CAVE_B1F` | 5 | - -**Coord events / BG events**: none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVEB2F_ROCKER` | `SPRITE_ROCKER` | 17 | 23 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerCooltrainermNick` | -1 | -| `UNIONCAVEB2F_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 5 | 13 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerCooltrainerfGwen` | -1 | -| `UNIONCAVEB2F_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 3 | 28 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerCooltrainerfEmma` | -1 | -| `UNIONCAVEB2F_POKE_BALL1` | `SPRITE_POKE_BALL` | 16 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB2FElixer` | `EVENT_UNION_CAVE_B2F_ELIXER` | -| `UNIONCAVEB2F_POKE_BALL2` | `SPRITE_POKE_BALL` | 12 | 19 | `STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB2FHyperPotion` | `EVENT_UNION_CAVE_B2F_HYPER_POTION` | -| `UNIONCAVEB2F_LAPRAS` | `SPRITE_SURF` | 11 | 31 | `SWIM_WANDER` (r 1,1) | `OBJECTTYPE_SCRIPT` | `UnionCaveLapras` | `EVENT_UNION_CAVE_B2F_LAPRAS` | - -**Scripts of interest** - -- `UnionCaveB2FLaprasCallback` (`callback MAPCALLBACK_OBJECTS`) - - `checkflag ENGINE_UNION_CAVE_LAPRAS` -> if true `.NoAppear`; - `readvar VAR_WEEKDAY` / `ifequal FRIDAY, .Appear`; else `disappear`. -- `UnionCaveLapras` - `faceplayer`, `cry LAPRAS`, `loadwildmon LAPRAS, 20`, - `startbattle`, `disappear`, `setflag ENGINE_UNION_CAVE_LAPRAS`, - `reloadmapafterbattle`. **Level 20, once per week only if you fail to catch it - - no, once ever**: `ENGINE_UNION_CAVE_LAPRAS` (`constants/engine_flags.asm:107`) is - set unconditionally after the battle. The Lapras is on water, so Surf is needed to - reach `11, 31`. - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `NICK` | `COOLTRAINERM` | 1 | `CooltrainerMGroup` `; COOLTRAINERM (1)` line 740, `TRAINERTYPE_MOVES`: `26 CHARMANDER` (EMBER/SMOKESCREEN/RAGE/SCARY_FACE), `26 SQUIRTLE` (WITHDRAW/WATER_GUN/BITE/CURSE), `26 BULBASAUR` (LEECH_SEED/POISONPOWDER/SLEEP_POWDER/RAZOR_LEAF) | `TrainerCooltrainermNick` | -| `GWEN` | `COOLTRAINERF` | 1 | `CooltrainerFGroup` `; COOLTRAINERF (1)` line 865: `26 EEVEE`, `22 FLAREON`, `22 VAPOREON`, `22 JOLTEON` | `TrainerCooltrainerfGwen` | -| `EMMA` | `COOLTRAINERF` | 15 | line 954: `28 POLIWHIRL` | `TrainerCooltrainerfEmma` | - -**Wild encounters** - -`data/wild/johto_grass.asm:554`, rate `4 percent`, identical morn/day/nite: -`22 ZUBAT, 22 RATICATE, 22 GOLBAT, 21 GEODUDE, 20 RATTATA, 23 ONIX, 23 ONIX`. -`data/wild/johto_water.asm:26`, rate `4 percent`: -`15 TENTACOOL, 20 QUAGSIRE, 20 TENTACRUEL`. - ---- - -### MAP_ROUTE_33 - -- Script: `maps/Route33.asm` -- Blocks: `maps/Route33.blk` -- Header: `data/maps/maps.asm:231` -> `TILESET_JOHTO_MODERN, ROUTE, - LANDMARK_ROUTE_33, MUSIC_ROUTE_30, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:212` group `AZALEA` (8), id 6, `10, 9` -- Connections (`data/maps/attributes.asm`): `connection north, Route32, ROUTE_32, 0` - and `connection west, AzaleaTown, AZALEA_TOWN, 0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 9 | `UNION_CAVE_1F` | 3 | - -**Coord events**: none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 11 | `BGEVENT_READ` | `Route33Sign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE33_POKEFAN_M` | `SPRITE_POKEFAN_M` | 6 | 13 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerHikerAnthony` | -1 | -| `ROUTE33_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 14 | 16 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route33FruitTree` (`fruittree FRUITTREE_ROUTE_33`) | -1 | - -**Scripts of interest** - -- `TrainerHikerAnthony` - same phone shape as Ralph/Liz: - `EVENT_ANTHONY_READY_FOR_REMATCH` -> `.Rematch`; - `checkcellnum PHONE_HIKER_ANTHONY`; `EVENT_ANTHONY_ASKED_FOR_PHONE_NUMBER`; - `askforphonenumber PHONE_HIKER_ANTHONY`. Rematch escalates to `ANTHONY1` after - `ENGINE_FLYPOINT_OLIVINE` and `ANTHONY3` after `EVENT_CLEARED_RADIO_TOWER`. -- `Route33FruitTree` - `fruittree FRUITTREE_ROUTE_33` (`constants/script_constants.asm:212`, - index `06`); `data/items/fruit_trees.asm` row 6 is `db PSNCUREBERRY ; ROUTE_33`. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `PSNCUREBERRY` | headbutt-style fruit tree object at `14, 16` | `Route33FruitTree` / `data/items/fruit_trees.asm` `; ROUTE_33` | none - fruit trees respawn on a timer, tracked in `wFruitTreeFlags`, not an `EVENT_*` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `ANTHONY2` | `HIKER` | 5 | `HikerGroup` `; HIKER (5)` line 2143: `11 GEODUDE`, `11 MACHOP` | `TrainerHikerAnthony` (`EVENT_BEAT_HIKER_ANTHONY`) | `PHONE_HIKER_ANTHONY` (slot 19); `ANTHONY1` `16 GEODUDE / 18 MACHAMP`, `ANTHONY3` `25 GRAVELER / 27 GRAVELER / 29 MACHOKE` | - -**Wild encounters** - -`data/wild/johto_grass.asm:1766` `def_grass_wildmons ROUTE_33`, rate `10 percent`. -**Gold** branch: - -- morn: `6 HOPPIP, 7 RATTATA, 6 SPEAROW, 6 RATTATA, 8 HOPPIP, 4 ZUBAT, 4 ZUBAT` -- day: `6 HOPPIP, 7 RATTATA, 6 SPEAROW, 6 RATTATA, 8 HOPPIP, 8 RATTATA, 8 RATTATA` -- nite: `6 ZUBAT, 7 RATTATA, 6 RATTATA, 6 RATTATA, 8 ZUBAT, 8 ZUBAT, 8 ZUBAT` - -Silver replaces the slot-2 `RATTATA` with `EKANS`. No water table. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Route 32 south is soft-blocked at the top | `maps/Route32.asm` `coord_event 18, 8, SCENE_ROUTE32_COOLTRAINER_M_BLOCKS, Route32CooltrainerMStopsYouScene` | scene byte for `ROUTE_32` must not be 0 | `maps/VioletPokecenter1F.asm` `setmapscene ROUTE_32, SCENE_ROUTE32_OFFER_SLOWPOKETAIL` when you take the Togepi EGG. Until then the coord event fires every time and `applymovement PLAYER, Movement_Route32CooltrainerMPushesYouBackToViolet` (`UP UP`) shoves you back toward Violet | -| Elm's Aide does not exist yet | `engine/events/std_scripts.asm:468` sets `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER`; `engine/phone/scripts/elm.asm:84` clears it | Falkner must be beaten so `maps/VioletGym.asm:38` `specialphonecall SPECIALCALL_ASSISTANT` arms the call, then the call must actually land | after the phone call, `VIOLETPOKECENTER1F_ELMS_AIDE` appears at `4, 3` | -| Miracle Seed | `maps/Route32.asm` `Route32CooltrainerMScript` | `ENGINE_ZEPHYRBADGE` AND `EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE` | both true -> `.GiveMiracleSeed` | -| Kabuto chamber floor | `maps/RuinsOfAlphKabutoChamber.asm` `RuinsOfAlphKabutoChamberHiddenDoorsCallback` (`MAPCALLBACK_TILES`) | `EVENT_SOLVED_KABUTO_PUZZLE` | solve `special UnownPuzzle` with `setval UNOWNPUZZLE_KABUTO` from the `BGEVENT_UP` at `3, 2` | -| Unown do not spawn at all | `engine/overworld/wildmons.asm:341-347` `cp UNOWN` / `ld a, [wUnlockedUnowns]` / `and a` / `jr z, .nowildbattle` | at least one `ENGINE_UNLOCKED_UNOWNS_*` bit set | solve any chamber puzzle | -| Only some Unown letters spawn | `engine/battle/core.asm:6219` `CheckUnownLetter` vs `data/wild/unlocked_unowns.asm` | the letter must be in an unlocked set | Kabuto -> A-K, Omanyte -> L-R, Aerodactyl -> S-W, Ho-Oh -> X-Z | -| Unown #DEX scientist | `maps/RuinsOfAlphOutside.asm` `RuinsOfAlphOutsideScientistCallback` | `ENGINE_UNOWN_DEX` clear AND `EVENT_MADE_UNOWN_APPEAR_IN_RUINS` set AND `VAR_UNOWNCOUNT > 2` | catch three distinct Unown forms, then walk onto `11, 14` or `10, 15` | -| Aerodactyl chamber | reachable only across the Ruins Outside water; `engine/events/overworld.asm:490` `ld de, ENGINE_FOGBADGE` / `call CheckEngineFlag`, then `ld d, SURF` / `call CheckPartyMove` | FOGBADGE + a party member knowing SURF | Chuck's badge, later game | -| Union Cave B1F boulder at `7, 10` | `maps/UnionCaveB1F.asm` `UnionCaveB1FBoulder` -> `jumpstd StrengthBoulderScript` -> `engine/events/std_scripts.asm:197` `farsjump AskStrengthScript` -> `engine/events/overworld.asm:1033` `TryStrengthOW`: `ld d, STRENGTH` / `CheckPartyMove`, then `ld de, ENGINE_PLAINBADGE` / `CheckEngineFlag` | PLAINBADGE + STRENGTH in the party | Whitney's badge + HM04 | -| Union Cave Lapras | `maps/UnionCaveB2F.asm` `UnionCaveB2FLaprasCallback` | `VAR_WEEKDAY == FRIDAY` and `ENGINE_UNION_CAVE_LAPRAS` clear | real-world Friday, once ever | -| Frieda / POISON_BARB | `maps/Route32.asm` `Route32FriedaCallback` + `FriedaScript` | `VAR_WEEKDAY == FRIDAY` | real-world Friday | -| Route 36 Sudowoodo | `maps/Route36.asm` `object_event 35, 9, SPRITE_WEIRD_TREE, ... SudowoodoScript, EVENT_ROUTE_36_SUDOWOODO` | out of scope for this section - the walkthrough says "ignore that for now" | next section | -| FLASH | **not required** - `data/maps/maps.asm:107-109` give all three Union Cave floors `PALETTE_NITE`, not `PALETTE_DARK` | none | n/a | - ---- - -## 4. Bot checklist - -Coordinates are `x, y` in the map's own cell space, as written in the asm. - -1. `VIOLET_CITY` -> step onto warp 5 at `31, 25`. - pre: `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` CLEAR (Elm's call landed). - post: on `VIOLET_POKECENTER_1F`. -2. `VIOLET_POKECENTER_1F` -> talk to `VIOLETPOKECENTER1F_ELMS_AIDE` at `4, 3`, - answer YES. - pre: `VAR_PARTYCOUNT < PARTY_LENGTH`. - post: `EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE` set; `ROUTE_32` scene = - `SCENE_ROUTE32_OFFER_SLOWPOKETAIL`. -3. `VIOLET_POKECENTER_1F` -> heal at `VIOLETPOKECENTER1F_NURSE` (`3, 1`), buy balls - at `VIOLET_MART` (Violet City warp 1 at `9, 17`). Then exit west out of Violet - City on the map connection into `ROUTE_36`. -4. `ROUTE_36` -> walk to warp 3/4 at `47, 13` / `48, 13`. - avoid: `object_event 35, 9` Sudowoodo, and trainers at `20, 12` / `31, 14`. - post: `ROUTE_36_RUINS_OF_ALPH_GATE`. -5. `ROUTE_36_RUINS_OF_ALPH_GATE` -> warp 3/4 at `4, 7` / `5, 7`. - post: `RUINS_OF_ALPH_OUTSIDE` at warp 9 (`7, 5`). -6. `RUINS_OF_ALPH_OUTSIDE` -> walk to warp 2 at `14, 7`. - avoid: `TrainerPsychicNathan` at `4, 20` (L26 Girafarig, sight 1) - it is far - south of this path, so no detour is needed. - post: `RUINS_OF_ALPH_KABUTO_CHAMBER`. -7. `RUINS_OF_ALPH_KABUTO_CHAMBER` -> stand at `3, 3` facing UP, press A on the - `BGEVENT_UP` at `3, 2`. Solve the sliding puzzle - (`special UnownPuzzle`, `setval UNOWNPUZZLE_KABUTO`). - post: `EVENT_SOLVED_KABUTO_PUZZLE`, `ENGINE_UNLOCKED_UNOWNS_A_TO_K`, - `EVENT_RUINS_OF_ALPH_INNER_CHAMBER_TOURISTS`, - `EVENT_RUINS_OF_ALPH_KABUTO_CHAMBER_RECEPTIONIST`, `RUINS_OF_ALPH_INNER_CHAMBER` - scene = `SCENE_RUINSOFALPHINNERCHAMBER_STRANGE_PRESENCE`; `warpcheck` drops you - through warp 3/4 into the Inner Chamber at `15, 3` / `16, 3`. -8. `RUINS_OF_ALPH_INNER_CHAMBER` -> the deferred scene prints "strange presence". - post: `EVENT_MADE_UNOWN_APPEAR_IN_RUINS`; scene reset to - `SCENE_RUINSOFALPHINNERCHAMBER_NOOP`. -9. `RUINS_OF_ALPH_INNER_CHAMBER` -> grind grass encounters (rate `6 percent`, - always `5 UNOWN`, letters A-K) and CATCH three distinct letters. - post: `VAR_UNOWNCOUNT >= 3`. -10. `RUINS_OF_ALPH_INNER_CHAMBER` -> warp 1 at `10, 13`. - post: `RUINS_OF_ALPH_OUTSIDE` at `10, 13`; `RuinsOfAlphOutsideScientistCallback` - runs on load and `appear`s the scientist + sets the scene. -11. `RUINS_OF_ALPH_OUTSIDE` -> step to `10, 15` (or `11, 14`). - pre: `VAR_UNOWNCOUNT > 2`, `ENGINE_UNOWN_DEX` clear. - post: cutscene runs; the script `applymovement`s you into the Research Center. - Do not drive movement during this - it uses `follow` / `stopfollow` / `warpcheck`. -12. `RUINS_OF_ALPH_RESEARCH_CENTER` -> the scene fires on entry; press through. - post: `ENGINE_UNOWN_DEX` set; scene reset to - `SCENE_RUINSOFALPHRESEARCHCENTER_NOOP`. -13. `RUINS_OF_ALPH_RESEARCH_CENTER` -> warp 1/2 at `2, 7` / `3, 7` back outside, then - walk south-east to warp 10/11 at `13, 20` / `13, 21`. - post: `ROUTE_32_RUINS_OF_ALPH_GATE`. -14. `ROUTE_32_RUINS_OF_ALPH_GATE` -> warp 3/4 at `9, 4` / `9, 5`. - post: `ROUTE_32` at `4, 2` / `4, 3`. -15. `ROUTE_32` -> optional: north connection into Violet City for the - `VioletCityFruitTree` at Violet City `14, 29` (PRZCUREBERRY, - `data/items/fruit_trees.asm` `; VIOLET_CITY`). -16. `ROUTE_32` -> talk to `ROUTE32_COOLTRAINER_M` at `19, 8`. - pre: `ENGINE_ZEPHYRBADGE` set AND `EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE` set. - post: `MIRACLE_SEED` in bag, `EVENT_GOT_MIRACLE_SEED_IN_ROUTE_32`. -17. `ROUTE_32` -> talk to `ROUTE32_FISHER5` at `15, 13`. - post: `TM_ROAR`, `EVENT_GOT_TM05_ROAR`. -18. `ROUTE_32` -> battle `TrainerYoungsterAlbert` (object at `13, 23`, sight 3). - post: `EVENT_BEAT_YOUNGSTER_ALBERT`. -19. `ROUTE_32` -> pick up the `POTION` itemball at `3, 30`. - post: `EVENT_ROUTE_32_POTION`. -20. `ROUTE_32` -> battle `TrainerPicnickerLiz1` (object at `10, 30`, sight 1), then - TALK TO HER AGAIN. - post: `EVENT_BEAT_PICNICKER_LIZ`, `EVENT_LIZ_ASKED_FOR_PHONE_NUMBER`, then - `PHONE_PICNICKER_LIZ` registered via `askforphonenumber`. -21. `ROUTE_32` -> hidden `SUPER_POTION` at `11, 40` (`BGEVENT_ITEM`, needs the - ITEMFINDER-style bump or a direct A press on the tile). - post: `EVENT_ROUTE_32_HIDDEN_SUPER_POTION`. -22. `ROUTE_32` -> battle `TrainerFisherJustin` (`8, 49`, sight 1), then - `TrainerFisherHenry` (`6, 48`, sight 1), then `TrainerFisherRalph1` - (`12, 56`, sight 3) and talk to Ralph again for `PHONE_FISHER_RALPH`. -23. `ROUTE_32` -> battle `TrainerCamperRoland` (`1, 56`, sight 4). -24. `ROUTE_32` -> pick up the `GREAT_BALL` itemball at `6, 53`. - post: `EVENT_ROUTE_32_GREAT_BALL`. -25. `ROUTE_32` -> battle `TrainerYoungsterGordon` (`4, 65`, `SPINCLOCKWISE`, sight 3). -26. `ROUTE_32` -> hidden `GREAT_BALL` at `12, 67`; on a Friday `ROUTE32_FRIEDA` - stands there, talk to her for `POISON_BARB`. - post: `EVENT_ROUTE_32_HIDDEN_GREAT_BALL`; optionally - `EVENT_GOT_POISON_BARB_FROM_FRIEDA`. -27. `ROUTE_32` -> step onto `7, 71` to fire `Route32WannaBuyASlowpokeTailScript` - (pre: scene == `SCENE_ROUTE32_OFFER_SLOWPOKETAIL`); answer either way. - post: scene = `SCENE_ROUTE32_NOOP`, one-shot done. -28. `ROUTE_32` -> warp 1 at `11, 73` into `ROUTE_32_POKECENTER_1F`; heal at `3, 1`; - talk to the Fishing Guru at `1, 4` and answer YES. - post: `OLD_ROD`, `EVENT_GOT_OLD_ROD`. -29. `ROUTE_32` -> battle `TrainerBirdKeeperPeter` (`11, 82`, sight 3), then warp 4 at - `6, 79`. - post: `UNION_CAVE_1F` at `17, 3`. -30. `UNION_CAVE_1F` -> `POTION` itemball at `4, 2`; battle `TrainerHikerDaniel` - (`4, 4`, `SPINRANDOM_FAST`, sight 1). - post: `EVENT_UNION_CAVE_1F_POTION`, `EVENT_BEAT_HIKER_DANIEL`. -31. `UNION_CAVE_1F` -> battle `TrainerHikerRussell` (`15, 8`, sight 3) and - `TrainerFirebreatherBill` (`15, 15`, sight 2). -32. `UNION_CAVE_1F` -> `X_ATTACK` itemball at `4, 17`; `GREAT_BALL` itemball at - `17, 21`; battle `TrainerPokemaniacLarry` (`4, 21`, sight 2). - post: `EVENT_UNION_CAVE_1F_X_ATTACK`, `EVENT_UNION_CAVE_1F_GREAT_BALL`, - `EVENT_BEAT_POKEMANIAC_LARRY`. -33. `UNION_CAVE_1F` -> warp 1 at `5, 19`. - post: `UNION_CAVE_B1F` at `7, 19`. -34. `UNION_CAVE_B1F` -> `TM_SWIFT` itemball at `2, 16`, then `X_DEFEND` itemball at - `17, 23`. - avoid: `TrainerHikerPhillip` `10, 4`, `TrainerHikerLeonard` `17, 10`, - `TrainerPokemaniacCalvin` `17, 30` (sight 3), `TrainerPokemaniacAndrew` `5, 32` - - all L23-26. - post: `EVENT_UNION_CAVE_B1F_TM_SWIFT`, `EVENT_UNION_CAVE_B1F_X_DEFEND`. -35. `UNION_CAVE_B1F` -> warp 4 at `3, 33`. - post: `UNION_CAVE_1F` at `3, 33`. -36. `UNION_CAVE_1F` -> `AWAKENING` itemball at `12, 33`; battle - `TrainerFirebreatherRay` (`16, 31`, sight 4); warp 3 at `17, 31`. - post: `EVENT_UNION_CAVE_1F_AWAKENING`, `EVENT_BEAT_FIREBREATHER_RAY`, - on `ROUTE_33` at `11, 9`. -37. `ROUTE_33` -> `Route33FruitTree` at `14, 16` for `PSNCUREBERRY`. -38. `ROUTE_33` -> battle `TrainerHikerAnthony` (`6, 13`, `SPINRANDOM_FAST`, sight 2), - then talk again for `PHONE_HIKER_ANTHONY`. - post: `EVENT_BEAT_HIKER_ANTHONY`, `EVENT_ANTHONY_ASKED_FOR_PHONE_NUMBER`. -39. `ROUTE_33` -> walk west across the map connection into `AZALEA_TOWN`. - Section ends here. - -Deferred steps (record as TODO, do not attempt this pass): - -- `RUINS_OF_ALPH_OUTSIDE` warp 4 at `16, 33` (Aerodactyl puzzle) - needs SURF + - `ENGINE_FOGBADGE`. -- `UNION_CAVE_B1F` boulder at `7, 10` -> `UNION_CAVE_B2F` - needs STRENGTH + - `ENGINE_PLAINBADGE`; then the Friday Lapras at B2F `11, 31` (needs SURF). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map data (warps / coord / bg / object events) for every map in this section | `src/import/RomExtractorGen2.lua` (`MapGroupPointers` walk, per-map `def_*` header parse around lines 878-987), consumed by `src/world/gen2/Map.lua` and `src/world/gen2/World.lua` | implemented - maps are extracted generically from the ROM, so nothing here is hand-authored per map | -| Warps / map connections | `src/world/gen2/World.lua`, `src/world/gen2/BorderFill.lua` | implemented | -| Coord-event trip-wires and `scene` bytes | `src/world/gen2/World.lua:5013` (coord event scan), `src/script/gen2/Vm.lua:274-296` (`setscene`, `checkscene`, `setmapscene`, `checkmapscene`) | implemented | -| Map callbacks (`MAPCALLBACK_OBJECTS` / `MAPCALLBACK_TILES`) - Frieda, Lapras, Ruins scientist, Kabuto floor | `src/world/gen2/World.lua:5659-5700` (`runMapCallback`, documented ordering at 5980-5984), `src/script/gen2/Vm.lua:1002` (`changeblock`) | implemented; driver `tests/drivers/gold_map_callbacks.lua` exercises callbacks generally, but not these maps specifically | -| `verbosegiveitem` / `giveitem` (Miracle Seed, TM05, Poison Barb, Old Rod) | `src/script/gen2/Vm.lua:490-498` | implemented | -| Itemballs (`OBJECTTYPE_ITEMBALL`) | `src/world/gen2/World.lua` object handling | implemented | -| Hidden items (`BGEVENT_ITEM` / `hiddenitem`) | `src/world/gen2/HiddenItems.lua`, `src/import/RomExtractorGen2.lua` | implemented | -| `giveegg` (Togepi EGG) + hatching | `src/script/gen2/Vm.lua:453`, `src/core/gen2/Breeding.lua`; driver `tests/drivers/gold_egg_hatch.lua` | implemented | -| Phone: `askforphonenumber` / `checkcellnum`, Ralph/Liz/Anthony contacts | `src/script/gen2/Vm.lua` (op present), `src/core/gen2/Phone.lua:172-179` has `[17] FISHER/RALPH1 ROUTE_32`, `[18] PICNICKER/LIZ1 ROUTE_32`, `[19] HIKER/ANTHONY2 ROUTE_33` | implemented | -| Elm's `SPECIALCALL_ASSISTANT` call that spawns the aide | `src/core/gen2/Phone.lua` | partial - the contact table is present; I did not find a `SPECIALCALL_*` special-call scheduler, so verify by hand that the aide actually appears | -| Unown puzzle (`special UnownPuzzle`, `setval UNOWNPUZZLE_*`) | `src/script/gen2/Specials.lua:919` (`H.UnownPuzzle`), UI in `src/ui/gen2/UnownPuzzle.lua` | implemented | -| Unown letter unlock sets / `wUnlockedUnowns` / letter-from-DVs | `src/core/gen2/Unown.lua` (ports `GetUnownLetter`, `CheckUnownLetter`, `UnlockedUnownLetterSets`, `NUM_UNOWN = 26`, `ENGINE_UNOWN_DEX = 12`) | implemented | -| `ChooseWildEncounter` cancelling Unown when `wUnlockedUnowns == 0` | `src/battle/gen2/Encounter.lua` | partial - `Encounter.lua` handles grass/water/fish tables; I did not verify the Unown short-circuit lives there. Verify by hand | -| Unown #DEX mode / Unown printer | `src/ui/gen2/PokedexMenu.lua`, `src/ui/gen2/UnownPrinter.lua` | implemented | -| Wild encounters, morn/day/nite split, encounter rates | `src/battle/gen2/Encounter.lua` | implemented | -| Fishing (Old Rod, fish groups) | `src/battle/gen2/Encounter.lua:79-111` (`Encounter.fish`, `Encounter.fishSlot`, map `fishGroup`) | implemented | -| Fruit trees (`fruittree FRUITTREE_ROUTE_33` -> PSNCUREBERRY) | `src/script/gen2/Vm.lua` (`fruittree` op), `src/core/gen2/Apricorns.lua:360` (`"PSNCUREBERRY", -- 06 FRUITTREE_ROUTE_33`) | implemented | -| Strength boulder (`jumpstd StrengthBoulderScript` -> `TryStrengthOW`) | `src/world/gen2/FieldMoves.lua:684-689` (documents the 0/1/2 `wScriptVar` result), badge map at :104-110 (`STRENGTH = "PLAIN"`) | implemented; driver `tests/drivers/gold_icepath_boulder.lua` covers a different map | -| Surf / FOGBADGE gate for the Aerodactyl chamber | `src/world/gen2/FieldMoves.lua:106` (`SURF = "FOG"`) | implemented | -| `ENGINE_UNION_CAVE_LAPRAS` flag id | `src/core/gen2/Apricorns.lua:84` (`{ id = 88, name = "ENGINE_UNION_CAVE_LAPRAS" }`) | implemented | -| `VAR_WEEKDAY` gating (Frieda, Lapras) | `src/script/gen2/Specials.lua`, `src/world/gen2/World.lua` | implemented | -| Ruins of Alph radio station (213.5) | `src/ui/gen2/Pokegear.lua:85, 799-802` (`UNOWN_RADIO`, landmark-gated to `LANDMARK_RUINS_OF_ALPH`) | implemented | -| Mom's shopping (Super Potion / Repel) | `src/core/gen2/MomShopping.lua`; asm table `data/items/mom_phone.asm` | implemented | -| End-to-end driver for this section | none | **missing** - `tests/drivers/gold_*.lua` has no Ruins of Alph, Route 32, Union Cave or Route 33 driver. The nearest are `gold_walk_smoke.lua` and `gold_trainer_smoke.lua` | - ---- - -## 6. Unresolved / verify by hand - -1. **"Repel" on Route 32.** The walkthrough lists Repel among Route 32 items and says - "Take the left path and get the Repel". `maps/Route32.asm` has no `REPEL` anywhere. - The only pickups on that fork are the `POTION` itemball at `3, 30` and the hidden - `SUPER_POTION` at `11, 40`. The Repel the player actually ends up with almost - certainly comes from `data/items/mom_phone.asm` `MomItems_2` (`momitem 4000, 270, - MOM_ITEM, REPEL`), which fits the walkthrough's own "Mom will buy something once - your savings reach certain thresholds" paragraph. Treat the Route 32 Repel as a - walkthrough error. - -2. **Ekans on Routes 32 and 33.** The walkthrough lists `#023 Ekans` for both routes. - `data/wild/johto_grass.asm` puts `EKANS` only in the `ELIF DEF(_SILVER)` branches; - the `IF DEF(_GOLD)` branches have `RATTATA` in that slot. This section's encounter - list is Silver's, not Gold's. - -3. **"Unown in its 28 forms ... plus two punctuation marks (? and !)."** - `constants/pokemon_constants.asm:308` is `DEF NUM_UNOWN EQU const_value - 1 ; 26`, - and the form constants stop at `UNOWN_Z`. The `?` and `!` forms are a Gen 3 - addition. The Research Center printer/computer both check `ifequal NUM_UNOWN`, so - the completion target in Gold is 26. - -4. **"Talk to the two people inside" the Kabuto chamber.** - `maps/RuinsOfAlphKabutoChamber.asm` defines exactly one `object_event` - (`RUINSOFALPHKABUTOCHAMBER_RECEPTIONIST` at `5, 5`). There is no second NPC. - -5. **`RuinsOfAlphOutside` declares five object consts but only two objects.** - `RUINSOFALPHOUTSIDE_FISHER`, `_YOUNGSTER2` and `_YOUNGSTER3` have no - `object_event` row. Either dead constants or a Crystal-era leftover; a port that - indexes objects by const must not assume the list is dense. - -6. **Route 32 grass Pokemon the walkthrough omits.** It lists Rattata, Ekans, Zubat, - Bellsprout, Mareep, Hoppip, Wooper. The Gold table also never yields Ekans (see 2), - and Wooper is morn/nite only - the day table has no Wooper at all. Similarly Union - Cave 1F/B1F contain `SANDSHREW` and `ONIX` in Gold, neither of which the - walkthrough's Union Cave list mentions. - -7. **"Some rando fisher will give you an Old Rod" - order.** The walkthrough puts the - Old Rod after Frieda and the Slowpoke-tail salesman. The Fishing Guru is inside - `ROUTE_32_POKECENTER_1F` (`1, 4`), reached by warp 1 at Route 32 `11, 73`, which is - south of Frieda (`12, 67`) and the salesman coord event (`7, 71`). Order is - consistent, but the guru is indoors, not "on the route". - -8. **"X Attack below Bill" / "the item to the far left ... a Potion".** - Firebreather Bill is at `15, 15`; the X Attack is at `4, 17` and the Potion at - `4, 2`. Both are far to the WEST, not directly below/left of Bill. The prose - directions are loose; use the coordinates. - -9. **`SPECIALCALL_*` scheduling in the port.** I could not find the mechanism that - arms and delivers `SPECIALCALL_ASSISTANT` (`maps/VioletGym.asm:38` -> - `engine/phone/scripts/elm.asm` `.assistant`) in this repo. `src/core/gen2/Phone.lua` - has the contact table but the special-call queue was not located. Without it, - `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` never clears, the aide never appears, - and Route 32 stays permanently blocked by the `18, 8` coord event - the single - hardest failure mode for a bot in this section. - -10. **The Unown short-circuit in `ChooseWildEncounter`.** `engine/overworld/wildmons.asm:341-347` - cancels the encounter entirely when `wUnlockedUnowns == 0`. I confirmed - `src/core/gen2/Unown.lua` ports the letter sets and `CheckUnownLetter`, but did - not confirm `src/battle/gen2/Encounter.lua` reproduces the "no puzzle solved -> - no encounter at all" branch. If it does not, a bot could meet Unown in the Inner - Chamber before solving any puzzle. - -11. **`Route32Pokecenter1FFishingGuruScript` has no bag-full guard.** Unlike - `Route32CooltrainerMScript` and `FriedaScript`, it does not `iffalse` after - `verbosegiveitem OLD_ROD` before `setevent EVENT_GOT_OLD_ROD`. If the KEY ITEMS - pocket were full the flag would be set without the rod. This is asm behaviour, - not a port bug - flagged so nobody "fixes" it. - -12. **Union Cave Lapras is once-ever, not once-weekly.** `UnionCaveLapras` sets - `ENGINE_UNION_CAVE_LAPRAS` after `startbattle` regardless of outcome, and - `UnionCaveB2FLaprasCallback` checks that flag before the weekday check. The - walkthrough's "only appears on Friday" is right about the weekday but does not - mention that fleeing or fainting loses it permanently. diff --git a/docs/gold-walkthrough/asm-walk/section-04-slowpoke-well-azalea-town-gym.md b/docs/gold-walkthrough/asm-walk/section-04-slowpoke-well-azalea-town-gym.md deleted file mode 100644 index f63be0ca..00000000 --- a/docs/gold-walkthrough/asm-walk/section-04-slowpoke-well-azalea-town-gym.md +++ /dev/null @@ -1,907 +0,0 @@ -# Section 04 - Slowpoke Well & Azalea Town Gym - -Source: `../section-04-slowpoke-well-azalea-town-gym.txt` -Maps covered: `MAP_AZALEA_TOWN`, `MAP_KURTS_HOUSE`, `MAP_AZALEA_POKECENTER_1F`, -`MAP_SLOWPOKE_WELL_B1F`, `MAP_SLOWPOKE_WELL_B2F` (off-route, Surf-only), -`MAP_AZALEA_GYM`. `MAP_AZALEA_MART` is referenced for its stock only -(`data/items/marts.asm` `MartAzalea`); its map file was not transcribed. -Badges / key milestones in this section: **HIVEBADGE** (`ENGINE_HIVEBADGE`), -`EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_BEAT_BUGSY`, `TM_FURY_CUTTER` (TM49), -`LURE_BALL`, `SUPER_POTION`, `WHT_APRICORN`, hidden `FULL_HEAL`. Clearing the -well is what unlocks the Gym door and arms the Azalea rival battle for the next -section. - -Coordinate convention throughout: the raw asm x/y from -`warp_event` / `coord_event` / `bg_event` / `object_event`, i.e. walk cells -(two 8x8 tiles per side, two cells per map block). `macros/scripts/maps.asm` -adds the +4 border internally; the numbers below are the pre-border values as -written in the map file. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | west edge of Route 33 (`connection east, Route33, ROUTE_33, 0` in `data/maps/attributes.asm`) | warp 4, `warp_event 9, 5, KURTS_HOUSE, 1` | Rocket guard is standing on the well; head west then north to Kurt's house | -| 2 | `MAP_KURTS_HOUSE` | `maps/KurtsHouse.asm` | warp 1 (`3, 7`) | warps 1/2 (`3, 7` / `4, 7`) back to `AZALEA_TOWN` warp 4 | `Kurt1` first-visit arm: Kurt storms off to the well and sets `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`, which hides the guard | -| 3 | `MAP_AZALEA_POKECENTER_1F` | `maps/AzaleaPokecenter1F.asm` | `AZALEA_TOWN` warp 1 (`15, 9`) | warps 1/2 (`3, 7` / `4, 7`) | "heal at the Pokemon Center" | -| 4 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | Pokecenter warp | warp 6, `warp_event 31, 7, SLOWPOKE_WELL_B1F, 1` | walk east to the now-unguarded well | -| 5 | `MAP_SLOWPOKE_WELL_B1F` | `maps/SlowpokeWellB1F.asm` | warp 1 (`17, 15`) | **scripted** `warp KURTS_HOUSE, 3, 3` at the end of `TrainerGruntM1.Script` | four Rocket grunts, Super Potion, tailless Slowpoke | -| 6 | `MAP_KURTS_HOUSE` | `maps/KurtsHouse.asm` | script warp to `3, 3` | warps 1/2 | `Kurt1.ClearedSlowpokeWell` -> free `LURE_BALL` | -| 7 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | Kurt's House warp | fruit tree object at `8, 2`, then warp 1 (Pokecenter) | `WHT_APRICORN` from `WhiteApricornTree` / `FRUITTREE_AZALEA_TOWN`, then heal | -| 8 | `MAP_AZALEA_GYM` | `maps/AzaleaGym.asm` | `AZALEA_TOWN` warp 5, `warp_event 10, 15, AZALEA_GYM, 1` | warps 1/2 (`4, 15` / `5, 15`) | five gym trainers + Bugsy -> HIVEBADGE + TM49 | - -Off-route in this section: `MAP_SLOWPOKE_WELL_B2F` (`maps/SlowpokeWellB2F.asm`) -hangs off `SLOWPOKE_WELL_B1F` warp 2 at `7, 11`, but the only land approach to -that ladder is across water (see the derived grid under B1F) - it is a Surf -return trip, not part of this visit, and holds the King's Rock and TM Rain Dance. - -Spills into the next section: `AzaleaTown_MapEvents` has -`coord_event 5, 10` / `coord_event 5, 11` for `SCENE_AZALEATOWN_RIVAL_BATTLE`. -`TrainerGruntM1.Script` arms that scene with -`setmapscene AZALEA_TOWN, SCENE_AZALEATOWN_RIVAL_BATTLE`, so the rival ambush -fires the first time you walk west toward the Ilex Forest gate. That battle and -Ilex Forest belong to section 05. - -## 2. Maps - -### MAP_AZALEA_TOWN - -- Script: `maps/AzaleaTown.asm` -- Blocks: `maps/AzaleaTown.blk` -- Header: `data/maps/maps.asm:232` -> `TILESET_JOHTO_MODERN`, `TOWN`, - `LANDMARK_AZALEA_TOWN`, `MUSIC_AZALEA_TOWN`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:213` -> `map_const AZALEA_TOWN, 20, 9` - (20x9 blocks = 40x18 walk cells) -- Connections (`data/maps/attributes.asm:132`): `map_attributes AzaleaTown, AZALEA_TOWN, $05`, - `connection west, Route34, ROUTE_34, -18`, `connection east, Route33, ROUTE_33, 0` -- Scene var: `data/maps/scenes.asm:37` -> `scene_var AZALEA_TOWN, wAzaleaTownSceneID` -- Spawn / fly landing: `data/maps/spawn_points.asm:31` -> `spawn AZALEA_TOWN, 15, 10`; - flypoint row `data/maps/flypoints.asm:8` -> `db LANDMARK_AZALEA_TOWN, SPAWN_AZALEA` -- Map scripts: `def_scene_scripts` = `AzaleaTownNoop1Scene` (`SCENE_AZALEATOWN_NOOP` = 0), - `AzaleaTownNoop2Scene` (`SCENE_AZALEATOWN_RIVAL_BATTLE` = 1); - `def_callbacks` = `callback MAPCALLBACK_NEWMAP, AzaleaTownFlypointCallback` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 9 | `AZALEA_POKECENTER_1F` | 1 | -| 2 | 21 | 13 | `CHARCOAL_KILN` | 1 | -| 3 | 21 | 5 | `AZALEA_MART` | 2 | -| 4 | 9 | 5 | `KURTS_HOUSE` | 1 | -| 5 | 10 | 15 | `AZALEA_GYM` | 1 | -| 6 | 31 | 7 | `SLOWPOKE_WELL_B1F` | 1 | -| 7 | 2 | 10 | `ILEX_FOREST_AZALEA_GATE` | 3 | -| 8 | 2 | 11 | `ILEX_FOREST_AZALEA_GATE` | 4 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 10 | `AzaleaTownRivalBattleScene1` | rival ambush, approach from the east; next section | -| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 11 | `AzaleaTownRivalBattleScene2` | same battle, second entry row; next section | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 19 | 9 | `BGEVENT_READ` | `AzaleaTownSign` | -| 10 | 9 | `BGEVENT_READ` | `KurtsHouseSign` | -| 14 | 15 | `BGEVENT_READ` | `AzaleaGymSign` | -| 29 | 7 | `BGEVENT_READ` | `SlowpokeWellSign` | -| 19 | 13 | `BGEVENT_READ` | `CharcoalKilnSign` | -| 16 | 9 | `BGEVENT_READ` | `AzaleaTownPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 22 | 5 | `BGEVENT_READ` | `AzaleaTownMartSign` (`jumpstd MartSignScript`) | -| 3 | 9 | `BGEVENT_READ` | `AzaleaTownIlextForestSign` | -| 31 | 6 | `BGEVENT_ITEM` | `AzaleaTownHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` | - -**Object events** (`def_object_events`) - `object_const_def` is `const_def 2`, so -the first const is 2 and row N carries const N+1. - -| const | sprite | x | y | movement (radius x,y / hours / pal) | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `AZALEATOWN_AZALEA_ROCKET1` | `SPRITE_AZALEA_ROCKET` | 31 | 9 | `STANDING_DOWN` 0,0 / -1,-1 / 0 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownRocket1Script` | `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` | -| 3 `AZALEATOWN_GRAMPS` | `SPRITE_GRAMPS` | 21 | 9 | `WANDER` 1,2 / -1,-1 / 0 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownGrampsScript` | -1 | -| 4 `AZALEATOWN_TEACHER` | `SPRITE_TEACHER` | 15 | 13 | `WALK_UP_DOWN` 0,2 / -1,-1 / `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownTeacherScript` | -1 | -| 5 `AZALEATOWN_YOUNGSTER` | `SPRITE_YOUNGSTER` | 7 | 9 | `WALK_LEFT_RIGHT` 1,0 / -1,-1 / `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownYoungsterScript` | -1 | -| 6 `AZALEATOWN_SLOWPOKE1` | `SPRITE_SLOWPOKE` | 8 | 17 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` | -| 7 `AZALEATOWN_SLOWPOKE2` | `SPRITE_SLOWPOKE` | 18 | 9 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` | -| 8 `AZALEATOWN_SLOWPOKE3` | `SPRITE_SLOWPOKE` | 29 | 9 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` | -| 9 `AZALEATOWN_SLOWPOKE4` | `SPRITE_SLOWPOKE` | 15 | 15 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` | -| 10 `AZALEATOWN_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 8 | 2 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `WhiteApricornTree` | -1 | -| 11 `AZALEATOWN_RIVAL` | `SPRITE_AZALEA_ROCKET` | 11 | 10 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT`, 0 | `ObjectEvent` | `EVENT_RIVAL_AZALEA_TOWN` | -| 12 `AZALEATOWN_AZALEA_ROCKET3` | `SPRITE_AZALEA_ROCKET` | 10 | 16 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownRocket2Script` | `EVENT_SLOWPOKE_WELL_ROCKETS` | - -`SPRITE_AZALEA_ROCKET` (`constants/sprite_constants.asm:156`, id `$f6`) is a -variable sprite. `engine/events/std_scripts.asm:539` seeds it as -`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_ROCKET`; `TrainerGruntM1.Script` -in `maps/SlowpokeWellB1F.asm` re-points it with -`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL`, which is why const 11 draws -as the rival even though the row names the Rocket slot. - -**Derived walkability** (from `maps/AzaleaTown.blk` + `data/tilesets/johto_modern_collision.asm` -+ `data/collision/collision_permissions.asm`; `.` land, `#` wall, `h` ledge hop, -`W` warp tile, columns are x, rows are y) - -``` - 0123456789012345678901234567890123456789 - 0 ###############################......... - 1 ######....#####################......... - 2 ######....#####################......... - 3 ######....############################## - 4 ######..################################ - 5 ######..#W###########W######......###### - 6 ######hhh.hh########....####..###.###### - 7 #########.##########....####.##W#.###### - 8 ##...#......######............#.#.###### - 9 ######....#.###W##.#..............###### - 10 ##W.........................hhh.hh###### - 11 ##W.....................#######.######## - 12 ######..######......########......###### - 13 ######..######.....##W######............ - 14 ######..######..........####............ - 15 ######..##W####.........####............ - 16 ######..............#################### - 17 ######..............#################### -``` - -Two chokepoints matter: `31, 9` is the only walkable cell that reaches `31, 8` -and then the well ladder at `31, 7`, and `10, 16` is the only walkable cell -adjacent to the Gym door at `10, 15` (`9, 15`, `11, 15` and `10, 14` are all -wall). The two Rocket objects sit exactly on those cells. - -**Scripts of interest** - -- `AzaleaTownFlypointCallback` - `MAPCALLBACK_NEWMAP`; `setflag ENGINE_FLYPOINT_AZALEA`, - `endcallback`. Entering the map once is enough to register the fly point. -- `AzaleaTownRocket1Script` / `AzaleaTownRocket2Script` - - `jumptextfaceplayer AzaleaTownRocket1Text` / `...Rocket2Text`. Pure flavour; - neither one gates anything by script. They block by standing on tiles. -- `AzaleaTownGrampsScript` - `checkevent EVENT_CLEARED_SLOWPOKE_WELL` / - `iftrue .ClearedWell`, then one of `AzaleaTownGrampsTextBefore` / - `AzaleaTownGrampsTextAfter`. Cheap read-only probe of well state. -- `WhiteApricornTree` - `fruittree FRUITTREE_AZALEA_TOWN`. - `constants/script_constants.asm:226` gives `FRUITTREE_AZALEA_TOWN ; 14` (hex), - and `data/items/fruit_trees.asm:23` is `db WHT_APRICORN ; AZALEA_TOWN`. - Once-per-day, tracked by the fruit-tree daily bit, not by an `EVENT_*`. -- `AzaleaTownHiddenFullHeal` - `hiddenitem FULL_HEAL, EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL`; - reached by facing the wall cell `31, 6` from `31, 7`/`31, 5` neighbours. -- `AzaleaTownRivalBattleScene1` / `...Scene2` / `AzaleaTownRivalBattleScript` - - next section. For completeness they `setevent EVENT_RIVAL_AZALEA_TOWN`, - branch on `EVENT_GOT_TOTODILE_FROM_ELM` / `EVENT_GOT_CHIKORITA_FROM_ELM`, - `loadtrainer RIVAL1, RIVAL1_2_TOTODILE|RIVAL1_2_CHIKORITA|RIVAL1_2_CYNDAQUIL`, - and end with `setscene SCENE_AZALEATOWN_NOOP`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_AZALEA` | `constants/engine_flags.asm:82` | set by `AzaleaTownFlypointCallback` | Fly destination unlocked on first entry | -| `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` | `constants/event_flags.asm:1180` | set by `Kurt1` (first-visit arm) and read by `KurtsGranddaughter.Lonely` | **set = the well guard at `31, 9` is hidden**; this is the well gate | -| `EVENT_SLOWPOKE_WELL_ROCKETS` | `constants/event_flags.asm:1182` | set by the `disappear` calls in `TrainerGruntM1.Script` | **set = the Rocket at `10, 16` is hidden**; this is the Gym gate | -| `EVENT_AZALEA_TOWN_SLOWPOKES` | `constants/event_flags.asm:1179` | `clearevent` in `TrainerGruntM1.Script` | cleared = the four town Slowpoke appear | -| `EVENT_CLEARED_SLOWPOKE_WELL` | `constants/event_flags.asm:52` | set in `TrainerGruntM1.Script`; read by `AzaleaTownGrampsScript`, `Kurt1`, `KurtsGranddaughter`, `KurtsHouseKurtCallback` | the section-4 milestone flag | -| `EVENT_RIVAL_AZALEA_TOWN` | `constants/event_flags.asm:1121` | set by `AzaleaTownRivalBattleScript` | next section | -| `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:187` | `hiddenitem` | one-shot hidden item | -| `SCENE_AZALEATOWN_NOOP` = 0, `SCENE_AZALEATOWN_RIVAL_BATTLE` = 1 | generated by `scene_script` in `macros/scripts/maps.asm:25` (`scene_const` + `EXPORT`) | `setscene` / `setmapscene` | scene id in `wAzaleaTownSceneID` | - -Object visibility semantics, since everything above depends on it: -`engine/overworld/scripting.asm:879` `Script_appear` calls -`ApplyEventActionAppearDisappear` with `b = 0 ; clear`, and `:887` -`Script_disappear` with `b = 1 ; set`. **An object's `EVENT_*` flag SET means the -object is hidden.** - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `WHT_APRICORN` | talk to the tree object at `8, 2` | `WhiteApricornTree` -> `fruittree FRUITTREE_AZALEA_TOWN`, `data/items/fruit_trees.asm:23` | none (daily respawn) | -| `FULL_HEAL` | hidden, face `31, 6` | `AzaleaTownHiddenFullHeal` bg_event | `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` | - -Azalea Mart stock (`data/items/marts.asm:71`, `MartAzalea`, 9 items): -`CHARCOAL`, `POKE_BALL`, `POTION`, `SUPER_POTION`, `ESCAPE_ROPE`, `REPEL`, -`ANTIDOTE`, `PARLYZ_HEAL`, `FLOWER_MAIL`. - -**Trainers** - -None on this map in this section (the rival at const 11 is section 05). - -**Wild encounters** - -`AZALEA_TOWN` has no entry in `data/wild/johto_grass.asm` or -`data/wild/johto_water.asm`. Fishing group is `FISHGROUP_SHORE` -(`data/maps/maps.asm:232`); headbutt set is -`data/wild/treemon_maps.asm:31` -> `treemon_map AZALEA_TOWN, TREEMON_SET_FOREST`. - ---- - -### MAP_KURTS_HOUSE - -- Script: `maps/KurtsHouse.asm` -- Blocks: `maps/KurtsHouse.blk` -- Header: `data/maps/maps.asm:229` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`, - `LANDMARK_AZALEA_TOWN`, `MUSIC_AZALEA_TOWN`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:210` -> `map_const KURTS_HOUSE, 8, 4` -- Connections: none (indoor) -- Map scripts: no scene scripts; `callback MAPCALLBACK_OBJECTS, KurtsHouseKurtCallback` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `AZALEA_TOWN` | 4 | -| 2 | 4 | 7 | `AZALEA_TOWN` | 4 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 6 | 1 | `BGEVENT_READ` | `KurtsHouseRadio` (`jumpstd Radio2Script`) | -| 8 | 0 | `BGEVENT_READ` | `KurtsHouseOakPhoto` | -| 9 | 0 | `BGEVENT_READ` | `KurtsHouseOakPhoto` | -| 5 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 2 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` | -| 3 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` | -| 4 | 1 | `BGEVENT_READ` | `KurtsHouseCelebiStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `KURTSHOUSE_KURT1` | `SPRITE_KURT` | 3 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `Kurt1` | `EVENT_KURTS_HOUSE_KURT_1` | -| 3 `KURTSHOUSE_TWIN` | `SPRITE_TWIN` | 5 | 3 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT`, 0 | `KurtsGranddaughter` | -1 | -| 4 `KURTSHOUSE_SLOWPOKE` | `SPRITE_SLOWPOKE` | 6 | 3 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `KurtsHouseSlowpoke` | `EVENT_KURTS_HOUSE_SLOWPOKE` | -| 5 `KURTSHOUSE_KURT2` | `SPRITE_KURT` | 14 | 3 | `STANDING_UP` | `OBJECTTYPE_SCRIPT`, 0 | `Kurt1` | `EVENT_KURTS_HOUSE_KURT_2` | - -Both Kurt objects run the same `Kurt1` script; the callback picks which one is -visible. - -**Derived walkability** (`maps/KurtsHouse.blk` + `data/tilesets/traditional_house_collision.asm`) - -``` - 0123456789012345 - 0 ################ - 1 #######...###### - 2 #...........#### - 3 .......##...#... - 4 .......##...#... - 5 ................ - 6 ................ - 7 ...WW........... -``` - -The post-well script warp lands you on `3, 3`, one cell south of `KURTSHOUSE_KURT1` -at `3, 2`, who faces down. Talk immediately, no walking required. - -**Scripts of interest** - -- `KurtsHouseKurtCallback` (`MAPCALLBACK_OBJECTS`) - - `checkevent EVENT_CLEARED_SLOWPOKE_WELL` / `iffalse .Done`; - then `checkflag ENGINE_KURT_MAKING_BALLS` / `iftrue .MakingBalls`. - Not making balls: `disappear KURTSHOUSE_KURT2`, `appear KURTSHOUSE_KURT1`. - Making balls: `disappear KURTSHOUSE_KURT1`, `appear KURTSHOUSE_KURT2` - (he moves to the workshop at `14, 3`). -- `Kurt1` - three arms, tested in this order: - 1. `checkevent EVENT_KURT_GAVE_YOU_LURE_BALL` / `iftrue .GotLureBall` - 2. `checkevent EVENT_CLEARED_SLOWPOKE_WELL` / `iftrue .ClearedSlowpokeWell` - 3. fallthrough = **first visit**: `writetext KurtsHouseKurtMakingBallsMustWaitText`, - `special FadeOutMusic`, `setevent EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`, - `readvar VAR_FACING` / `ifequal UP, .RunAround`, `turnobject PLAYER, DOWN`, - `playsound SFX_FLY`, - `applymovement KURTSHOUSE_KURT1, KurtsHouseKurtExitHouseMovement` - (5x `big_step DOWN`; the `.RunAround` variant is - `KurtsHouseKurtGoAroundPlayerThenExitHouseMovement`, `big_step RIGHT` then - 5x `big_step DOWN`), `disappear KURTSHOUSE_KURT1`, `special RestartMapMusic`. - **This single `setevent` is what removes the well guard.** - - `.ClearedSlowpokeWell`: `writetext KurtsHouseKurtHonoredToMakeBallsText`, - `promptbutton`, `verbosegiveitem LURE_BALL`, `iffalse .NoRoomForBall`, - `setevent EVENT_KURT_GAVE_YOU_LURE_BALL`, then falls into `.GotLureBall`. - - `.GotLureBall`: the apricorn loop. Checks `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_2`, - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`, then the seven - `EVENT_GAVE_KURT_<colour>_APRICORN` in order RED, BLU, YLW, GRN, WHT, BLK, PNK; - if none, `checkitem` the seven apricorns, `special SelectApricornForKurt`, - set the matching event, `setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`, - `setflag ENGINE_KURT_MAKING_BALLS`. WHT -> `verbosegiveitem FAST_BALL` - (`.GiveFastBall`) the next day. Only one apricorn is held at a time, which - is exactly the reader comment quoted in the walkthrough. -- `KurtsGranddaughter` - branch order `EVENT_FAST_SHIP_FIRST_TIME`, - `EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`. Read-only. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_KURTS_HOUSE_KURT_1` | `constants/event_flags.asm:1248` | `disappear`/`appear` in `Kurt1` + callback | set = house Kurt hidden (he is at the well) | -| `EVENT_KURTS_HOUSE_KURT_2` | `constants/event_flags.asm:1249` | callback; also `setevent` in `engine/events/std_scripts.asm:531` | set = workshop Kurt hidden | -| `EVENT_KURTS_HOUSE_SLOWPOKE` | `constants/event_flags.asm:1183` | `clearevent` in `TrainerGruntM1.Script` | cleared = the family Slowpoke is back | -| `EVENT_KURT_GAVE_YOU_LURE_BALL` | `constants/event_flags.asm:62` | set in `Kurt1.ClearedSlowpokeWell` | free ball already taken | -| `EVENT_GAVE_KURT_WHT_APRICORN` | `constants/event_flags.asm:263` | `Kurt1.Wht` / cleared in `.GiveFastBall` | apricorn currently in Kurt's hands | -| `ENGINE_KURT_MAKING_BALLS` | `constants/engine_flags.asm:97` (`wDailyFlags1`) | `setflag` in `.GaveKurtApricorns`, read by the callback and every `.Give*Ball` arm | the day-long wait; cleared by the daily reset, not by a script | -| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` / `_2` | `constants/event_flags.asm:5` / `:6` | `Kurt1` | per-visit conversation state, wiped on map reload | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `LURE_BALL` | talk to Kurt after clearing the well | `Kurt1.ClearedSlowpokeWell` -> `verbosegiveitem LURE_BALL` | `EVENT_KURT_GAVE_YOU_LURE_BALL` | -| `FAST_BALL` | give Kurt the `WHT_APRICORN`, return a day later | `Kurt1.GiveFastBall` -> `verbosegiveitem FAST_BALL` | clears `EVENT_GAVE_KURT_WHT_APRICORN` | - -**Trainers** - none. **Wild encounters** - none (indoor). - ---- - -### MAP_AZALEA_POKECENTER_1F - -- Script: `maps/AzaleaPokecenter1F.asm` -- Blocks: not present in `maps/` (shared Pokecenter layout) -- Header: `data/maps/maps.asm:226` -> `TILESET_POKECENTER`, `INDOOR`, - `LANDMARK_AZALEA_TOWN`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:207` -> `map_const AZALEA_POKECENTER_1F, 5, 4` -- Map scripts: `def_scene_scripts` = `AzaleaPokecenter1FNoopScene` (marked `; unusable`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `AZALEA_TOWN` | 1 | -| 2 | 4 | 7 | `AZALEA_TOWN` | 1 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events** / **BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `AZALEAPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 | -| 3 `AZALEAPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 9 | 6 | `WALK_UP_DOWN` 0,1 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FGentlemanScript` | -1 | -| 4 `AZALEAPOKECENTER1F_FISHING_GURU` | `SPRITE_FISHING_GURU` | 6 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FFishingGuruScript` | -1 | -| 5 `AZALEAPOKECENTER1F_POKEFAN_F` | `SPRITE_POKEFAN_F` | 1 | 4 | `WANDER` 1,2 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FPokefanFScript` | -1 | - -Healing is `jumpstd PokecenterNurseScript`; no flags are touched. - ---- - -### MAP_SLOWPOKE_WELL_B1F - -- Script: `maps/SlowpokeWellB1F.asm` -- Blocks: `maps/SlowpokeWellB1F.blk` -- Header: `data/maps/maps.asm:110` -> `TILESET_CAVE`, `CAVE`, - `LANDMARK_SLOWPOKE_WELL`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_NITE`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:97` -> `map_const SLOWPOKE_WELL_B1F, 10, 9` - (20x18 walk cells) -- Connections: none -- Map scripts: `def_scene_scripts` and `def_callbacks` are both empty - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 15 | `AZALEA_TOWN` | 6 | -| 2 | 7 | 11 | `SLOWPOKE_WELL_B2F` | 1 | - -**Coord events** / **BG events** - none. Nothing trips automatically on this map; -every beat is a trainer sight-line or an A press. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement / pal | type, sight | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `SLOWPOKEWELLB1F_ROCKET1` | `SPRITE_ROCKET` | 14 | 8 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, 1 | `TrainerGruntM29` | `EVENT_SLOWPOKE_WELL_ROCKETS` | -| 3 `SLOWPOKEWELLB1F_ROCKET2` | `SPRITE_ROCKET` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, 1 | `TrainerGruntM1` | `EVENT_SLOWPOKE_WELL_ROCKETS` | -| 4 `SLOWPOKEWELLB1F_ROCKET3` | `SPRITE_ROCKET` | 5 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, 2 | `TrainerGruntM2` | `EVENT_SLOWPOKE_WELL_ROCKETS` | -| 5 `SLOWPOKEWELLB1F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 13 | 2 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 2 | `TrainerGruntF1` | `EVENT_SLOWPOKE_WELL_ROCKETS` | -| 6 `SLOWPOKEWELLB1F_SLOWPOKE1` | `SPRITE_SLOWPOKE` | 7 | 4 | `STILL` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FSlowpokeWithMailScript` | `EVENT_SLOWPOKE_WELL_SLOWPOKES` | -| 7 `SLOWPOKEWELLB1F_SLOWPOKE2` | `SPRITE_SLOWPOKE` | 6 | 2 | `STILL` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FTaillessSlowpokeScript` | `EVENT_SLOWPOKE_WELL_SLOWPOKES` | -| 8 `SLOWPOKEWELLB1F_KURT` | `SPRITE_KURT` | 16 | 14 | `STANDING_UP` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FKurtScript` | `EVENT_SLOWPOKE_WELL_KURT` | -| 9 `SLOWPOKEWELLB1F_BOULDER` | `SPRITE_BOULDER` | 3 | 2 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 | -| 10 `SLOWPOKEWELLB1F_POKE_BALL` | `SPRITE_POKE_BALL` | 10 | 3 | `STILL` | `OBJECTTYPE_ITEMBALL`, 0 | `SlowpokeWellB1FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` | - -**Derived walkability** (`maps/SlowpokeWellB1F.blk` + `data/tilesets/cave_collision.asm` -+ `data/collision/collision_permissions.asm`; `~` = water, `L` = ladder/warp) - -``` - 01234567890123456789 - 1 .################... - 2 .#......#......##... - 3 .#..#...#......##... - 4 .#.#....#......##... - 5 .#.#....###.##.##... - 6 .#..#.......#...#... - 7 .##.##########..#... - 8 .#..#####....#..#... - 9 .#.######....#..#... - 10 .#.##........#..#... - 11 .#.##..L.#####..###. - 12 .#~~#....#~~~~....#. - 13 .#~~###.##~~~~....#. - 14 .#~~~~~~##~~~~....#. - 15 .#~~~~~~##~~~~...L#. -``` - -Route through the well, matching the walkthrough beat for beat: -`17, 15` (entry ladder) -> Kurt at `16, 14` -> north up the `14-15` column to -`14, 8`/`15, 8` where `TrainerGruntM29` sees you -> continue north to the top -room `9..14, 2..4`, where `TrainerGruntF1` at `13, 2` faces down with sight 2 -> -Super Potion ball at `10, 3` -> back east/down through the gap at `11, 5` into -the long row-6 corridor -> west to `TrainerGruntM2` at `5, 6` (faces right, -sight 2) -> Slowpoke with mail at `7, 4`, tailless Slowpoke at `6, 2` -> -`TrainerGruntM1` at `5, 2` (faces down, sight 1). - -The B2F ladder at `7, 11` sits in a chamber walled off along row 7 and reachable -only from `7, 13`, whose only non-wall neighbour outside the chamber is the water -at `7, 14`. **B2F is Surf-only**; the water is also the only route down from the -land at `2, 11`. - -**Scripts of interest** - -- `SlowpokeWellB1FKurtScript` - `jumptextfaceplayer SlowpokeWellB1FKurtText`. - Flavour only; Kurt does not gate anything here. -- `TrainerGruntM29` - `trainer GRUNTM, GRUNTM_29, EVENT_BEAT_ROCKET_GRUNTM_29, GruntM29SeenText, GruntM29BeatenText, 0, .Script`; - `.Script` is `endifjustbattled` + `GruntM29AfterBattleText`. Talking again is - the "they're chopping off Slowpoke tails" line the walkthrough mentions. -- `TrainerGruntF1` - `trainer GRUNTF, GRUNTF_1, EVENT_BEAT_ROCKET_GRUNTF_1, ...`, - same `endifjustbattled` shape. -- `TrainerGruntM2` - `trainer GRUNTM, GRUNTM_2, EVENT_BEAT_ROCKET_GRUNTM_2, ...`, - same shape. -- `TrainerGruntM1` - **the section's payload.** - `trainer GRUNTM, GRUNTM_1, EVENT_BEAT_ROCKET_GRUNTM_1, GruntM1SeenText, GruntM1BeatenText, 0, .Script`. - `.Script` has no `endifjustbattled`, so it runs immediately after the win: - `writetext TrainerGruntM1WhenTalkText`, `special FadeOutToBlack`, - `special ReloadSpritesNoPalettes`, - `disappear` on consts 2/3/4/5 (all four share `EVENT_SLOWPOKE_WELL_ROCKETS`, - so this is the flag that clears the Gym door in town), `pause 15`, - `special FadeInFromBlack`, `disappear SLOWPOKEWELLB1F_KURT`, - `moveobject SLOWPOKEWELLB1F_KURT, 11, 6`, `appear SLOWPOKEWELLB1F_KURT`, - `applymovement SLOWPOKEWELLB1F_KURT, KurtSlowpokeWellVictoryMovementData` - (4x `step LEFT`, `step UP`, 3x `step_sleep 8`, `step LEFT`, 2x `step UP`, - 3x `step_sleep 8`, `turn_head LEFT`), `turnobject PLAYER, RIGHT`, - `writetext KurtLeaveSlowpokeWellText`, then the flag block: - ``` - setevent EVENT_CLEARED_SLOWPOKE_WELL - variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL - setmapscene AZALEA_TOWN, SCENE_AZALEATOWN_RIVAL_BATTLE - clearevent EVENT_ILEX_FOREST_APPRENTICE - clearevent EVENT_ILEX_FOREST_FARFETCHD_1 - setevent EVENT_CHARCOAL_KILN_FARFETCH_D - setevent EVENT_CHARCOAL_KILN_APPRENTICE - setevent EVENT_SLOWPOKE_WELL_SLOWPOKES - setevent EVENT_SLOWPOKE_WELL_KURT - clearevent EVENT_AZALEA_TOWN_SLOWPOKES - clearevent EVENT_KURTS_HOUSE_SLOWPOKE - clearevent EVENT_KURTS_HOUSE_KURT_1 - ``` - then `special FadeOutToWhite`, `special HealParty`, `pause 15`, - `warp KURTS_HOUSE, 3, 3`, `end`. Note the free party heal, and note that the - Ilex Forest Farfetch'd quest is armed here, not in Ilex Forest. -- `SlowpokeWellB1FSlowpokeWithMailScript` - `faceplayer`, `cry SLOWPOKE`, - `SlowpokeWellB1FSlowpokeWithMailText`, `yesorno` / `iftrue .ReadMail` -> - `SlowpokeWellB1FSlowpokeMailText`. No flag, no item, purely optional. -- `SlowpokeWellB1FSuperPotion` - `itemball SUPER_POTION`, guarded by - `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION`. -- `SlowpokeWellB1FBoulder` - `jumpstd StrengthBoulderScript`; needs Strength, - which is not obtainable in this section. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_ROCKET_GRUNTM_29` | `constants/event_flags.asm:792` | `TrainerGruntM29` | grunt 1 beaten | -| `EVENT_BEAT_ROCKET_GRUNTF_1` | `constants/event_flags.asm:796` | `TrainerGruntF1` | grunt 2 beaten | -| `EVENT_BEAT_ROCKET_GRUNTM_2` | `constants/event_flags.asm:765` | `TrainerGruntM2` | grunt 3 beaten | -| `EVENT_BEAT_ROCKET_GRUNTM_1` | `constants/event_flags.asm:764` | `TrainerGruntM1` | grunt 4 (leader) beaten | -| `EVENT_SLOWPOKE_WELL_ROCKETS` | `constants/event_flags.asm:1182` | set via the four `disappear` calls | hides the well grunts **and** the Azalea Gym blocker | -| `EVENT_SLOWPOKE_WELL_SLOWPOKES` | `constants/event_flags.asm:1181` | `setevent` in `TrainerGruntM1.Script` | hides the two well Slowpoke | -| `EVENT_SLOWPOKE_WELL_KURT` | `constants/event_flags.asm:1250` | `setevent` in `TrainerGruntM1.Script` | hides well Kurt | -| `EVENT_CLEARED_SLOWPOKE_WELL` | `constants/event_flags.asm:52` | set here | section milestone | -| `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` | `constants/event_flags.asm:1027` | itemball | Super Potion taken | -| `EVENT_ILEX_FOREST_APPRENTICE` / `EVENT_ILEX_FOREST_FARFETCHD_1` | `constants/event_flags.asm:1173` / `:1163` | `clearevent` here | Farfetch'd quest NPCs become visible in Ilex Forest (section 05) | -| `EVENT_CHARCOAL_KILN_FARFETCH_D` / `EVENT_CHARCOAL_KILN_APPRENTICE` | `constants/event_flags.asm:1175` / `:1176` | `setevent` here | they leave the Charcoal Kiln | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `SUPER_POTION` | ball at `10, 3` | `SlowpokeWellB1FSuperPotion` | `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` | - -**Trainers** - -Money = `base reward` x level of the last enemy mon x 4. The x4 is -`engine/battle/read_trainer_party.asm:300` `ComputeTrainerReward` -(`base * wCurPartyLevel`) followed by the `ld c, 4` add loop and two -unconditional `call .DoubleReward` at `engine/battle/core.asm:2340-2361`. -`GRUNTM` / `GRUNTF` base reward is 10 (`data/trainers/attributes.asm:185`, `:395`). - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `GRUNTM_29` | `GRUNTM` | `constants/trainer_constants.asm:289` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L9 `RATTATA`, L9 `RATTATA` | `TrainerGruntM29` | no (360 = 10x9x4) | -| `GRUNTF_1` | `GRUNTF` | `constants/trainer_constants.asm:648` | `GruntFGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L9 `ZUBAT`, L11 `EKANS` | `TrainerGruntF1` | no (440 = 10x11x4) | -| `GRUNTM_2` | `GRUNTM` | `constants/trainer_constants.asm:262` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L7 `RATTATA`, L9 `ZUBAT`, L9 `ZUBAT` | `TrainerGruntM2` | no (360 = 10x9x4) | -| `GRUNTM_1` | `GRUNTM` | `constants/trainer_constants.asm:261` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L14 `KOFFING` | `TrainerGruntM1` | no (560 = 10x14x4) | - -**Wild encounters** - -- Grass/cave, `data/wild/johto_grass.asm:582` `def_grass_wildmons SLOWPOKE_WELL_B1F`, - `db 2 percent, 2 percent, 2 percent` - **identical morn/day/nite lists**: - L5 `ZUBAT`, L6 `ZUBAT`, L7 `ZUBAT`, L6 `SLOWPOKE`, L8 `ZUBAT`, L8 `SLOWPOKE`, - L8 `SLOWPOKE`. -- Water, `data/wild/johto_water.asm:33` `def_water_wildmons SLOWPOKE_WELL_B1F`, - `db 2 percent`: L15 `SLOWPOKE`, L20 `SLOWPOKE`, L10 `SLOWPOKE`. -- Fishing: `FISHGROUP_LAKE` (`data/wild/fish.asm:14`) -> - `.Lake_Old` L10 `MAGIKARP`/`MAGIKARP`/`GOLDEEN`, `.Lake_Good` L20 - `MAGIKARP`/`GOLDEEN`/`GOLDEEN` + `time_group 4`, `.Lake_Super` L40 - `GOLDEEN`/`time_group 5`/`MAGIKARP`/`SEAKING`. -- Headbutt: `data/wild/treemon_maps.asm:47` -> `treemon_map SLOWPOKE_WELL_B1F, TREEMON_SET_ROCK` - (rock smash set; no headbutt trees underground). - ---- - -### MAP_SLOWPOKE_WELL_B2F (off-route, Surf-gated) - -- Script: `maps/SlowpokeWellB2F.asm` -- Blocks: `maps/SlowpokeWellB2F.blk` -- Header: `data/maps/maps.asm:111` -> `TILESET_CAVE`, `CAVE`, - `LANDMARK_SLOWPOKE_WELL`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_NITE`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:98` -> `map_const SLOWPOKE_WELL_B2F, 10, 9` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 11 | `SLOWPOKE_WELL_B1F` | 2 | - -**Coord events** / **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `SLOWPOKEWELLB2F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 5 | 4 | `WANDER` 1,2 | `OBJECTTYPE_SCRIPT`, 1 | `SlowpokeWellB2FGymGuideScript` | -1 | -| 3 `SLOWPOKEWELLB2F_POKE_BALL` | `SPRITE_POKE_BALL` | 15 | 5 | `STILL` | `OBJECTTYPE_ITEMBALL`, 0 | `SlowpokeWellB2FTMRainDance` (`itemball TM_RAIN_DANCE`) | `EVENT_SLOWPOKE_WELL_B2F_TM_RAIN_DANCE` | - -`SlowpokeWellB2FGymGuideScript`: `checkevent EVENT_GOT_KINGS_ROCK_IN_SLOWPOKE_WELL` -/ `iftrue .GotKingsRock`, else `verbosegiveitem KINGS_ROCK`, `iffalse .NoRoom`, -`setevent EVENT_GOT_KINGS_ROCK_IN_SLOWPOKE_WELL` -(`constants/event_flags.asm:124`). - -Wild: `data/wild/johto_grass.asm:610` L19-23 `ZUBAT`/`SLOWPOKE`/`GOLBAT` -(same three time slots); `data/wild/johto_water.asm:40` is `4 percent` in Gold -(`IF DEF(_GOLD)`) / `2 percent` in Silver, L15 `SLOWPOKE`, L20 `SLOWPOKE`, -L20 `SLOWBRO`. - ---- - -### MAP_AZALEA_GYM - -- Script: `maps/AzaleaGym.asm` -- Blocks: `maps/AzaleaGym.blk` -- Header: `data/maps/maps.asm:230` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`, - `LANDMARK_AZALEA_TOWN`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:211` -> `map_const AZALEA_GYM, 5, 8` - (10x16 walk cells) -- Map scripts: `def_scene_scripts` and `def_callbacks` are both empty - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 15 | `AZALEA_TOWN` | 5 | -| 2 | 5 | 15 | `AZALEA_TOWN` | 5 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 13 | `BGEVENT_READ` | `AzaleaGymStatue` | -| 6 | 13 | `BGEVENT_READ` | `AzaleaGymStatue` | - -`AzaleaGymStatue`: `checkflag ENGINE_HIVEBADGE` / `iftrue .Beaten`; -unbeaten -> `jumpstd GymStatue1Script`, beaten -> -`gettrainername STRING_BUFFER_4, BUGSY, BUGSY1` + `jumpstd GymStatue2Script`. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement / pal | type, sight | script label | event flag | -|---|---|---|---|---|---|---|---| -| 2 `AZALEAGYM_BUGSY` | `SPRITE_BUGSY` | 5 | 7 | `SPINRANDOM_SLOW` / `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaGymBugsyScript` | -1 | -| 3 `AZALEAGYM_BUG_CATCHER1` | `SPRITE_BUG_CATCHER` | 5 | 3 | `SPINRANDOM_FAST` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 2 | `TrainerBugCatcherBenny` | -1 | -| 4 `AZALEAGYM_BUG_CATCHER2` | `SPRITE_BUG_CATCHER` | 8 | 8 | `STANDING_DOWN` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 3 | `TrainerBugCatcherAl` | -1 | -| 5 `AZALEAGYM_BUG_CATCHER3` | `SPRITE_BUG_CATCHER` | 0 | 2 | `STANDING_DOWN` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 3 | `TrainerBugCatcherJosh` | -1 | -| 6 `AZALEAGYM_TWIN1` | `SPRITE_TWIN` | 4 | 10 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 1 | `TrainerTwinsAmyandmay1` | -1 | -| 7 `AZALEAGYM_TWIN2` | `SPRITE_TWIN` | 5 | 10 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 1 | `TrainerTwinsAmyandmay2` | -1 | -| 8 `AZALEAGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaGymGuideScript` | -1 | - -None of the gym objects carry an event flag, so they are always drawn; being -"beaten" is entirely the `trainer` macro's own `EVENT_BEAT_*` check. - -**Derived walkability** (`maps/AzaleaGym.blk` + `data/tilesets/elite_four_room_collision.asm`) - -``` - 0123456789 - 0 ########## - 1 ########## - 2 .......... - 3 .......... - 4 ..##..##.. - 5 ..#....#.. - 6 .#..##..#. - 7 .#......#. - 8 ..#....#.. - 9 ..##..##.. - 10 .......... - 11 .......... - 12 ##.#..#.## - 13 ##.#..#.## - 14 ###.....## - 15 ###.WW..## -``` - -It is a spiral. Entry `4, 15` / `5, 15` -> `4, 14`..`7, 14` -> the two -entry stalks at columns 4-5 and 7 -> the row 10/11 ring -> up either side -column (0-1 or 8-9) -> row 2/3 -> in at `4, 4` / `5, 4` -> `4, 5`..`6, 5` -> -`3, 6` -> `3, 7` -> Bugsy at `5, 7`. - -Derived from the grid plus the sight fields: only the **twins** sit on the -straight-line path (`4, 11` and `5, 11` are their sight cells), and even they -are dodgeable by taking the column-7 stalk from `7, 14` up to `7, 11`. Josh's -sight covers `0, 3`-`0, 5` so the left column is his; Al's covers `8, 9`-`8, 11` -so the column-8 approach is his; column 9 is clear all the way from row 11 to -row 3. Benny spins (`SPINRANDOM_FAST`, sight 2) at `5, 3`, so crossing row 2/3 -near column 5 is a coin flip rather than a guarantee. - -**Scripts of interest** - -- `AzaleaGymBugsyScript` - - `faceplayer`, `opentext`, `checkevent EVENT_BEAT_BUGSY` / `iftrue .FightDone`; - otherwise `BugsyText_INeverLose`, - `winlosstext BugsyText_ResearchIncomplete, 0` (no loss text - losing is a - whiteout), `loadtrainer BUGSY, BUGSY1`, `startbattle`, `reloadmapafterbattle`, - `setevent EVENT_BEAT_BUGSY`, `Text_ReceivedHiveBadge`, - `playsound SFX_GET_BADGE`, `waitsfx`, **`setflag ENGINE_HIVEBADGE`**, - `readvar VAR_BADGES`, `scall AzaleaGymActivateRockets`. - `.FightDone`: `checkevent EVENT_GOT_TM49_FURY_CUTTER` / `iftrue .GotFuryCutter`, - then - and this is the bot-relevant part - - ``` - setevent EVENT_BEAT_TWINS_AMY_AND_MAY - setevent EVENT_BEAT_BUG_CATCHER_BENNY - setevent EVENT_BEAT_BUG_CATCHER_AL - setevent EVENT_BEAT_BUG_CATCHER_JOSH - ``` - so beating Bugsy retroactively marks the five gym trainers beaten. Then - `BugsyText_HiveBadgeSpeech`, `promptbutton`, - `verbosegiveitem TM_FURY_CUTTER`, `iffalse .NoRoomForFuryCutter`, - `setevent EVENT_GOT_TM49_FURY_CUTTER`, `BugsyText_FuryCutterSpeech`. -- `AzaleaGymActivateRockets` - `ifequal 7, .RadioTowerRockets`, - `ifequal 6, .GoldenrodRockets`, `end`. `VAR_BADGES` - (`constants/script_constants.asm:55`, id 7) is the badge count, which is 2 - right after HIVEBADGE, so **this scall is a no-op at Bugsy**; it is the shared - gym-clear hook that only fires at the 6th and 7th badge. -- `TrainerTwinsAmyandmay1` / `TrainerTwinsAmyandmay2` - both use - `EVENT_BEAT_TWINS_AMY_AND_MAY`, so you fight **one** twin, whichever spots you - first, and the other drops to her after-battle line. -- `TrainerBugCatcherBenny` / `...Al` / `...Josh` - plain - `trainer BUG_CATCHER, <id>, EVENT_BEAT_BUG_CATCHER_<id>, ...` with - `endifjustbattled` after-scripts. -- `AzaleaGymGuideScript` - `checkevent EVENT_BEAT_BUGSY`; the pre-battle text is - the "bug Pokemon don't like fire, flying-type moves are super-effective" hint. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BUGSY` | `constants/event_flags.asm:707` | `AzaleaGymBugsyScript` | leader beaten | -| `ENGINE_HIVEBADGE` | `constants/engine_flags.asm:39` (`wJohtoBadges`, `data/events/engine_flags.asm:47`) | `setflag` here; read by `CutFunction.CheckAble`, `TryCutOW`, `AzaleaGymStatue`, `engine/battle/core.asm:6566` (obedience) | **the badge that enables field Cut and L30 obedience** | -| `EVENT_GOT_TM49_FURY_CUTTER` | `constants/event_flags.asm:15` | `AzaleaGymBugsyScript` | TM49 already handed over | -| `EVENT_BEAT_TWINS_AMY_AND_MAY` | `constants/event_flags.asm:608` | both twin objects; force-set by `.FightDone` | one flag, two objects | -| `EVENT_BEAT_BUG_CATCHER_BENNY` | `constants/event_flags.asm:843` | `TrainerBugCatcherBenny`; force-set by `.FightDone` | | -| `EVENT_BEAT_BUG_CATCHER_AL` | `constants/event_flags.asm:844` | `TrainerBugCatcherAl`; force-set by `.FightDone` | | -| `EVENT_BEAT_BUG_CATCHER_JOSH` | `constants/event_flags.asm:845` | `TrainerBugCatcherJosh`; force-set by `.FightDone` | | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_FURY_CUTTER` (TM49) | Bugsy hands it over after the badge speech | `AzaleaGymBugsyScript.FightDone` -> `verbosegiveitem TM_FURY_CUTTER` | `EVENT_GOT_TM49_FURY_CUTTER` | - -**Trainers** - -Base rewards: `BUG_CATCHER` 4 (`data/trainers/attributes.asm:215`), -`TWINS` 5 (`:365`), `BUGSY` 25 (`:17`). Money = base x last-mon level x 4. - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `AMYANDMAY1` | `TWINS` | `constants/trainer_constants.asm:621` | `TwinsGroup` TWINS(1) "AMY & MAY@", `TRAINERTYPE_NORMAL`: L10 `SPINARAK`, L10 `LEDYBA` | `TrainerTwinsAmyandmay1` | no (200 = 5x10x4) | -| `AMYANDMAY2` | `TWINS` | `constants/trainer_constants.asm:624` | `TwinsGroup` TWINS(4) "AMY & MAY@", `TRAINERTYPE_NORMAL`: L10 `LEDYBA`, L10 `SPINARAK` | `TrainerTwinsAmyandmay2` | no (200) | -| `JOSH` | `BUG_CATCHER` | `constants/trainer_constants.asm:319` | `BugCatcherGroup` BUG_CATCHER(7) "JOSH@", `TRAINERTYPE_NORMAL`: L13 `PARAS` | `TrainerBugCatcherJosh` | no (208 = 4x13x4) | -| `BENNY` | `BUG_CATCHER` | `constants/trainer_constants.asm:317` | `BugCatcherGroup` BUG_CATCHER(5) "BENNY@", `TRAINERTYPE_NORMAL`: L7 `WEEDLE`, L9 `KAKUNA`, L12 `BEEDRILL` | `TrainerBugCatcherBenny` | no (192 = 4x12x4) | -| `AL` | `BUG_CATCHER` | `constants/trainer_constants.asm:318` | `BugCatcherGroup` BUG_CATCHER(6) "AL@", `TRAINERTYPE_NORMAL`: L12 `CATERPIE`, L12 `WEEDLE` | `TrainerBugCatcherAl` | no (192 = 4x12x4) | -| `BUGSY1` | `BUGSY` (class 3, `constants/trainer_constants.asm:33`) | `:34` | `BugsyGroup` "BUGSY@", **`TRAINERTYPE_MOVES`**: L14 `METAPOD` (`TACKLE`, `STRING_SHOT`, `HARDEN`), L14 `KAKUNA` (`POISON_STING`, `STRING_SHOT`, `HARDEN`), L16 `SCYTHER` (`QUICK_ATTACK`, `LEER`, `FURY_CUTTER`) | `AzaleaGymBugsyScript` | no (1600 = 25x16x4) | - -Bugsy's AI/items row (`data/trainers/attributes.asm:17`): `NO_ITEM, NO_ITEM`, -`AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`, -`CONTEXT_USE | SWITCH_SOMETIMES`. Bug Catchers are `AI_BASIC | AI_SETUP | AI_STATUS`; -Twins are `NO_AI` with `SWITCH_OFTEN`. - -**Wild encounters** - none (indoor). - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Rocket standing on the Slowpoke Well approach | `maps/AzaleaTown.asm` object const 2 at `31, 9`, flag `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`. `31, 9` is the only walkable neighbour of `31, 8`, which is the only walkable neighbour of the ladder at `31, 7` (derived from `maps/AzaleaTown.blk` + `data/tilesets/johto_modern_collision.asm`) | talk to Kurt once | `Kurt1` first-visit arm runs `setevent EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`, which hides him (`Script_disappear` semantics, `engine/overworld/scripting.asm:887`) | -| Rocket standing on the Azalea Gym door | `maps/AzaleaTown.asm` object const 12 at `10, 16`, flag `EVENT_SLOWPOKE_WELL_ROCKETS`. `10, 16` is the only walkable neighbour of the gym warp at `10, 15` | clear Slowpoke Well | the four `disappear SLOWPOKEWELLB1F_ROCKET*` calls in `maps/SlowpokeWellB1F.asm` `TrainerGruntM1.Script` set that shared flag | -| Slowpoke Well B2F (King's Rock, TM Rain Dance) | `maps/SlowpokeWellB1F.asm` warp 2 at `7, 11`; the chamber's only outside neighbour is water at `7, 14` (derived from `maps/SlowpokeWellB1F.blk` + `data/tilesets/cave_collision.asm` + `data/collision/collision_permissions.asm`) | Surf (`ENGINE_FOGBADGE` + HM03) | out of section - Fog Badge is Morty | -| Boulder at Slowpoke Well `3, 2` | `SlowpokeWellB1FBoulder` -> `jumpstd StrengthBoulderScript` | Strength | out of section; nothing behind it is required | -| Field Cut (leaving Azalea west for Ilex Forest) | `engine/events/overworld.asm` `CutFunction.CheckAble` (`ld de, ENGINE_HIVEBADGE` / `call CheckBadge` / `jr c, .nohivebadge`, sym `03:47e1`) and `TryCutOW` (`CheckPartyMove` with `CUT`, then `ld de, ENGINE_HIVEBADGE` / `CheckEngineFlag`, sym `03:5193`) | HIVEBADGE **and** a party member that knows CUT | HIVEBADGE from `AzaleaGymBugsyScript`; HM01 itself comes from the Charcoal Kiln / Ilex Forest chain in section 05 | -| Pokemon obedience above L30 | `engine/battle/core.asm:6566` masks `(1 << ZEPHYRBADGE) | (1 << HIVEBADGE) | ...` | HIVEBADGE | same | -| Bugsy himself | `AzaleaGymBugsyScript` has no `checkevent` guard before the battle | nothing - the five gym trainers are optional | `.FightDone` force-sets all four of their beat events afterwards | - -## 4. Bot checklist - -Coordinates are `x, y` as written in the asm. "warp N" means the Nth -`warp_event` row of the source map. - -1. `MAP_AZALEA_TOWN`, walk in from the Route 33 east connection. Post: engine - flag `ENGINE_FLYPOINT_AZALEA` set by `AzaleaTownFlypointCallback` - (`MAPCALLBACK_NEWMAP`). No input needed. -2. `MAP_AZALEA_TOWN`, walk to `9, 5` and step on it -> warp 4 -> - `MAP_KURTS_HOUSE` `3, 7`. Pre: none. -3. `MAP_KURTS_HOUSE`, walk to `3, 3`, face UP, press A on object const 2 - (`KURTSHOUSE_KURT1`, `3, 2`). Pre: `EVENT_CLEARED_SLOWPOKE_WELL` clear and - `EVENT_KURT_GAVE_YOU_LURE_BALL` clear. Post: - `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` set, `EVENT_KURTS_HOUSE_KURT_1` set - (Kurt walks out). If the player is facing UP when the script starts, Kurt - takes the `.RunAround` movement instead - same result, one extra step. -4. `MAP_KURTS_HOUSE`, step on `3, 7` or `4, 7` -> `MAP_AZALEA_TOWN` warp 4 (`9, 5`). -5. Optional heal: `MAP_AZALEA_TOWN` `15, 9` -> `MAP_AZALEA_POKECENTER_1F`; - talk to object const 2 at `3, 1` (`jumpstd PokecenterNurseScript`); leave via - `3, 7`. -6. `MAP_AZALEA_TOWN`, walk east along row 10 to `31, 10`, north to `31, 9` - (now empty), `31, 8`, then step on `31, 7` -> warp 6 -> `MAP_SLOWPOKE_WELL_B1F` - `17, 15`. Optional: from `31, 7` face UP and press A for the hidden - `FULL_HEAL` at `31, 6` (post: `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL`). -7. `MAP_SLOWPOKE_WELL_B1F`, optional A on const 8 (`16, 14`, Kurt). -8. Walk north along column 15 to `15, 8`. Trigger: const 2 `TrainerGruntM29` - (sight 1, faces right). Battle `GRUNTM` / `GRUNTM_29` (L9 Rattata x2). - Post: `EVENT_BEAT_ROCKET_GRUNTM_29`. Optional A again for the after-battle text. -9. Continue north column 14 to `14, 4`, then west into the top room. Trigger: - const 5 `TrainerGruntF1` at `13, 2` (sight 2, faces down) from `13, 3` or - `13, 4`. Battle `GRUNTF` / `GRUNTF_1` (L9 Zubat, L11 Ekans). Post: - `EVENT_BEAT_ROCKET_GRUNTF_1`. -10. Walk to `11, 3` -> `10, 3`, press A on const 10 -> `SUPER_POTION`. Post: - `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION`. -11. Go to `11, 4` -> `11, 5` -> `11, 6`, then west along row 6. Trigger: const 4 - `TrainerGruntM2` at `5, 6` (sight 2, faces right) from `7, 6` or `6, 6`. - Battle `GRUNTM` / `GRUNTM_2` (L7 Rattata, L9 Zubat, L9 Zubat). Post: - `EVENT_BEAT_ROCKET_GRUNTM_2`. -12. Optional: `7, 5` face UP, A on const 6 at `7, 4` (Slowpoke with mail, - `yesorno` -> read). No flag. -13. Walk to `5, 3`. Trigger: const 3 `TrainerGruntM1` at `5, 2` (sight 1, faces - down). Battle `GRUNTM` / `GRUNTM_1` (L14 Koffing). Post: - `EVENT_BEAT_ROCKET_GRUNTM_1`, then `.Script` runs with no `endifjustbattled`, - so the whole cutscene plays automatically and ends with - `special HealParty` + `warp KURTS_HOUSE, 3, 3`. Post-flags to assert: - `EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_SLOWPOKE_WELL_ROCKETS`, - `EVENT_SLOWPOKE_WELL_SLOWPOKES`, `EVENT_SLOWPOKE_WELL_KURT`, - `EVENT_CHARCOAL_KILN_FARFETCH_D`, `EVENT_CHARCOAL_KILN_APPRENTICE`; - cleared: `EVENT_AZALEA_TOWN_SLOWPOKES`, `EVENT_KURTS_HOUSE_SLOWPOKE`, - `EVENT_KURTS_HOUSE_KURT_1`, `EVENT_ILEX_FOREST_APPRENTICE`, - `EVENT_ILEX_FOREST_FARFETCHD_1`; scene: `AZALEA_TOWN` -> `SCENE_AZALEATOWN_RIVAL_BATTLE`. -14. `MAP_KURTS_HOUSE` at `3, 3`, face UP, A on const 2. Pre: - `EVENT_CLEARED_SLOWPOKE_WELL` set, `EVENT_KURT_GAVE_YOU_LURE_BALL` clear. - Post: `LURE_BALL` in bag, `EVENT_KURT_GAVE_YOU_LURE_BALL` set. The script - then falls straight into the apricorn prompt - answer no unless you want to - hand over the White Apricorn you have not picked yet. -15. Leave via `3, 7`; in `MAP_AZALEA_TOWN` walk to `8, 3`, face UP, A on const 10 - at `8, 2` -> `WHT_APRICORN` (daily). Optional: return to Kurt and give it - (`special SelectApricornForKurt`, post: `EVENT_GAVE_KURT_WHT_APRICORN` + - `ENGINE_KURT_MAKING_BALLS`), then come back a real in-game day later for - `FAST_BALL`. -16. Optional heal at the Pokecenter (`15, 9`). -17. `MAP_AZALEA_TOWN`, walk to `10, 17` -> `10, 16` (now empty) -> step on - `10, 15` -> warp 5 -> `MAP_AZALEA_GYM` `4, 15`. -18. `MAP_AZALEA_GYM`: optional A on const 8 at `7, 13` for the type hint. - Walk `4, 14` -> `4, 13` -> `4, 12` -> `4, 11`. Trigger: const 6 - `TrainerTwinsAmyandmay1` at `4, 10` (sight 1). Battle `TWINS` / `AMYANDMAY1` - (L10 Spinarak, L10 Ledyba). Post: `EVENT_BEAT_TWINS_AMY_AND_MAY`, which also - retires const 7. -19. West along row 11 to `0, 11`, north up column 0 to `0, 5`. Trigger: const 5 - `TrainerBugCatcherJosh` at `0, 2` (sight 3, faces down). Battle - `BUG_CATCHER` / `JOSH` (L13 Paras). Post: `EVENT_BEAT_BUG_CATCHER_JOSH`. -20. North to `0, 3`, east along row 3 toward `4, 3`. Trigger: const 3 - `TrainerBugCatcherBenny` at `5, 3` (sight 2, spins). Battle - `BUG_CATCHER` / `BENNY` (L7 Weedle, L9 Kakuna, L12 Beedrill). Post: - `EVENT_BEAT_BUG_CATCHER_BENNY`. -21. East along row 2/3 to column 8-9, south to `8, 11`. Trigger: const 4 - `TrainerBugCatcherAl` at `8, 8` (sight 3, faces down). Battle - `BUG_CATCHER` / `AL` (L12 Caterpie, L12 Weedle). Post: - `EVENT_BEAT_BUG_CATCHER_AL`. -22. Route into the middle: `4, 3` or `5, 3` row -> `4, 4` -> `4, 5` -> `3, 5` -> - `3, 6` -> `3, 7` -> `4, 7`, face RIGHT, A on const 2 at `5, 7`. Pre: - `EVENT_BEAT_BUGSY` clear. Battle `BUGSY` / `BUGSY1` (L14 Metapod, L14 Kakuna, - L16 Scyther). Post: `EVENT_BEAT_BUGSY`, `ENGINE_HIVEBADGE`, then the same - conversation continues and yields `TM_FURY_CUTTER` + - `EVENT_GOT_TM49_FURY_CUTTER` and force-sets the four gym-trainer events. - A bot that wants the badge fast can skip steps 19-21 entirely. -23. Leave via `4, 15` / `5, 15`. Next section starts when you walk west and hit - `coord_event 5, 10` / `5, 11`. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map data for all six maps (blocks, connections, warps, coord/bg/object events, `scriptKey`) | `src/import/RomExtractorGen2.lua` (`self:write("maps", out)`, line 987), consumed by `src/world/gen2/Map.lua` | implemented - `docs/gold-phase1.md` states all 368 maps extract | -| Collision / walkability (`COLL_*` quads, block -> cell) | `src/world/gen2/Map.lua` `Map:cellCollision`, `src/world/gen2/Permissions.lua` | implemented (quad order `ly*2 + lx` matches `gfx/tilesets.asm` `tilecoll`) | -| Warps between these maps | `src/world/gen2/World.lua` + `Map:warpAt`; driver `tests/drivers/gold_warp_scene.lua` | implemented | -| Map callbacks (`MAPCALLBACK_NEWMAP` flypoint, `MAPCALLBACK_OBJECTS` Kurt swap) | `src/world/gen2/World.lua`, driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| Script VM opcodes used here (`checkevent`/`setevent`/`clearevent`, `checkflag`/`setflag`, `readvar`, `scall`, `jumpstd`, `special`, `warp`, `setscene`/`setmapscene`, `loadtrainer`/`startbattle`/`winlosstext`/`endifjustbattled`, `applymovement`, `moveobject`, `showemote`, `variablesprite`, `verbosegiveitem`, `yesorno`, `checkitem`, `promptbutton`, `fruittree`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` | implemented - every opcode in this section's scripts has a row and a VM arm | -| Specials used here (`FadeOutToBlack`, `FadeInFromBlack`, `FadeOutToWhite`, `ReloadSpritesNoPalettes`, `HealParty`, `SelectApricornForKurt`, `FadeOutMusic`/`RestartMapMusic`) | `src/script/gen2/Specials.lua` | implemented (spot-checked `FadeOutToBlack`, `ReloadSpritesNoPalettes`, `HealParty`, `SelectApricornForKurt`) | -| Trainer sight lines / approach walk (the four grunts, the five gym trainers) | `src/world/gen2/Trainers.lua` (`Trainers.sees`, `Trainers.approach`), wired at `src/world/gen2/World.lua:5212-5264`; driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| Trainer parties + class attributes | `src/import/RomExtractorGen2.lua` (trainers), `src/world/gen2/Trainers.lua`, `src/battle/gen2/Battle.lua` (`BUGSY` in the gym-leader set, line 85) | implemented | -| Prize money (base x level x 4, Mom's split) | `src/battle/gen2/Prize.lua` | implemented, and it ports the `ld c, 4` loop and `.DoubleReward` faithfully | -| Badge storage + `VAR_BADGES` count | `src/core/gen2/Save.lua` (`player.badges`, `player.kantoBadges`), `src/world/gen2/World.lua:1241-1244`, `World:engineFlags` (`:1304`) | implemented | -| Cut gate on `ENGINE_HIVEBADGE` | `src/world/gen2/FieldMoves.lua` (`FieldMoves.BADGE.CUT = "HIVE"`, `cutFromMenu`, the overworld ask path) | implemented | -| Fly point `ENGINE_FLYPOINT_AZALEA` | `src/world/gen2/FieldMoves.lua:347` (`LANDMARK_AZALEA_TOWN` / `SPAWN_AZALEA` / flag 67) | implemented | -| Hidden item at `31, 6` (`BGEVENT_ITEM`) | `src/world/gen2/HiddenItems.lua` | implemented (module exists and the extractor carries `hiddenItem = { item, event }`); not exercised by any Azalea driver | -| Kurt / apricorn conversation, `ENGINE_KURT_MAKING_BALLS` day wait, Lure Ball, Fast Ball | `src/core/gen2/Apricorns.lua` | implemented at the model level; the file is written against `maps/KurtsHouse.asm` directly and lists `FRUITTREE_AZALEA_TOWN` -> `WHT_APRICORN` at index `0x14` | -| Item balls (`OBJECTTYPE_ITEMBALL`: Super Potion at B1F `10, 3`, TM Rain Dance at B2F `15, 5`) | extracted by `src/import/RomExtractorGen2.lua:2968`; **no runtime handler found in `src/world/gen2/World.lua`** | **missing** - the object data is there, nothing picks it up | -| `fruittree` at runtime (White Apricorn tree object) | opcode row exists (`src/script/gen2/Opcodes.lua`, `[0x9a]`) and `Vm.lua` references it; the daily-tree store is described in `src/core/gen2/Apricorns.lua` (`save.fruitTrees`) | partial - modelled, not verified end to end in a driver | -| Wild encounters for Slowpoke Well (grass 3-slot time split, water, fishing) | `src/import/RomExtractorGen2.lua` (Encounters), `src/battle/gen2/Encounter.lua` | implemented per `docs/gold-phase1.md`; not verified for this map specifically | -| A driver that walks any part of this section | none - `tests/drivers/gold_*.lua` has nothing matching azalea / slowpoke / kurt / bugsy | **missing** | - -## 6. Unresolved / verify by hand - -- **Bug Catcher Al's prize.** The walkthrough says 200G. The asm gives - 4 (`data/trainers/attributes.asm:215`) x 12 (last mon, L12 Weedle) x 4 = **192G**. - Every other figure in the section matches the formula exactly - (360 / 440 / 360 / 560 / 200 / 208 / 192 / 1600), so this looks like a - walkthrough typo, not a special case. -- **Bugsy's gender.** The walkthrough says "Bugsy and her apprentices" and - "she may have the Bug Pokemon". `AzaleaGymGuideText` in `maps/AzaleaGym.asm` - says "BUGSY's young, but **his** knowledge of bug #MON is for real". The asm - wins. -- **"Liz the Picnicker calls about Moo Moo Milk."** `LizPhoneCallerScript` - (`engine/phone/scripts/trainers.asm:234-254`) only does - `PhoneScript_GreetPhone_Female`, a `checkflag ENGINE_FLYPOINT_GOLDENROD` - rematch gate, `PhoneScript_Random2` and `Phone_GenericCall_Female` / - `Phone_CheckIfUnseenRare_Female`. There is no Moo Moo Milk text in her script, - and no `MOOMOO` string turned up anywhere under `engine/phone/`. The generic - call pool may contain such a line, but it is not attributable to Liz from the - map/phone scripts - unresolved. -- **"TM49" listed under *Items found in Azalea Town*.** TM49 is - `verbosegiveitem TM_FURY_CUTTER` inside `AzaleaGymBugsyScript`, i.e. found in - `MAP_AZALEA_GYM`, not on the town map. Cosmetic, but a bot indexing by map - should not look for it outdoors. -- **Battle order inside Slowpoke Well.** The walkthrough's "move on up the - stairs", "head down the stairs" prose does not correspond to any `LADDER` or - `STAIRCASE` collision inside B1F - the only two ladder cells are the two warps - (`17, 15` and `7, 11`). The trainer order it describes (M29 -> F1 -> M2 -> M1) - does match the geometry, so the "stairs" are just prose for the corridors. -- **`SLOWPOKE_WELL_B2F` reachability.** I derived "Surf only" from - `maps/SlowpokeWellB1F.blk` decoded against `data/tilesets/cave_collision.asm` - and `data/collision/collision_permissions.asm`; no script or engine check - states it. If a future reader wants a stronger citation, it would have to come - from the block data, not from an `EVENT_*`. -- **Gym-trainer skippability.** The claim that Josh, Al and (via the column-7 - stalk) the twins can be walked past is derived from `maps/AzaleaGym.blk` plus - each object's sight field, not asserted anywhere in the asm. The retroactive - `setevent` block in `AzaleaGymBugsyScript.FightDone` is direct asm evidence - that skipping them is an anticipated state, but the exact dodge path should be - confirmed in-game before a driver depends on it. -- **`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL`.** This is why - `AZALEATOWN_RIVAL` is declared with `SPRITE_AZALEA_ROCKET` in - `maps/AzaleaTown.asm`. It also means consts 2 and 12 on that map would draw as - the rival if they were still visible after the well is cleared - they are not, - because both are hidden by then. Worth a look if a port ever renders them. diff --git a/docs/gold-walkthrough/asm-walk/section-05-ilex-forest-and-goldenrod-city-gym.md b/docs/gold-walkthrough/asm-walk/section-05-ilex-forest-and-goldenrod-city-gym.md deleted file mode 100644 index 9383a40c..00000000 --- a/docs/gold-walkthrough/asm-walk/section-05-ilex-forest-and-goldenrod-city-gym.md +++ /dev/null @@ -1,1309 +0,0 @@ -# Section 05 - Ilex Forest and Goldenrod City Gym - -Source: `../section-05-ilex-forest-and-goldenrod-city-gym.txt` - -Maps covered: `AZALEA_TOWN` (west exit only), `ILEX_FOREST_AZALEA_GATE`, -`ILEX_FOREST`, `ROUTE_34_ILEX_FOREST_GATE`, `ROUTE_34`, `DAY_CARE`, -`GOLDENROD_CITY`, `GOLDENROD_POKECENTER_1F`, `GOLDENROD_DEPT_STORE_1F` .. -`GOLDENROD_DEPT_STORE_6F`, `BILLS_FAMILYS_HOUSE`, -`GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`, `GOLDENROD_UNDERGROUND`, -`GOLDENROD_BIKE_SHOP`, `GOLDENROD_MAGNET_TRAIN_STATION`, `RADIO_TOWER_1F`, -`GOLDENROD_GAME_CORNER`, `GOLDENROD_GYM`. - -Badges / key milestones in this section: - -- Rival battle 2 (Azalea Town), `EVENT_RIVAL_AZALEA_TOWN` -- `HM_CUT` (HM01) from the charcoal master, `EVENT_GOT_HM01_CUT` -- `TM_HEADBUTT` (TM02), `TM_SWEET_SCENT` (TM12) -- `BICYCLE`, `COIN_CASE`, `ENGINE_RADIO_CARD` -- PLAINBADGE (`ENGINE_PLAINBADGE`) + `TM_ATTRACT` (TM45) from Whitney - -Conventions used below are the ones in `_TEMPLATE.md`. Disassembly paths are -relative to the pokegold checkout root; port paths are relative to this repo -root. All coordinates are raw asm values (map coordinates, 2 per map block). - -Note on `SCENE_*` names: pokegold has no `constants/map_scenes.asm`. The -`scene_script` macro (`macros/scripts/maps.asm`) defines each scene constant at -the point of use, so `SCENE_AZALEATOWN_RIVAL_BATTLE` is declared by -`maps/AzaleaTown.asm` itself. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `AZALEA_TOWN` | `maps/AzaleaTown.asm` | out of `AZALEA_GYM` (warp 5 at 10,15) | warps 7/8 at (2,10)/(2,11) -> `ILEX_FOREST_AZALEA_GATE` 3/4 | heal, walk west, rival trip-wire at (5,10)/(5,11) | -| 2 | `ILEX_FOREST_AZALEA_GATE` | `maps/IlexForestAzaleaGate.asm` | warps 3/4 at (9,4)/(9,5) | warps 1/2 at (0,4)/(0,5) -> `ILEX_FOREST` 2/3 | gate house, two flavour NPCs | -| 3 | `ILEX_FOREST` | `maps/IlexForest.asm` | warps 2/3 at (3,42)/(3,43) | warp 1 at (1,5) -> `ROUTE_34_ILEX_FOREST_GATE` 3 | herd Farfetch'd, get HM01, cut the tree, TM02, Revive | -| 4 | `ROUTE_34_ILEX_FOREST_GATE` | `maps/Route34IlexForestGate.asm` | warps 3/4 at (4,7)/(5,7) | warps 1/2 at (4,0)/(5,0) -> `ROUTE_34` 1/2 | teacher hands over TM12 Sweet Scent | -| 5 | `ROUTE_34` | `maps/Route34.asm` | warps 1/2 at (13,37)/(14,37) | north map connection -> `GOLDENROD_CITY` (offset -5) | Samuel, Brandon, Ian, Gina, Keith, Todd; Day-Care | -| 6 | `DAY_CARE` | `maps/DayCare.asm` | `ROUTE_34` warps 3/4/5 at (11,14)/(11,15)/(13,15) | warps 1..4 back to `ROUTE_34` | deposit / breed, egg pickup | -| 7 | `GOLDENROD_CITY` | `maps/GoldenrodCity.asm` | south map connection from `ROUTE_34` (offset 5) | see the city's 15 warps | hub for everything below | -| 8 | `GOLDENROD_POKECENTER_1F` | `maps/GoldenrodPokecenter1F.asm` | city warp 7 at (15,27) | warps 1/2 at (3,7)/(4,7) | heal | -| 9 | `GOLDENROD_DEPT_STORE_1F`..`6F` | `maps/GoldenrodDeptStore1F.asm` .. `6F.asm` | city warp 10 at (24,27) | stairs / elevator | shopping, Sunday TM lady on 5F | -| 10 | `BILLS_FAMILYS_HOUSE` | `maps/BillsFamilysHouse.asm` | city warp 4 at (5,25) | warps 1/2 at (2,7)/(3,7) | Bill's phone number from the younger sister | -| 11 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` | city warp 15 at (11,29) -> warp 5 at (4,29) | warp 4 at (5,25) -> `GOLDENROD_UNDERGROUND` 2 | the "house below Bill's" is the south underground stair | -| 12 | `GOLDENROD_UNDERGROUND` | `maps/GoldenrodUnderground.asm` | warp 2 at (3,34) | warp 1 at (3,2) -> switch-room warp 7 at (21,25) | Eric, Issac, Coin Case, salon, Teru, Donald, locked basement door | -| 13 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` (north half) | as above | warp 7 at (21,25) | warps 8/9 at (20,29)/(21,29) -> `GOLDENROD_CITY` 14 at (9,5) | come back up on the north side of town | -| 14 | `GOLDENROD_BIKE_SHOP` | `maps/GoldenrodBikeShop.asm` | city warp 2 at (29,29) | warps 1/2 at (2,7)/(3,7) | borrow the `BICYCLE` | -| 15 | `GOLDENROD_MAGNET_TRAIN_STATION` | `maps/GoldenrodMagnetTrainStation.asm` | city warp 5 at (9,13) | warps 1/2 at (8,17)/(9,17) | conductor flavour text, no train yet | -| 16 | `RADIO_TOWER_1F` | `maps/RadioTower1F.asm` | city warp 12 at (5,15) | warps 1/2 at (2,7)/(3,7) | Lucky Number man + Radio Card quiz | -| 17 | `GOLDENROD_GAME_CORNER` | `maps/GoldenrodGameCorner.asm` | city warp 11 at (14,21) | warps 1/2 at (2,13)/(3,13) | slots, prize corner (needs `COIN_CASE`) | -| 18 | `GOLDENROD_GYM` | `maps/GoldenrodGym.asm` | city warp 1 at (24,7) | warps 1/2 at (2,17)/(3,17) | Victoria, Samantha, Carrie, Bridget, Whitney | - -Spills into the next section: after PLAINBADGE the walkthrough's next hop is -north out of Goldenrod through `ROUTE_35_GOLDENROD_GATE` (city warp 13 at -(19,1)); that map and Route 35 belong to the following section. - ---- - -## 2. Maps - -### MAP_AZALEA_TOWN (west exit / rival battle only) - -- Script: `maps/AzaleaTown.asm` -- Blocks: `maps/AzaleaTown.blk` -- Header: `data/maps/maps.asm:232` -> `TILESET_JOHTO_MODERN, TOWN, LANDMARK_AZALEA_TOWN, MUSIC_AZALEA_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:213` -> `map_const AZALEA_TOWN, 20, 9` -- Connections (`data/maps/attributes.asm:132`): west `Route34` (offset -18), east `Route33` (offset 0) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 9 | `AZALEA_POKECENTER_1F` | 1 | -| 2 | 21 | 13 | `CHARCOAL_KILN` | 1 | -| 3 | 21 | 5 | `AZALEA_MART` | 2 | -| 4 | 9 | 5 | `KURTS_HOUSE` | 1 | -| 5 | 10 | 15 | `AZALEA_GYM` | 1 | -| 6 | 31 | 7 | `SLOWPOKE_WELL_B1F` | 1 | -| 7 | 2 | 10 | `ILEX_FOREST_AZALEA_GATE` | 3 | -| 8 | 2 | 11 | `ILEX_FOREST_AZALEA_GATE` | 4 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 10 | `AzaleaTownRivalBattleScene1` | rival is teleported to (11,11) first, then walks 6 left | -| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 11 | `AzaleaTownRivalBattleScene2` | rival walks 6 left from his placed position | - -**BG events** (`def_bg_events`) - the ones on the west path - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 9 | `BGEVENT_READ` | `AzaleaTownIlextForestSign` | -| 31 | 6 | `BGEVENT_ITEM` | `AzaleaTownHiddenFullHeal` (`FULL_HEAL`, `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL`) | - -(The other seven bg events - town sign, Kurt's house, gym, Slowpoke Well, -charcoal kiln, Pokecenter, mart - are listed verbatim in the asm and belong to -the previous section.) - -**Object events** (`def_object_events`) - rival row only - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `AZALEATOWN_RIVAL` | `SPRITE_AZALEA_ROCKET` | 11 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_AZALEA_TOWN` | - -`SPRITE_AZALEA_ROCKET` is remapped to `SPRITE_RIVAL` by -`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL` in -`maps/SlowpokeWellB1F.asm` (line 59), so the object draws as the rival. - -**Scripts of interest** - -- `AzaleaTownRivalBattleScene1` / `AzaleaTownRivalBattleScene2` -> - `AzaleaTownRivalBattleScript`: `playmusic MUSIC_RIVAL_ENCOUNTER`, before-text, - `setevent EVENT_RIVAL_AZALEA_TOWN`, then branches on the starter: - `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `loadtrainer RIVAL1, RIVAL1_2_CHIKORITA`; - `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `RIVAL1_2_CYNDAQUIL`; - otherwise (player took Cyndaquil) -> `RIVAL1_2_TOTODILE`. - `winlosstext AzaleaTownRivalWinText, AzaleaTownRivalLossText`, - `setlasttalked AZALEATOWN_RIVAL`, `startbattle`, `reloadmapafterbattle`. -- `.AfterBattle`: `MUSIC_RIVAL_AFTER`, after-text, rival walks 3 left, - `disappear AZALEATOWN_RIVAL`, `setscene SCENE_AZALEATOWN_NOOP`. -- Arming: `maps/SlowpokeWellB1F.asm` (the Slowpoke Well clear scene) runs - `setevent EVENT_CLEARED_SLOWPOKE_WELL`, - `setmapscene AZALEA_TOWN, SCENE_AZALEATOWN_RIVAL_BATTLE`, - `clearevent EVENT_ILEX_FOREST_APPRENTICE`, - `clearevent EVENT_ILEX_FOREST_FARFETCHD_1`, - `setevent EVENT_CHARCOAL_KILN_FARFETCH_D`, - `setevent EVENT_CHARCOAL_KILN_APPRENTICE`. So the Ilex Forest Farfetch'd quest - and the Azalea rival battle are both armed by clearing Slowpoke Well, not by - the Hive Badge. -- `AzaleaTownFlypointCallback` (`MAPCALLBACK_NEWMAP`): `setflag ENGINE_FLYPOINT_AZALEA`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `SCENE_AZALEATOWN_RIVAL_BATTLE` | `maps/AzaleaTown.asm:17` (`scene_script`) | set by `maps/SlowpokeWellB1F.asm:58`, cleared to `SCENE_AZALEATOWN_NOOP` by `.AfterBattle` | trip-wire armed / disarmed | -| `EVENT_RIVAL_AZALEA_TOWN` | `constants/event_flags.asm:1121` | set by `AzaleaTownRivalBattleScript` | also the rival object's spawn flag | -| `EVENT_CLEARED_SLOWPOKE_WELL` | `constants/event_flags.asm` | read by `AzaleaTownGrampsScript` | prerequisite for this whole section | -| `ENGINE_FLYPOINT_AZALEA` | `constants/engine_flags.asm:82` | `AzaleaTownFlypointCallback` | Fly destination unlocked | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL1` | `RIVAL1` | `RIVAL1_2_CHIKORITA` / `_CYNDAQUIL` / `_TOTODILE` (`constants/trainer_constants.asm:55-57`) | `Rival1Group` entries 4/5/6: L12 Gastly, L14 Zubat, L16 Bayleef / Quilava / Croconaw | `AzaleaTownRivalBattleScript` | none | - -Note the asm order is Gastly, Zubat, starter; the walkthrough prints Gastly, -starter, Zubat. Trust the asm. - ---- - -### MAP_ILEX_FOREST_AZALEA_GATE - -- Script: `maps/IlexForestAzaleaGate.asm` -- Blocks: none (`maps/IlexForestAzaleaGate.blk` is absent; gate uses the shared gate blockset) -- Header: `data/maps/maps.asm:287` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_34, MUSIC_ROUTE_36, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:265` -> `map_const ILEX_FOREST_AZALEA_GATE, 5, 4` -- Connections: none (indoor gate) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ILEX_FOREST` | 2 | -| 2 | 0 | 5 | `ILEX_FOREST` | 3 | -| 3 | 9 | 4 | `AZALEA_TOWN` | 7 | -| 4 | 9 | 5 | `AZALEA_TOWN` | 8 | - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ILEXFORESTAZALEAGATE_OFFICER` | `SPRITE_OFFICER` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IlexForestAzaleaGateOfficerScript` | -1 | -| `ILEXFORESTAZALEAGATE_GRANNY` | `SPRITE_GRANNY` | 1 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IlexForestAzaleaGateGrannyScript` | -1 | - -Both are `jumptextfaceplayer` only. No gate guard, no badge check. - ---- - -### MAP_ILEX_FOREST - -- Script: `maps/IlexForest.asm` -- Blocks: `maps/IlexForest.blk` (405 bytes = 15 x 27) -- Header: `data/maps/maps.asm:122` -> `TILESET_FOREST, CAVE, LANDMARK_ILEX_FOREST, MUSIC_UNION_CAVE, FALSE, PALETTE_NITE, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:109` -> `map_const ILEX_FOREST, 15, 27` -- Connections: none (`data/maps/attributes.asm:441` has no `connection` rows) -- Environment is `CAVE` and palette is `PALETTE_NITE`, so it renders dark all day; it is *not* a Flash map (no `EVENT_...FLASH` requirement anywhere in the file). - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 1 | 5 | `ROUTE_34_ILEX_FOREST_GATE` | 3 | -| 2 | 3 | 42 | `ILEX_FOREST_AZALEA_GATE` | 1 | -| 3 | 3 | 43 | `ILEX_FOREST_AZALEA_GATE` | 2 | - -**Coord events**: `def_coord_events` is empty. The Farfetch'd chase is driven -entirely by talking to the object, not by stepping on tiles. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 17 | `BGEVENT_READ` | `IlexForestSignpost` | -| 27 | 1 | `BGEVENT_ITEM` | `IlexForestHiddenEther` -> `hiddenitem ETHER, EVENT_ILEX_FOREST_HIDDEN_ETHER` | -| 17 | 7 | `BGEVENT_ITEM` | `IlexForestHiddenSuperPotion` -> `hiddenitem SUPER_POTION, EVENT_ILEX_FOREST_HIDDEN_SUPER_POTION` | -| 9 | 17 | `BGEVENT_ITEM` | `IlexForestHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_ILEX_FOREST_HIDDEN_FULL_HEAL` | -| 8 | 22 | `BGEVENT_READ` | `IlexForestShrineScript` (the shrine the walkthrough calls "Ilex Forest Shrine") | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ILEXFOREST_FARFETCHD1` | `SPRITE_BIRD` | 14 | 31 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition1` | `EVENT_ILEX_FOREST_FARFETCHD_1` | -| `ILEXFOREST_FARFETCHD2` | `SPRITE_BIRD` | 15 | 25 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition2` | `EVENT_ILEX_FOREST_FARFETCHD_2` | -| `ILEXFOREST_FARFETCHD3` | `SPRITE_BIRD` | 20 | 24 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition3` | `EVENT_ILEX_FOREST_FARFETCHD_3` | -| `ILEXFOREST_FARFETCHD4` | `SPRITE_BIRD` | 29 | 22 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition4` | `EVENT_ILEX_FOREST_FARFETCHD_4` | -| `ILEXFOREST_FARFETCHD5` | `SPRITE_BIRD` | 28 | 31 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition5` | `EVENT_ILEX_FOREST_FARFETCHD_5` | -| `ILEXFOREST_FARFETCHD6` | `SPRITE_BIRD` | 24 | 35 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition6` | `EVENT_ILEX_FOREST_FARFETCHD_6` | -| `ILEXFOREST_FARFETCHD7` | `SPRITE_BIRD` | 22 | 31 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition7` | `EVENT_ILEX_FOREST_FARFETCHD_7` | -| `ILEXFOREST_FARFETCHD8` | `SPRITE_BIRD` | 15 | 29 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition8` | `EVENT_ILEX_FOREST_FARFETCHD_8` | -| `ILEXFOREST_FARFETCHD9` | `SPRITE_BIRD` | 10 | 35 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition9` | `EVENT_ILEX_FOREST_FARFETCHD_9` | -| `ILEXFOREST_FARFETCHD10` | `SPRITE_BIRD` | 6 | 28 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FarfetchdPosition10` | `EVENT_ILEX_FOREST_FARFETCHD_10` | -| `ILEXFOREST_YOUNGSTER` | `SPRITE_YOUNGSTER` | 7 | 28 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IlexForestCharcoalApprenticeScript` | `EVENT_ILEX_FOREST_APPRENTICE` | -| `ILEXFOREST_BLACK_BELT` | `SPRITE_BLACK_BELT` | 5 | 28 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `IlexForestCharcoalMasterScript` | `EVENT_ILEX_FOREST_CHARCOAL_MASTER` | -| `ILEXFOREST_ROCKER` | `SPRITE_ROCKER` | 15 | 14 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `IlexForestHeadbuttGuyScript` | -1 | -| `ILEXFOREST_POKE_BALL` | `SPRITE_POKE_BALL` | 20 | 32 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IlexForestRevive` (`itemball REVIVE`) | `EVENT_ILEX_FOREST_REVIVE` | - -**Scripts of interest** - -- `IlexForestCharcoalApprenticeScript` (object at 7,28): `checkevent EVENT_HERDED_FARFETCHD` - picks the "please help" text vs the "thanks" text. Pure text, sets nothing. -- `FarfetchdPosition1`: no facing check. Talk once and it always runs - `applymovement ILEXFOREST_FARFETCHD1, MovementData_Farfetchd_Pos1_Pos2`, - `appear ILEXFOREST_FARFETCHD2`, `disappear ILEXFOREST_FARFETCHD1`. -- `FarfetchdPosition2`..`9`: each `scall FarfetchdCryAndCheckFacing`, which does - `faceplayer` / cry / `readvar VAR_FACING`, then `ifequal <DIR>, ...` on the - *player's* facing to decide which way the bird flees. The forward chain is the - fall-through branch of each; the listed `ifequal` directions send it backwards. - Concretely (fall-through = progress): - - Pos2 -> Pos3, except facing `DOWN` -> Pos8 - - Pos3 -> Pos4, except `LEFT` -> Pos2 - - Pos4 -> Pos5, except `UP` -> Pos3 - - Pos5 -> Pos6, except `LEFT` -> Pos7, `UP`/`RIGHT` -> Pos4 - - Pos6 -> Pos7, except `RIGHT` -> Pos5 - - Pos7 -> Pos8, except `LEFT` -> Pos6, `DOWN` -> Pos5 - - Pos8 -> Pos9, except `RIGHT` -> Pos7, `UP`/`LEFT` -> Pos2 - - Pos9 -> Pos10 (**terminal**), except `RIGHT`/`DOWN` -> Pos8 -- `FarfetchdPosition9` fall-through additionally runs - `appear 13 ; ILEXFOREST_BLACK_BELT`, `setevent EVENT_CHARCOAL_KILN_BOSS`, - `setevent EVENT_HERDED_FARFETCHD`. That `appear 13` is a raw object index, not - the const - worth mirroring exactly in a port. -- `IlexForestCharcoalMasterScript` (object at 5,28): `checkevent EVENT_GOT_HM01_CUT`, - else `verbosegiveitem HM_CUT`, `setevent EVENT_GOT_HM01_CUT`, then - `setevent EVENT_ILEX_FOREST_FARFETCHD_10`, `setevent EVENT_ILEX_FOREST_APPRENTICE`, - `setevent EVENT_ILEX_FOREST_CHARCOAL_MASTER`, - `clearevent EVENT_CHARCOAL_KILN_FARFETCH_D`, `clearevent EVENT_CHARCOAL_KILN_APPRENTICE`, - `clearevent EVENT_CHARCOAL_KILN_BOSS` - i.e. the trio moves back to the - Charcoal Kiln in Azalea. Note this script has **no** `iffalse` bag-full guard - around `verbosegiveitem HM_CUT`. -- `IlexForestHeadbuttGuyScript` (object at 15,14): `checkevent EVENT_GOT_TM02_HEADBUTT`, - else `verbosegiveitem TM_HEADBUTT`, `iffalse .BagFull`, `setevent EVENT_GOT_TM02_HEADBUTT`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ILEX_FOREST_FARFETCHD_1`..`_10` | `constants/event_flags.asm` (`_10` at :1172) | spawn flags on the ten bird objects; moved by the chase scripts | exactly one is set at a time | -| `EVENT_HERDED_FARFETCHD` | `constants/event_flags.asm:50` | set by `FarfetchdPosition9` fall-through | chase complete | -| `EVENT_CHARCOAL_KILN_BOSS` | `constants/event_flags.asm:1177` | set by `FarfetchdPosition9`, cleared by `IlexForestCharcoalMasterScript` | kiln boss spawn | -| `EVENT_ILEX_FOREST_APPRENTICE` | `constants/event_flags.asm:1173` | cleared by `SlowpokeWellB1F`, set by the master script | apprentice spawn (inverted: set = hidden here) | -| `EVENT_ILEX_FOREST_CHARCOAL_MASTER` | `constants/event_flags.asm:1174` | set by the master script | master spawn | -| `EVENT_GOT_HM01_CUT` | `constants/event_flags.asm:23` | `IlexForestCharcoalMasterScript` | one-shot HM01 | -| `EVENT_GOT_TM02_HEADBUTT` | `constants/event_flags.asm:104` | `IlexForestHeadbuttGuyScript`; also read by `maps/GoldenrodDeptStore5F.asm` | one-shot TM02 and a Dept 5F stock switch | -| `EVENT_ILEX_FOREST_REVIVE` | `constants/event_flags.asm:1042` | item ball | one-shot | -| `EVENT_ILEX_FOREST_HIDDEN_ETHER` / `_SUPER_POTION` / `_FULL_HEAL` | `constants/event_flags.asm:146-148` | hidden items | one-shot | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `HM_CUT` (HM01) | talk to charcoal master at (5,28) after herding | `IlexForestCharcoalMasterScript` | `EVENT_GOT_HM01_CUT` | -| `TM_HEADBUTT` (TM02) | talk to rocker at (15,14) | `IlexForestHeadbuttGuyScript` | `EVENT_GOT_TM02_HEADBUTT` | -| `REVIVE` | item ball at (20,32) | `IlexForestRevive` | `EVENT_ILEX_FOREST_REVIVE` | -| `ETHER` | hidden at (27,1) | `IlexForestHiddenEther` | `EVENT_ILEX_FOREST_HIDDEN_ETHER` | -| `SUPER_POTION` | hidden at (17,7) | `IlexForestHiddenSuperPotion` | `EVENT_ILEX_FOREST_HIDDEN_SUPER_POTION` | -| `FULL_HEAL` | hidden at (9,17) | `IlexForestHiddenFullHeal` | `EVENT_ILEX_FOREST_HIDDEN_FULL_HEAL` | - -**Trainers**: none. `def_object_events` has no `OBJECTTYPE_TRAINER` row. - -**Wild encounters** - -`data/wild/johto_grass.asm`, `def_grass_wildmons ILEX_FOREST`, rates -`4 percent` morn/day/nite: - -| slot | morn (Gold) | day (Gold) | nite (both) | -|---|---|---|---| -| 1 | L5 Caterpie | L5 Caterpie | L5 Oddish | -| 2 | L6 Metapod | L6 Caterpie | L6 Oddish | -| 3 | L6 Caterpie | L5 Metapod | L6 Zubat | -| 4 | L5 Paras | L6 Metapod | L5 Paras | -| 5 | L5 Zubat | L5 Zubat | L5 Zubat | -| 6 | L6 Paras | L6 Paras | L6 Paras | -| 7 | L6 Paras | L6 Paras | L6 Paras | - -Silver swaps Caterpie/Metapod for Weedle/Kakuna in the morn and day blocks -(the `ELIF DEF(_SILVER)` arm). The nite block is shared. - -Water (`data/wild/johto_water.asm:51`, `def_water_wildmons ILEX_FOREST`, -`2 percent`): L15 Psyduck, L10 Psyduck, L15 Golduck. - -Headbutt: `data/wild/treemon_maps.asm:40` -> `treemon_map ILEX_FOREST, TREEMON_SET_FOREST`. -`TreeMonSet_Forest` (Gold) common: 50% Caterpie, 15% Caterpie, 15% Metapod, -10% Exeggcute, 5% Exeggcute, 5% Butterfree - all level 10; rare swaps the two -15% slots for Pineco. - -**The Cut tree** - -`data/collision/field_move_blocks.asm` `.forest` lists exactly one cuttable -block for `TILESET_FOREST`: `db $0f, $17, 0` (facing block `$0f`, replacement -`$17`, animation 0). Scanning `maps/IlexForest.blk` (15 wide) for `$0f` finds a -single occurrence, block (4,12), i.e. **map coordinates x 8-9, y 24-25**. That -is the tree between the Farfetch'd/charcoal area in the south and the shrine -bg_event at (8,22) / the north half of the forest, and it is the only thing -standing between the player and warp 1 at (1,5). - ---- - -### MAP_ROUTE_34_ILEX_FOREST_GATE - -- Script: `maps/Route34IlexForestGate.asm` -- Blocks: none -- Header: `data/maps/maps.asm:288` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_34, MUSIC_ROUTE_36, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:266` -> `map_const ROUTE_34_ILEX_FOREST_GATE, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_34` | 1 | -| 2 | 5 | 0 | `ROUTE_34` | 2 | -| 3 | 4 | 7 | `ILEX_FOREST` | 1 | -| 4 | 5 | 7 | `ILEX_FOREST` | 1 | - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE34ILEXFORESTGATE_TEACHER` | `SPRITE_TEACHER` | 9 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route34IlexForestGateTeacherScript` | -1 | -| `ROUTE34ILEXFORESTGATE_BUTTERFREE` | `SPRITE_BUTTERFREE` | 9 | 4 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `Route34IlexForestGateButterfreeScript` | -1 | -| `ROUTE34ILEXFORESTGATE_LASS` | `SPRITE_LASS` | 3 | 4 | `WALK_UP_DOWN` | `OBJECTTYPE_SCRIPT` | `Route34IlexForestGateLassScript` | -1 | - -**Scripts of interest** - -- `Route34IlexForestGateTeacherScript`: `checkevent EVENT_GOT_TM12_SWEET_SCENT`, - else `verbosegiveitem TM_SWEET_SCENT`, `iffalse .NoRoom`, - `setevent EVENT_GOT_TM12_SWEET_SCENT`. The walkthrough calls her "the lady - behind the counter"; she is at (9,3), the top-right of the gate. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SWEET_SCENT` (TM12) | talk to teacher at (9,3) | `Route34IlexForestGateTeacherScript` | `EVENT_GOT_TM12_SWEET_SCENT` (`constants/event_flags.asm:131`) | - ---- - -### MAP_ROUTE_34 - -- Script: `maps/Route34.asm` -- Blocks: `maps/Route34.blk` -- Header: `data/maps/maps.asm:268` -> `TILESET_JOHTO_MODERN, ROUTE, LANDMARK_ROUTE_34, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:246` -> `map_const ROUTE_34, 10, 27` -- Connections (`data/maps/attributes.asm:198`): north `GoldenrodCity` (offset -5), east `AzaleaTown` (offset 18) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 37 | `ROUTE_34_ILEX_FOREST_GATE` | 1 | -| 2 | 14 | 37 | `ROUTE_34_ILEX_FOREST_GATE` | 2 | -| 3 | 11 | 14 | `DAY_CARE` | 1 | -| 4 | 11 | 15 | `DAY_CARE` | 2 | -| 5 | 13 | 15 | `DAY_CARE` | 3 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 12 | 6 | `BGEVENT_READ` | `Route34Sign` | -| 13 | 33 | `BGEVENT_READ` | `Route34TrainerTips` | -| 10 | 13 | `BGEVENT_READ` | `DayCareSign` | -| 8 | 32 | `BGEVENT_ITEM` | `Route34HiddenRareCandy` -> `hiddenitem RARE_CANDY, EVENT_ROUTE_34_HIDDEN_RARE_CANDY` | -| 17 | 19 | `BGEVENT_ITEM` | `Route34HiddenSuperPotion` -> `hiddenitem SUPER_POTION, EVENT_ROUTE_34_HIDDEN_SUPER_POTION` | - -(`Route34IlexForestSign` exists in the file but is marked `; unreferenced`.) - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE34_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 13 | 7 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerCamperTodd1` | -1 | -| `ROUTE34_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 15 | 33 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerYoungsterSamuel` | -1 | -| `ROUTE34_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 17 | 22 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerYoungsterIan` | -1 | -| `ROUTE34_LASS` | `SPRITE_LASS` | 10 | 26 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPicnickerGina1` | -1 | -| `ROUTE34_OFFICER` | `SPRITE_OFFICER` | 9 | 11 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `OfficerKeithScript` | -1 | -| `ROUTE34_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 28 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 2 | `TrainerPokefanmBrandon` | -1 | -| `ROUTE34_GRAMPS` | `SPRITE_GRAMPS` | 15 | 16 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `DayCareManScript_Outside` | `EVENT_DAY_CARE_MAN_ON_ROUTE_34` | -| `ROUTE34_DAY_CARE_MON_1` | `SPRITE_DAY_CARE_MON_1` | 14 | 18 | `POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `DayCareMon1Script` | `EVENT_DAY_CARE_MON_1` | -| `ROUTE34_DAY_CARE_MON_2` | `SPRITE_DAY_CARE_MON_2` | 17 | 19 | `POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `DayCareMon2Script` | `EVENT_DAY_CARE_MON_2` | -| `ROUTE34_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 11 | 48 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerCooltrainerfIrene` | -1 | -| `ROUTE34_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 3 | 48 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerCooltrainerfJenn` | -1 | -| `ROUTE34_COOLTRAINER_F3` | `SPRITE_COOLTRAINER_F` | 6 | 51 | `STANDING_UP` | `OBJECTTYPE_TRAINER` | 2 | `TrainerCooltrainerfKate` | -1 | - -The three Cooltrainer F sisters sit at y 48-51, on the beach south-west of the -Ilex gate. That area is water-locked; reaching them needs SURF, which needs the -FOGBADGE. They are not part of this section's path. - -**Scripts of interest** - -- `Route34EggCheckCallback` (`MAPCALLBACK_OBJECTS`): reads - `ENGINE_DAY_CARE_MAN_HAS_EGG` to decide whether the Day-Care man stands - outside (`EVENT_DAY_CARE_MAN_ON_ROUTE_34`) or inside - (`EVENT_DAY_CARE_MAN_IN_DAY_CARE`), then mirrors - `ENGINE_DAY_CARE_MAN_HAS_MON` / `ENGINE_DAY_CARE_LADY_HAS_MON` onto - `EVENT_DAY_CARE_MON_1` / `_2` (which are *hide* flags: set = hidden). -- `DayCareManScript_Outside`: `special DayCareManOutside` (the yes/no egg - handoff), then `ifequal TRUE, .end_fail`, `clearflag ENGINE_DAY_CARE_MAN_HAS_EGG`, - and walks him back inside with one of two movement scripts depending on - `readvar VAR_FACING` (`RIGHT` uses the walk-around-player variant). -- `OfficerKeithScript`: `checktime NITE`, `iffalse .NoFight`. Night only, as the - walkthrough says. Wins set `EVENT_BEAT_OFFICER_KEITH`. It is an - `OBJECTTYPE_SCRIPT`, not a `trainer` row, so there is no sight-line trigger: - the bot must talk to him. -- `TrainerCamperTodd1` / `TrainerPicnickerGina1`: full phone-number flow - (`askforphonenumber PHONE_CAMPER_TODD` / `PHONE_PICNICKER_GINA`), with - rematch parties gated on `ENGINE_FLYPOINT_CIANWOOD` / `ENGINE_FLYPOINT_BLACKTHORN` - (Todd) and `ENGINE_FLYPOINT_MAHOGANY` / `EVENT_CLEARED_RADIO_TOWER` (Gina). -- `TrainerYoungsterSamuel`, `TrainerYoungsterIan`, `TrainerPokefanmBrandon`: - plain `endifjustbattled` + after-text. **No phone number.** -- `TrainerCooltrainerfKate`: `verbosegiveitem SOFT_SAND`, - `setevent EVENT_GOT_SOFT_SAND_FROM_KATE`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_DAY_CARE_MAN_HAS_EGG` | `constants/engine_flags.asm:10` | `Route34EggCheckCallback`, `DayCareEggCheckCallback`, `DayCareManScript_Outside` | egg is waiting; man stands outside at (15,16) | -| `ENGINE_DAY_CARE_MAN_HAS_MON` / `ENGINE_DAY_CARE_LADY_HAS_MON` | `constants/engine_flags.asm:11,13` | both egg-check callbacks | drives the two overworld Day-Care mon sprites | -| `EVENT_DAY_CARE_MAN_ON_ROUTE_34` | `constants/event_flags.asm:1160` | callbacks | outdoor Day-Care man spawn | -| `EVENT_BEAT_OFFICER_KEITH` | `constants/event_flags.asm` | `OfficerKeithScript` | one-shot night battle | -| `EVENT_GOT_SOFT_SAND_FROM_KATE` | `constants/event_flags.asm:121` | `TrainerCooltrainerfKate` | one-shot, needs SURF | -| `EVENT_ROUTE_34_HIDDEN_RARE_CANDY` / `_SUPER_POTION` | `constants/event_flags.asm:177,178` | hidden items | one-shot | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `SOFT_SAND` | beat Cooltrainer F Kate at (6,51) (SURF required) | `TrainerCooltrainerfKate` | `EVENT_GOT_SOFT_SAND_FROM_KATE` | -| `RARE_CANDY` | hidden at (8,32) | `Route34HiddenRareCandy` | `EVENT_ROUTE_34_HIDDEN_RARE_CANDY` | -| `SUPER_POTION` | hidden at (17,19) | `Route34HiddenSuperPotion` | `EVENT_ROUTE_34_HIDDEN_SUPER_POTION` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SAMUEL` | `YOUNGSTER` | YOUNGSTER (5) | L7 Rattata, L10 Sandshrew, L8 Spearow, L8 Spearow | `TrainerYoungsterSamuel` | none | -| `BRANDON` | `POKEFANM` | POKEFANM (7) | L13 Snubbull @ `BERRY` (`TRAINERTYPE_ITEM`) | `TrainerPokefanmBrandon` | none | -| `IAN` | `YOUNGSTER` | YOUNGSTER (6) | L10 Mankey, L12 Diglett | `TrainerYoungsterIan` | none | -| `GINA1` | `PICNICKER` | PICNICKER (2) | L9 Hoppip, L9 Hoppip, L12 Bulbasaur | `TrainerPicnickerGina1` | `PHONE_PICNICKER_GINA`; `GINA2`/`GINA3` rematches | -| `KEITH` | `OFFICER` | OFFICER (1) | L17 Growlithe | `OfficerKeithScript` | none; night only | -| `TODD1` | `CAMPER` | CAMPER (2) | L14 Psyduck | `TrainerCamperTodd1` | `PHONE_CAMPER_TODD`; `TODD2`/`TODD3` rematches | -| `IRENE` | `COOLTRAINERF` | - | see `data/trainers/parties.asm` | `TrainerCooltrainerfIrene` | SURF-gated beach | -| `JENN` | `COOLTRAINERF` | - | see `data/trainers/parties.asm` | `TrainerCooltrainerfJenn` | SURF-gated beach | -| `KATE` | `COOLTRAINERF` | - | see `data/trainers/parties.asm` | `TrainerCooltrainerfKate` | SURF-gated beach; gives `SOFT_SAND` | - -**Wild encounters** - -`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_34`, rates -`10 percent` morn/day/nite; all three time blocks are identical: - -L10 Drowzee, L11 Rattata, L12 Drowzee, L10 Abra, L13 Rattata, L10 Ditto, L10 Ditto. - -Water (`data/wild/johto_water.asm:149`, `6 percent`): L20 Tentacool, -L15 Tentacool, L20 Tentacruel. - -Headbutt: `data/wild/treemon_maps.asm:15` -> `treemon_map ROUTE_34, TREEMON_SET_FOREST` -(same table as Ilex Forest). - ---- - -### MAP_DAY_CARE - -- Script: `maps/DayCare.asm` -- Blocks: `maps/DayCare.blk` -- Header: `data/maps/maps.asm:289` -> `TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_34, MUSIC_AZALEA_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:267` -> `map_const DAY_CARE, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 5 | `ROUTE_34` | 3 | -| 2 | 0 | 6 | `ROUTE_34` | 4 | -| 3 | 2 | 7 | `ROUTE_34` | 5 | -| 4 | 3 | 7 | `ROUTE_34` | 5 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `DayCareBookshelf` | -| 1 | 1 | `BGEVENT_READ` | `DayCareBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DAYCARE_GRAMPS` | `SPRITE_GRAMPS` | 2 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `DayCareManScript_Inside` | `EVENT_DAY_CARE_MAN_IN_DAY_CARE` | -| `DAYCARE_GRANNY` | `SPRITE_GRANNY` | 5 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `DayCareLadyScript` | -1 | - -**Scripts of interest** - -- `DayCareEggCheckCallback` (`MAPCALLBACK_OBJECTS`): mirror of the Route 34 one; - moves the man in/out based on `ENGINE_DAY_CARE_MAN_HAS_EGG`. -- `DayCareManScript_Inside` -> `special DayCareMan`; `DayCareLadyScript` -> - `special DayCareLady`, unless `ENGINE_DAY_CARE_MAN_HAS_EGG` is set, in which - case she just says "Gramps was looking for you". - -There is **no** PC object in `maps/DayCare.asm`. The walkthrough's quoted claim -that "there's a PC in the corner of the Daycare" is not backed by this map. - ---- - -### MAP_GOLDENROD_CITY - -- Script: `maps/GoldenrodCity.asm` -- Blocks: `maps/GoldenrodCity.blk` -- Header: `data/maps/maps.asm:269` -> `TILESET_JOHTO_MODERN, TOWN, LANDMARK_GOLDENROD_CITY, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:247` -> `map_const GOLDENROD_CITY, 20, 18` -- Connections (`data/maps/attributes.asm:139`): north `Route35` (offset 5), south `Route34` (offset 5) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 24 | 7 | `GOLDENROD_GYM` | 1 | -| 2 | 29 | 29 | `GOLDENROD_BIKE_SHOP` | 1 | -| 3 | 31 | 21 | `GOLDENROD_HAPPINESS_RATER` | 1 | -| 4 | 5 | 25 | `BILLS_FAMILYS_HOUSE` | 1 | -| 5 | 9 | 13 | `GOLDENROD_MAGNET_TRAIN_STATION` | 2 | -| 6 | 33 | 5 | `GOLDENROD_FLOWER_SHOP` | 1 | -| 7 | 15 | 27 | `GOLDENROD_POKECENTER_1F` | 1 | -| 8 | 33 | 9 | `GOLDENROD_PP_SPEECH_HOUSE` | 1 | -| 9 | 15 | 7 | `GOLDENROD_NAME_RATER` | 1 | -| 10 | 24 | 27 | `GOLDENROD_DEPT_STORE_1F` | 1 | -| 11 | 14 | 21 | `GOLDENROD_GAME_CORNER` | 1 | -| 12 | 5 | 15 | `RADIO_TOWER_1F` | 1 | -| 13 | 19 | 1 | `ROUTE_35_GOLDENROD_GATE` | 3 | -| 14 | 9 | 5 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 8 | -| 15 | 11 | 29 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 5 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 10 | 14 | `BGEVENT_READ` | `GoldenrodCityStationSign` | -| 4 | 17 | `BGEVENT_READ` | `GoldenrodCityRadioTowerSign` | -| 26 | 27 | `BGEVENT_READ` | `GoldenrodDeptStoreSign` | -| 26 | 9 | `BGEVENT_READ` | `GoldenrodGymSign` | -| 22 | 18 | `BGEVENT_READ` | `GoldenrodCitySign` | -| 28 | 30 | `BGEVENT_READ` | `GoldenrodCityBikeShopSign` | -| 16 | 22 | `BGEVENT_READ` | `GoldenrodCityGameCornerSign` | -| 12 | 7 | `BGEVENT_READ` | `GoldenrodCityNameRaterSign` | -| 8 | 6 | `BGEVENT_READ` | `GoldenrodCityUndergroundSignNorth` | -| 12 | 30 | `BGEVENT_READ` | `GoldenrodCityUndergroundSignSouth` | -| 16 | 27 | `BGEVENT_READ` | `GoldenrodCityPokecenterSign` | - -No hidden items on this map. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODCITY_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 7 | 18 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityPokefanMScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 30 | 17 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `GoldenrodCityYoungster1Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 12 | 16 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityCooltrainerF1Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 20 | 26 | `WANDER` (1,2) | `OBJECTTYPE_SCRIPT` | `GoldenrodCityCooltrainerF2Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 19 | 17 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `GoldenrodCityYoungster2Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_LASS` | `SPRITE_LASS` | 17 | 10 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `GoldenrodCityLassScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_GRAMPS` | `SPRITE_GRAMPS` | 11 | 27 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `GoldenrodCityGrampsScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_ROCKETSCOUT` | `SPRITE_ROCKET` | 4 | 16 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocketScoutScript` | `EVENT_GOLDENROD_CITY_ROCKET_SCOUT` | -| `GOLDENRODCITY_ROCKET1` | `SPRITE_ROCKET` | 28 | 20 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket1Script` | `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET2` | `SPRITE_ROCKET` | 8 | 15 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket2Script` | `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET3` | `SPRITE_ROCKET` | 16 | 23 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket3Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET4` | `SPRITE_ROCKET` | 29 | 20 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket4Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET5` | `SPRITE_ROCKET` | 29 | 7 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket5Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET6` | `SPRITE_ROCKET` | 30 | 10 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `GoldenrodCityRocket6Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | - -**Scripts of interest** - -- `GoldenrodCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): - `setflag ENGINE_FLYPOINT_GOLDENROD`, `setflag ENGINE_REACHED_GOLDENROD`. - Both fire on first entry, no conditions. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_GOLDENROD` | `constants/engine_flags.asm:84` | flypoint callback | Fly unlocked | -| `ENGINE_REACHED_GOLDENROD` | `constants/engine_flags.asm:31` | flypoint callback | gates the Rocket-takeover storyline elsewhere | -| `ENGINE_RADIO_CARD` | `constants/engine_flags.asm:4` | set by `RadioTower1FRadioCardWomanScript`; read by `GoldenrodCityCooltrainerF2Script` | Pokegear radio | -| `EVENT_GOLDENROD_CITY_CIVILIANS` | `constants/event_flags.asm` | spawn flag on the seven civilians | cleared during the Rocket takeover | - -**Trainers**: none on the city map at this point in the game. - -**Wild encounters**: none (`data/wild/treemon_maps.asm:33` -> -`treemon_map GOLDENROD_CITY, TREEMON_SET_NONE`; no `def_grass_wildmons` / -`def_water_wildmons` entry for `GOLDENROD_CITY`). - ---- - -### MAP_BILLS_FAMILYS_HOUSE - -- Script: `maps/BillsFamilysHouse.asm` -- Header: `data/maps/maps.asm:273` -> `TILESET_HOUSE, INDOOR, LANDMARK_GOLDENROD_CITY, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:251` -> `map_const BILLS_FAMILYS_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 4 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 4 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `BillsHouseBookshelf2` | -| 1 | 1 | `BGEVENT_READ` | `BillsHouseBookshelf1` | -| 7 | 1 | `BGEVENT_READ` | `BillsHouseRadio` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BILLSFAMILYSHOUSE_BILL` | `SPRITE_BILL` | 2 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `BillScript` | `EVENT_MET_BILL` | -| `BILLSFAMILYSHOUSE_POKEFAN_F` | `SPRITE_POKEFAN_F` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BillsMomScript` | -1 | -| `BILLSFAMILYSHOUSE_TWIN` | `SPRITE_TWIN` | 5 | 4 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BillsYoungerSisterScript` | -1 | - -**Scripts of interest** - -- `BillsYoungerSisterScript` (the twin at 5,4 - the girl "closer to the door"): - `checkcellnum PHONE_BILL`, else `askforphonenumber PHONE_BILL` / - `addcellnum PHONE_BILL`. This is the only thing available in this section. -- `BillScript`: gives `EEVEE` at level 20 via `givepoke EEVEE, 20` + - `setevent EVENT_GOT_EEVEE`, guarded by `readvar VAR_PARTYCOUNT` / - `ifequal PARTY_LENGTH, .NoRoom`. Bill's object only spawns once - `EVENT_MET_BILL` is set, which happens in Ecruteak - **not in this section**. - `BillsMomScript` reads `EVENT_MET_BILL` too (note the two texts are wired - backwards relative to their names in the asm: `iffalse .HaventMetBill` - prints `BillsMomText_AfterEcruteak`). - ---- - -### MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES - -- Script: `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` -- Blocks: `maps/GoldenrodUndergroundSwitchRoomEntrances.blk` -- Header: `data/maps/maps.asm:124` -> `TILESET_ELITE_FOUR_ROOM, DUNGEON, LANDMARK_GOLDENROD_CITY, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:111` -> `map_const GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES, 15, 18` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 3 | `GOLDENROD_UNDERGROUND` | 6 | -| 2 | 22 | 10 | `GOLDENROD_UNDERGROUND_WAREHOUSE` | 1 | -| 3 | 23 | 10 | `GOLDENROD_UNDERGROUND_WAREHOUSE` | 2 | -| 4 | 5 | 25 | `GOLDENROD_UNDERGROUND` | 2 | -| 5 | 4 | 29 | `GOLDENROD_CITY` | 15 | -| 6 | 5 | 29 | `GOLDENROD_CITY` | 15 | -| 7 | 21 | 25 | `GOLDENROD_UNDERGROUND` | 1 | -| 8 | 20 | 29 | `GOLDENROD_CITY` | 14 | -| 9 | 21 | 29 | `GOLDENROD_CITY` | 14 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_GOLDENRODUNDERGROUNDSWITCHROOMENTRANCES_RIVAL_BATTLE` | 19 | 4 | `UndergroundRivalScene1` | later-game rival battle, not armed in this section | -| `SCENE_GOLDENRODUNDERGROUNDSWITCHROOMENTRANCES_RIVAL_BATTLE` | 19 | 5 | `UndergroundRivalScene2` | same | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 1 | `BGEVENT_READ` | `Switch1Script` | -| 10 | 1 | `BGEVENT_READ` | `Switch2Script` | -| 2 | 1 | `BGEVENT_READ` | `Switch3Script` | -| 20 | 11 | `BGEVENT_READ` | `EmergencySwitchScript` | -| 8 | 9 | `BGEVENT_ITEM` | `GoldenrodUndergroundSwitchRoomEntrancesHiddenMaxPotion` | -| 1 | 8 | `BGEVENT_ITEM` | `GoldenrodUndergroundSwitchRoomEntrancesHiddenRevive` | - -**Object events** (all the Rocket/Burglar rows are gated on -`EVENT_RADIO_TOWER_ROCKET_TAKEOVER`, so during this section only the two -stairwell NPCs and the two item balls exist) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._TEACHER` | `SPRITE_TEACHER` | 3 | 27 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `GoldenrodUndergroundSwitchRoomEntrancesTeacherScript` | -1 | -| `..._SUPER_NERD` | `SPRITE_SUPER_NERD` | 19 | 27 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `GoldenrodUndergroundSwitchRoomEntrancesSuperNerdScript` | -1 | -| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 1 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `..._SmokeBall` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_SMOKE_BALL` | -| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 9 | `STILL` | `OBJECTTYPE_ITEMBALL` | `..._FullHeal` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_FULL_HEAL` | -| `..._PHARMACIST1/2`, `..._ROCKET1/2/3`, `..._ROCKET_GIRL` | - | (9,12) (4,8) (17,2) (11,2) (3,2) (19,12) | - | `OBJECTTYPE_TRAINER` | `TrainerBurglarDuncan`, `TrainerBurglarEddie`, `TrainerGruntM13`, `TrainerGruntM11`, `TrainerGruntM25`, `TrainerGruntF3` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._RIVAL` | `SPRITE_RIVAL` | 23 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_GOLDENROD_UNDERGROUND` | - -The three `ugdoor` switch puzzles and the warehouse are also takeover-era -content. This section only walks through: city warp 15 -> (4,29), north to -warp 4 at (5,25) -> `GOLDENROD_UNDERGROUND`; and back up on the other side via -warp 7 at (21,25) -> warps 8/9 at (20/21,29) -> city warp 14 at (9,5). - ---- - -### MAP_GOLDENROD_UNDERGROUND - -- Script: `maps/GoldenrodUnderground.asm` -- Blocks: `maps/GoldenrodUnderground.blk` -- Header: `data/maps/maps.asm:123` -> `TILESET_GATE, DUNGEON, LANDMARK_GOLDENROD_CITY, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:110` -> `map_const GOLDENROD_UNDERGROUND, 15, 18` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 7 | -| 2 | 3 | 34 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 4 | -| 3 | 18 | 6 | `GOLDENROD_UNDERGROUND` | 4 | -| 4 | 21 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 5 | 22 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 6 | 22 | 27 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 1 | - -Warp 3 at (18,6) is the basement door; it is blocked until `BASEMENT_KEY`. - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 18 | 6 | `BGEVENT_READ` | `BasementDoorScript` | -| 19 | 6 | `BGEVENT_READ` | `GoldenrodUndergroundNoEntrySign` | -| 6 | 13 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenParlyzHeal` (`PARLYZ_HEAL`, `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_PARLYZ_HEAL`) | -| 4 | 18 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenSuperPotion` (`SUPER_POTION`, `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_SUPER_POTION`) | -| 17 | 8 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenAntidote` (`ANTIDOTE`, `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_ANTIDOTE`) | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `GOLDENRODUNDERGROUND_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 31 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSupernerdEric` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 6 | 9 | `STANDING_UP` | `OBJECTTYPE_TRAINER` | 2 | `TrainerSupernerdTeru` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD3` | `SPRITE_SUPER_NERD` | 3 | 27 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerPokemaniacIssac` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD4` | `SPRITE_SUPER_NERD` | 2 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPokemaniacDonald` | -1 | -| `GOLDENRODUNDERGROUND_POKE_BALL` | `SPRITE_POKE_BALL` | 7 | 25 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `GoldenrodUndergroundCoinCase` (`itemball COIN_CASE`) | `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE` | -| `GOLDENRODUNDERGROUND_GRAMPS` | `SPRITE_GRAMPS` | 7 | 11 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | 0 | `BargainMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRAMPS` | -| `GOLDENRODUNDERGROUND_OLDER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 14 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | 0 | `OlderHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 15 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | 0 | `YoungerHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_GRANNY` | `SPRITE_GRANNY` | 7 | 21 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | 0 | `BitterMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRANNY` | - -**Scripts of interest** - -- `GoldenrodUndergroundResetSwitchesCallback` (`MAPCALLBACK_NEWMAP`): clears - `EVENT_SWITCH_1..3`, `EVENT_EMERGENCY_SWITCH`, `EVENT_DOOR_1_OPEN`..`_11_OPEN` - and zeroes `wUndergroundSwitchPositions`. Runs every entry. -- `GoldenrodUndergroundCheckBasementKeyCallback` (`MAPCALLBACK_TILES`): unless - `EVENT_USED_BASEMENT_KEY`, `changeblock 18, 6, $3d` (locked door). This is the - "door is locked" the walkthrough hits. -- `BasementDoorScript` (bg at 18,6): `checkitem BASEMENT_KEY` -> - `changeblock 18, 6, $2e`, `refreshmap`, `setevent EVENT_USED_BASEMENT_KEY`. - The `BASEMENT_KEY` is not obtainable in this section. -- `GoldenrodUndergroundCheckDayOfWeekCallback` (`MAPCALLBACK_OBJECTS`): the - weekday schedule for the four shop NPCs - - Sunday: younger barber + granny; Monday morn: gramps (bargain merchant); - Tue/Thu: older barber; Wed/Fri: younger barber; Sat: older barber + granny. -- `BargainMerchantScript`: Monday `MORN` only, and only while - `ENGINE_GOLDENROD_UNDERGROUND_MERCHANT_CLOSED` is clear -> - `pokemart MARTTYPE_BARGAIN, 0`. -- `BitterMerchantScript`: Saturday or Sunday -> `pokemart MARTTYPE_BITTER, MART_UNDERGROUND`. -- `OlderHaircutBrotherScript` / `YoungerHaircutBrotherScript`: 500 / 300 money, - one per day via `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT`, - `special OlderHaircutBrother` / `YoungerHaircutBrother`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `COIN_CASE` | item ball at (7,25) | `GoldenrodUndergroundCoinCase` | `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE` (`constants/event_flags.asm:1043`) | -| `PARLYZ_HEAL` | hidden at (6,13) | `GoldenrodUndergroundHiddenParlyzHeal` | `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_PARLYZ_HEAL` | -| `SUPER_POTION` | hidden at (4,18) | `GoldenrodUndergroundHiddenSuperPotion` | `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_SUPER_POTION` | -| `ANTIDOTE` | hidden at (17,8) | `GoldenrodUndergroundHiddenAntidote` | `EVENT_GOLDENROD_UNDERGROUND_HIDDEN_ANTIDOTE` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `ERIC` | `SUPER_NERD` | SUPER_NERD (2) | L11 Grimer, L11 Grimer | `TrainerSupernerdEric` | none | -| `ISSAC` | `POKEMANIAC` | POKEMANIAC (11) | L12 Lickitung (Lick, Supersonic, Cut) `TRAINERTYPE_MOVES` | `TrainerPokemaniacIssac` | none | -| `TERU` | `SUPER_NERD` | SUPER_NERD (10) | L7 Magnemite, L11 Voltorb, L7 Magnemite, L9 Magnemite | `TrainerSupernerdTeru` | none | -| `DONALD` | `POKEMANIAC` | POKEMANIAC (12) | L10 Slowpoke, L10 Slowpoke | `TrainerPokemaniacDonald` | none | - -The walkthrough spells the Pokemaniac "Isaac"; the asm constant and party name -are `ISSAC` (two S). Its event flag is `EVENT_BEAT_POKEMANIAC_ISSAC`. - -**Wild encounters**: none. - ---- - -### MAP_GOLDENROD_BIKE_SHOP - -- Script: `maps/GoldenrodBikeShop.asm` -- Header: `data/maps/maps.asm:271` -> `TILESET_CHAMPIONS_ROOM, INDOOR, LANDMARK_GOLDENROD_CITY, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:249` -> `map_const GOLDENROD_BIKE_SHOP, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 2 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 2 | - -**BG events**: nine `GoldenrodBikeShopBicycle` display signs at -(1,2), (0,3), (1,3), (0,5), (1,5), (0,6), (1,6), (6,6), (7,6). - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODBIKESHOP_CLERK` | `SPRITE_CLERK` | 7 | 2 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `GoldenrodBikeShopClerkScript` | -1 | - -**Scripts of interest** - -- `GoldenrodBikeShopClerkScript`: `checkevent EVENT_GOT_BICYCLE`, else - intro text + `yesorno`, `giveitem BICYCLE`, `itemnotify`, - `setflag ENGINE_BIKE_SHOP_CALL_ENABLED`, `setevent EVENT_GOT_BICYCLE`. - No badge, money or item prerequisite. Note it is a plain `giveitem`, not - `verbosegiveitem`, and there is no bag-full branch. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BICYCLE` | say YES to the clerk at (7,2) | `GoldenrodBikeShopClerkScript` | `EVENT_GOT_BICYCLE` (`constants/event_flags.asm:100`) | - ---- - -### MAP_RADIO_TOWER_1F - -- Script: `maps/RadioTower1F.asm` -- Blocks: `maps/RadioTower1F.blk` -- Header: `data/maps/maps.asm:95` -> `TILESET_RADIO_TOWER, INDOOR, LANDMARK_RADIO_TOWER, RADIO_TOWER_MUSIC | MUSIC_GOLDENROD_CITY, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:82` -> `map_const RADIO_TOWER_1F, 9, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 12 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 12 | -| 3 | 15 | 0 | `RADIO_TOWER_2F` | 2 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 0 | `BGEVENT_READ` | `RadioTower1FDirectory` | -| 13 | 0 | `BGEVENT_READ` | `RadioTower1FLuckyChannelSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 5 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `RadioTower1FReceptionistScript` | -1 | -| `..._LASS` | `SPRITE_LASS` | 16 | 4 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `RadioTower1FLassScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `..._YOUNGSTER` | `SPRITE_YOUNGSTER` | 15 | 4 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `RadioTower1FYoungsterScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `..._ROCKET` | `SPRITE_ROCKET` | 14 | 1 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerGruntM3` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._GENTLEMAN` | `SPRITE_GENTLEMAN` | 8 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RadioTower1FLuckyNumberManScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `..._COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 12 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RadioTower1FRadioCardWomanScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | - -**Scripts of interest** - -`RadioTower1FRadioCardWomanScript` (object at 12,6 - the woman on the right): -`checkflag ENGINE_RADIO_CARD` short-circuits. Otherwise a five-question -`yesorno` chain; a wrong answer jumps to `.WrongAnswer` and the whole quiz -restarts on the next talk. Required answers, straight off the `iffalse`/`iftrue` -branches: - -| # | question text label | required answer | branch | -|---|---|---|---| -| 1 | `RadioTower1FRadioCardWomanQuestion1Text` (Town Map on Pokegear?) | YES | `iffalse .WrongAnswer` | -| 2 | `...Question2Text` (Nidorina female only?) | YES | `iffalse .WrongAnswer` | -| 3 | `...Question3Text` (Kurt uses Apricorn?) | NO | `iftrue .WrongAnswer` | -| 4 | `...Question4Text` (Magikarp learns no TM?) | YES | `iffalse .WrongAnswer` | -| 5 | `...Question5Text` (is MARIE the co-host?) | NO | `iftrue .WrongAnswer` | - -On success: `getstring STRING_BUFFER_4, .RadioCardText` (`"RADIO CARD@"`), -`scall .ReceiveItem` (`jumpstd ReceiveItemScript`), then -`setflag ENGINE_RADIO_CARD`. The Radio Card is a Pokegear card flag, not a bag -item. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| Radio Card (Pokegear card) | pass the five-question quiz | `RadioTower1FRadioCardWomanScript` | `ENGINE_RADIO_CARD` (`constants/engine_flags.asm:4`) | - ---- - -### MAP_GOLDENROD_GYM - -- Script: `maps/GoldenrodGym.asm` -- Blocks: `maps/GoldenrodGym.blk` -- Header: `data/maps/maps.asm:270` -> `TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_GOLDENROD_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:248` -> `map_const GOLDENROD_GYM, 10, 9` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 17 | `GOLDENROD_CITY` | 1 | -| 2 | 3 | 17 | `GOLDENROD_CITY` | 1 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_GOLDENRODGYM_WHITNEY_STOPS_CRYING` | 8 | 5 | `WhitneyCriesScript` | Bridget walks over, `clearevent EVENT_MADE_WHITNEY_CRY`, `setscene SCENE_GOLDENRODGYM_NOOP` | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 15 | `BGEVENT_READ` | `GoldenrodGymStatue` | -| 4 | 15 | `BGEVENT_READ` | `GoldenrodGymStatue` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `GOLDENRODGYM_WHITNEY` | `SPRITE_WHITNEY` | 8 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `GoldenrodGymWhitneyScript` | -1 | -| `GOLDENRODGYM_LASS1` | `SPRITE_LASS` | 9 | 13 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerLassCarrie` | -1 | -| `GOLDENRODGYM_LASS2` | `SPRITE_LASS` | 9 | 6 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerLassBridget` | -1 | -| `GOLDENRODGYM_BEAUTY1` | `SPRITE_BEAUTY` | 0 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBeautyVictoria` | -1 | -| `GOLDENRODGYM_BEAUTY2` | `SPRITE_BEAUTY` | 19 | 5 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBeautySamantha` | -1 | -| `GOLDENRODGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 5 | 15 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `GoldenrodGymGuideScript` | -1 | - -**Scripts of interest** - -`GoldenrodGymWhitneyScript` is a three-visit script; a bot must run it in order: - -1. First talk (`EVENT_BEAT_WHITNEY` clear): before-text, - `winlosstext WhitneyShouldntBeSoSeriousText, 0`, - `loadtrainer WHITNEY, WHITNEY1`, `startbattle`, `reloadmapafterbattle`. - On win it sets `EVENT_BEAT_WHITNEY`, `EVENT_MADE_WHITNEY_CRY`, - `setscene SCENE_GOLDENRODGYM_WHITNEY_STOPS_CRYING`, and marks all four gym - trainers beaten (`EVENT_BEAT_BEAUTY_VICTORIA`, `EVENT_BEAT_BEAUTY_SAMANTHA`, - `EVENT_BEAT_LASS_CARRIE`, `EVENT_BEAT_LASS_BRIDGET`). -2. Falls into `.FightDone` immediately: `checkevent EVENT_MADE_WHITNEY_CRY` - `iffalse .StoppedCrying`. While the flag is set, Whitney only prints - `WhitneyYouMeanieText` and **gives nothing**. -3. Stepping on the coord event at **(8,5)** runs `WhitneyCriesScript`, which - `clearevent EVENT_MADE_WHITNEY_CRY` and `setscene SCENE_GOLDENRODGYM_NOOP`. - Talk to Whitney again -> `.StoppedCrying`: `setflag ENGINE_PLAINBADGE`, - `readvar VAR_BADGES`, `scall GoldenrodGymActivateRockets`, then - `verbosegiveitem TM_ATTRACT` guarded by `iffalse .NoRoomForAttract` and - `setevent EVENT_GOT_TM45_ATTRACT`. - -`GoldenrodGymActivateRockets`: `ifequal 7, .RadioTowerRockets`, -`ifequal 6, .GoldenrodRockets` on the post-badge count. With PLAINBADGE as the -third badge neither branch fires here. - -`GoldenrodGymStatue`: `checkflag ENGINE_PLAINBADGE` -> `GymStatue2Script` with -`gettrainername STRING_BUFFER_4, WHITNEY, WHITNEY1`, else `GymStatue1Script`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_WHITNEY` | `constants/event_flags.asm:708` | `GoldenrodGymWhitneyScript`, `GoldenrodGymGuideScript` | battle done | -| `EVENT_MADE_WHITNEY_CRY` | `constants/event_flags.asm:49` | set by the win, cleared by `WhitneyCriesScript` | **blocks the badge** while set | -| `SCENE_GOLDENRODGYM_WHITNEY_STOPS_CRYING` | `maps/GoldenrodGym.asm:12` | `setscene` in the win path, `SCENE_GOLDENRODGYM_NOOP` after | arms the (8,5) trip-wire | -| `ENGINE_PLAINBADGE` | `constants/engine_flags.asm:40` | set in `.StoppedCrying`; read by `StrengthFunction.TryStrength` and `FlowerShopTeacherScript` | Strength field move + Squirtbottle | -| `EVENT_GOT_TM45_ATTRACT` | `constants/event_flags.asm:17` | `.StoppedCrying` | one-shot TM45 | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| PLAINBADGE | second talk after the cry scene | `GoldenrodGymWhitneyScript` `.StoppedCrying` | `ENGINE_PLAINBADGE` | -| `TM_ATTRACT` (TM45) | same conversation | `GoldenrodGymWhitneyScript` | `EVENT_GOT_TM45_ATTRACT` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `VICTORIA` | `BEAUTY` | BEAUTY (1) | L9 Sentret, L13 Sentret, L17 Sentret | `TrainerBeautyVictoria` | none | -| `SAMANTHA` | `BEAUTY` | BEAUTY (2) | L16 Meowth (Scratch, Growl, Bite, Pay Day), L16 Meowth (Scratch, Growl, Bite, Slash) | `TrainerBeautySamantha` | none | -| `CARRIE` | `LASS` | LASS (1) | L18 Snubbull (Scary Face, Charm, Bite, Lick) | `TrainerLassCarrie` | none | -| `BRIDGET` | `LASS` | LASS (2) | L15 Jigglypuff x3 | `TrainerLassBridget` | none | -| `WHITNEY1` | `WHITNEY` | WHITNEY (1), `data/trainers/parties.asm:20` | L18 Clefairy (DoubleSlap, Mimic, Encore, Metronome), L20 Miltank (Rollout, Attract, Stomp, Milk Drink) | `GoldenrodGymWhitneyScript` | none | - ---- - -### Supporting Goldenrod interiors (short form) - -**`GOLDENROD_POKECENTER_1F`** (`maps/GoldenrodPokecenter1F.asm`, header -`data/maps/maps.asm:276`, `map_const GOLDENROD_POKECENTER_1F, 5, 4`): -warps (3,7)/(4,7) -> city 7, (0,7) -> `POKECENTER_2F` 1. Nurse at (3,1). - -**`GOLDENROD_DEPT_STORE_1F`** (`maps/GoldenrodDeptStore1F.asm`, header -`data/maps/maps.asm:279`, `map_const GOLDENROD_DEPT_STORE_1F, 8, 4`): -warps (7,7)/(8,7) -> city 10, (15,0) -> 2F warp 2, (2,0) -> elevator warp 1. - -Mart stock (`data/items/marts.asm`): - -| floor | mart const | items | -|---|---|---| -| 2F | `MART_GOLDENROD_2F_1` / `_2` | `MartGoldenrod2F2`: Poke Ball, Great Ball, Escape Rope, Repel, Revive, Full Heal, Poke Doll, Flower Mail (plus the `_1` heal list) | -| 3F | `MART_GOLDENROD_3F` | X Speed, X Special, X Defend, X Attack, Dire Hit, Guard Spec., X Accuracy | -| 4F | `MART_GOLDENROD_4F` | Protein, Iron, Carbos, Calcium, **HP Up** | -| 5F | `MART_GOLDENROD_5F_1..4` | always TM41 ThunderPunch, TM48 Fire Punch, TM33 Ice Punch; **+ TM02 Headbutt once `EVENT_GOT_TM02_HEADBUTT`**, **+ TM08 Rock Smash once `EVENT_GOT_TM08_ROCK_SMASH`** (`GoldenrodDeptStore5FClerkScript` picks 1/2/3/4) | -| 6F | vending machines | `giveitem FRESH_WATER` / `SODA_POP` / `LEMONADE` (`maps/GoldenrodDeptStore6F.asm`) | - -`GoldenrodDeptStore5F` also holds: -- `GoldenrodDeptStore5FCheckIfSundayCallback` (`MAPCALLBACK_OBJECTS`) - - the receptionist at (7,5) only appears on `SUNDAY`. -- `GoldenrodDeptStore5FReceptionistScript`: Sunday only, one-shot via - `ENGINE_GOLDENROD_DEPT_STORE_TM27_RETURN`. `special GetFirstPokemonHappiness`, - then `>=150` -> `verbosegiveitem TM_RETURN` (TM27), `>=50` -> nothing, - `<50` -> `verbosegiveitem TM_FRUSTRATION` (TM21). That is the walkthrough's - "Sunday giveaway of TMs" and where TM21/TM27 come from. -- `Mike` at (6,3): NPC trade row 1 in `data/events/npc_trades.asm` - - `npctrade TRADE_DIALOGSET_COLLECTOR, DROWZEE, MACHOP, "MUSCLE", $37, $66, GOLD_BERRY, 37460, "MIKE", TRADE_GENDER_EITHER`, - i.e. give Drowzee, get Machop. That is the walkthrough's Machop-by-trading. - -**`GOLDENROD_GAME_CORNER`** (`maps/GoldenrodGameCorner.asm`, header -`data/maps/maps.asm:286`, `map_const GOLDENROD_GAME_CORNER, 10, 7`): -warps (2,13)/(3,13) -> city 11. Prize prices are `DEF`s at the top of the file: - -| prize | coins | const | -|---|---|---| -| Abra (L10) | 200 | `GOLDENRODGAMECORNER_ABRA_COINS` | -| Ekans (L10) | 700 | `GOLDENRODGAMECORNER_EKANS_COINS` (Gold; Silver sells Sandshrew at `..._SANDSHREW_COINS`, also 700) | -| Dratini (L10) | 2100 | `GOLDENRODGAMECORNER_DRATINI_COINS` | -| TM25 Thunder | 5500 | `GOLDENRODGAMECORNER_TM25_COINS` | -| TM14 Blizzard | 5500 | `GOLDENRODGAMECORNER_TM14_COINS` | -| TM38 Fire Blast | 5500 | `GOLDENRODGAMECORNER_TM38_COINS` | - -Mon prizes check `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH` and run -`special GameCornerPrizeMonCheckDex` before `givepoke`. - -**`GOLDENROD_MAGNET_TRAIN_STATION`** (`maps/GoldenrodMagnetTrainStation.asm`, -header `data/maps/maps.asm:274`, `map_const GOLDENROD_MAGNET_TRAIN_STATION, 10, 9`): -warps (8,17)/(9,17) -> city 5; (6,5)/(11,5) -> `SAFFRON_MAGNET_TRAIN_STATION` -4/3. Coord event at (11,6) for `SCENE_GOLDENRODMAGNETTRAINSTATION_ARRIVE_FROM_SAFFRON`. -The conductor is `GoldenrodMagnetTrainStationOfficerScript` at (9,9). - -**`GOLDENROD_FLOWER_SHOP`** (`maps/GoldenrodFlowerShop.asm`, city warp 6 at -(33,5)): `FlowerShopTeacherScript` gives `SQUIRTBOTTLE` gated on -`checkflag ENGINE_PLAINBADGE` + `EVENT_GOT_SQUIRTBOTTLE`. The walkthrough's -"Squirtwater" is this item, and it becomes available only **after** Whitney. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Azalea rival battle | `maps/AzaleaTown.asm` `def_coord_events` (5,10)/(5,11) + `SCENE_AZALEATOWN_RIVAL_BATTLE` | scene armed by `maps/SlowpokeWellB1F.asm:58` | clear Slowpoke Well; battle sets `EVENT_RIVAL_AZALEA_TOWN` and `setscene SCENE_AZALEATOWN_NOOP` | -| Farfetch'd quest exists at all | `maps/SlowpokeWellB1F.asm:60-63` (`clearevent EVENT_ILEX_FOREST_FARFETCHD_1`, `clearevent EVENT_ILEX_FOREST_APPRENTICE`) | `EVENT_CLEARED_SLOWPOKE_WELL` | same scene | -| Ilex Forest cut tree | block `$0f` at Ilex Forest block (4,12) = map (8-9, 24-25); `data/collision/field_move_blocks.asm` `.forest` | a party mon with `CUT` **and** `ENGINE_HIVEBADGE` | `engine/events/overworld.asm` `CutFunction.CheckAble` (`ld de, ENGINE_HIVEBADGE / call CheckBadge`) and `TryCutOW` (`CheckPartyMove` then `CheckEngineFlag ENGINE_HIVEBADGE`) | -| Getting HM01 in the first place | `maps/IlexForest.asm` `IlexForestCharcoalMasterScript`; the master object only spawns after `FarfetchdPosition9` runs `appear 13` + `setevent EVENT_CHARCOAL_KILN_BOSS` | complete the 9-step Farfetch'd herd | `EVENT_HERDED_FARFETCHD`, then `EVENT_GOT_HM01_CUT` | -| Whitney's badge | `maps/GoldenrodGym.asm` `GoldenrodGymWhitneyScript` `.FightDone` -> `checkevent EVENT_MADE_WHITNEY_CRY` `iffalse .StoppedCrying` | `EVENT_MADE_WHITNEY_CRY` must be **clear** | step on the coord event at (8,5) to run `WhitneyCriesScript` | -| Strength field move | `engine/events/overworld.asm` `StrengthFunction.TryStrength` (`ld de, ENGINE_PLAINBADGE / call CheckBadge`) | PLAINBADGE | beat Whitney and collect the badge | -| Squirtbottle | `maps/GoldenrodFlowerShop.asm` `FlowerShopTeacherScript` (`checkflag ENGINE_PLAINBADGE / iffalse .Lalala`) | PLAINBADGE | after Whitney; needed for Sudowoodo next section | -| Route 34 beach trainers + `SOFT_SAND` | water tiles between (3..11, 44..51); `SurfFunction.TrySurf` needs `ENGINE_FOGBADGE` | SURF + FOGBADGE | not reachable in this section | -| Goldenrod Underground basement door | `maps/GoldenrodUnderground.asm` `GoldenrodUndergroundCheckBasementKeyCallback` (`changeblock 18, 6, $3d`) and `BasementDoorScript` (`checkitem BASEMENT_KEY`) | `BASEMENT_KEY` | Rocket-takeover arc, not this section | -| Bill / Eevee | `maps/BillsFamilysHouse.asm` object flag `EVENT_MET_BILL` on `BILLSFAMILYSHOUSE_BILL` | meet Bill in Ecruteak | not this section - only the phone number is available now | -| Game Corner prizes | `maps/GoldenrodGameCorner.asm` prize vendors + coins | `COIN_CASE` | item ball at Goldenrod Underground (7,25) | -| Underground salon / bargain / bitter shops | `GoldenrodUndergroundCheckDayOfWeekCallback` + each script's `readvar VAR_WEEKDAY` | correct weekday (and `MORN` for the bargain merchant) | real-clock dependent | -| Officer Keith battle | `maps/Route34.asm` `OfficerKeithScript` `checktime NITE` | night | real-clock dependent | -| Fly to Goldenrod | `GoldenrodCityFlypointCallback` | none | fires on first map entry | - ---- - -## 4. Bot checklist - -Each row: map, target, intent, precondition, postcondition. - -1. `AZALEA_TOWN` - walk to (5,10) or (5,11). Intent: walk (trip-wire). - Pre: `SCENE_AZALEATOWN_RIVAL_BATTLE`. Post: rival battle - (`RIVAL1`, id `RIVAL1_2_<not-your-starter>`), `EVENT_RIVAL_AZALEA_TOWN`, - scene back to `SCENE_AZALEATOWN_NOOP`. -2. `AZALEA_TOWN` - warp 7 at (2,10). Intent: walk. Post: `ILEX_FOREST_AZALEA_GATE`. -3. `ILEX_FOREST_AZALEA_GATE` - warp 1 at (0,4). Post: `ILEX_FOREST` at (3,42). -4. `ILEX_FOREST` - walk to the apprentice `ILEXFOREST_YOUNGSTER` at (7,28), - talk. Pre: `EVENT_ILEX_FOREST_APPRENTICE` clear (object visible). Post: none - (text only). -5. `ILEX_FOREST` - talk to `ILEXFOREST_FARFETCHD1` at (14,31). Post: bird moves - to (15,25), `EVENT_ILEX_FOREST_FARFETCHD_2`. -6. `ILEX_FOREST` - repeat: walk to the current bird, **face it from the side the - fall-through branch wants**, talk. Use the Pos2..Pos9 table in the map section - above; the safe rule is "never face the direction listed as a back-branch for - that position". Coordinates: Pos2 (15,25), Pos3 (20,24), Pos4 (29,22), - Pos5 (28,31), Pos6 (24,35), Pos7 (22,31), Pos8 (15,29), Pos9 (10,35). - Post at Pos9 fall-through: `EVENT_HERDED_FARFETCHD`, - `EVENT_CHARCOAL_KILN_BOSS`, bird at (6,28), master appears at (5,28). -7. `ILEX_FOREST` - grab the item ball at (20,32). Intent: walk onto / A. - Post: `REVIVE`, `EVENT_ILEX_FOREST_REVIVE`. -8. `ILEX_FOREST` - talk to `ILEXFOREST_BLACK_BELT` at (5,28). - Pre: `EVENT_HERDED_FARFETCHD`. Post: `HM_CUT`, `EVENT_GOT_HM01_CUT`, - apprentice/master/bird despawn here and respawn in the Charcoal Kiln. -9. Teach `CUT` to a party mon (menu). Pre: `HM_CUT` in bag. -10. `ILEX_FOREST` - face the tree at map (8,24) (block (4,12)) and press A, or - use CUT from the pack. Pre: `ENGINE_HIVEBADGE` + party mon with CUT. - Post: block replaced with `$17`, path north opens. -11. `ILEX_FOREST` - optional: hidden Super Potion at (17,7), hidden Full Heal at - (9,17), hidden Ether at (27,1); read the shrine bg at (8,22). -12. `ILEX_FOREST` - talk to `ILEXFOREST_ROCKER` at (15,14). - Post: `TM_HEADBUTT`, `EVENT_GOT_TM02_HEADBUTT`. -13. `ILEX_FOREST` - warp 1 at (1,5). Post: `ROUTE_34_ILEX_FOREST_GATE` at (4,7). -14. `ROUTE_34_ILEX_FOREST_GATE` - talk to the teacher at (9,3). - Post: `TM_SWEET_SCENT`, `EVENT_GOT_TM12_SWEET_SCENT`. -15. `ROUTE_34_ILEX_FOREST_GATE` - warp 1 at (4,0). Post: `ROUTE_34` at (13,37). -16. `ROUTE_34` - Youngster Samuel at (15,33), sight 2 facing random (spinner); - battle. Post: `EVENT_BEAT_YOUNGSTER_SAMUEL`. -17. `ROUTE_34` - PokeFan M Brandon at (19,28), sight 2 facing LEFT. - Post: `EVENT_BEAT_POKEFANM_BRANDON`. -18. `ROUTE_34` - Youngster Ian at (17,22), sight 3 facing DOWN. - Post: `EVENT_BEAT_YOUNGSTER_IAN`. (No phone number - see Unresolved.) -19. `ROUTE_34` - Picnicker Gina at (10,26), sight 3 facing RIGHT. - Post: `EVENT_BEAT_PICNICKER_GINA`; talk again for - `askforphonenumber PHONE_PICNICKER_GINA`. -20. `ROUTE_34` - warp 3 at (11,14) into `DAY_CARE`; talk to the man at (2,3) - and/or the lady at (5,3) to deposit. Post: `ENGINE_DAY_CARE_MAN_HAS_MON` / - `ENGINE_DAY_CARE_LADY_HAS_MON`. -21. `ROUTE_34` - after walking, if `ENGINE_DAY_CARE_MAN_HAS_EGG`, the man stands - at (15,16); talk and answer YES. Post: egg in party, - `ENGINE_DAY_CARE_MAN_HAS_EGG` cleared. -22. `ROUTE_34` - optional night detour: Officer Keith at (9,11), talk. - Pre: `checktime NITE`. Post: `EVENT_BEAT_OFFICER_KEITH`. -23. `ROUTE_34` - Camper Todd at (13,7), sight 5 facing LEFT. - Post: `EVENT_BEAT_CAMPER_TODD`; talk again for - `askforphonenumber PHONE_CAMPER_TODD`. -24. `ROUTE_34` - walk north off the top of the map. Post: `GOLDENROD_CITY` - (connection offset -5), `ENGINE_FLYPOINT_GOLDENROD`, `ENGINE_REACHED_GOLDENROD`. -25. `GOLDENROD_CITY` - warp 7 at (15,27) to heal. -26. `GOLDENROD_CITY` - warp 10 at (24,27) for the Dept Store (optional). -27. `GOLDENROD_CITY` - warp 4 at (5,25) into `BILLS_FAMILYS_HOUSE`; talk to the - twin at (5,4). Post: `PHONE_BILL` registered. -28. `GOLDENROD_CITY` - warp 15 at (11,29) -> switch-room (4,29); walk north to - warp 4 at (5,25) -> `GOLDENROD_UNDERGROUND` at (3,34). -29. `GOLDENROD_UNDERGROUND` - Super Nerd Eric at (5,31) sight 3 facing LEFT. - Post: `EVENT_BEAT_SUPER_NERD_ERIC`. -30. `GOLDENROD_UNDERGROUND` - Pokemaniac Issac at (3,27), sight 2, spins. - Post: `EVENT_BEAT_POKEMANIAC_ISSAC`. -31. `GOLDENROD_UNDERGROUND` - item ball at (7,25). Post: `COIN_CASE`, - `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE`. -32. `GOLDENROD_UNDERGROUND` - Super Nerd Teru at (6,9), sight 2 facing UP. - Post: `EVENT_BEAT_SUPER_NERD_TERU`. -33. `GOLDENROD_UNDERGROUND` - Pokemaniac Donald at (2,6), sight 3 facing RIGHT. - Post: `EVENT_BEAT_POKEMANIAC_DONALD`. -34. `GOLDENROD_UNDERGROUND` - optional: read the basement door bg at (18,6) - (locked; `BASEMENT_KEY` not available yet). Hidden items at (6,13), (4,18), - (17,8). -35. `GOLDENROD_UNDERGROUND` - warp 1 at (3,2) -> switch-room (21,25) -> - warp 8 at (20,29) -> `GOLDENROD_CITY` (9,5). -36. `GOLDENROD_CITY` - warp 2 at (29,29) into `GOLDENROD_BIKE_SHOP`; talk to the - clerk at (7,2) and answer YES. Post: `BICYCLE`, `EVENT_GOT_BICYCLE`, - `ENGINE_BIKE_SHOP_CALL_ENABLED`. -37. `GOLDENROD_CITY` - warp 5 at (9,13) for the train station (flavour only). -38. `GOLDENROD_CITY` - warp 12 at (5,15) into `RADIO_TOWER_1F`; talk to the - woman at (12,6) and answer YES, YES, NO, YES, NO. - Post: `ENGINE_RADIO_CARD`. -39. `GOLDENROD_CITY` - heal (warp 7), then warp 1 at (24,7) into `GOLDENROD_GYM`. -40. `GOLDENROD_GYM` - Beauty Victoria at (0,2), sight 3 facing DOWN. - Post: `EVENT_BEAT_BEAUTY_VICTORIA`. -41. `GOLDENROD_GYM` - Beauty Samantha at (19,5), sight 3 facing DOWN. - Post: `EVENT_BEAT_BEAUTY_SAMANTHA`. -42. `GOLDENROD_GYM` - Lass Carrie at (9,13), sight 4 facing RIGHT. - Post: `EVENT_BEAT_LASS_CARRIE`. -43. `GOLDENROD_GYM` - Lass Bridget at (9,6), sight 1 facing LEFT. - Post: `EVENT_BEAT_LASS_BRIDGET`. -44. `GOLDENROD_GYM` - talk to Whitney at (8,3). Post: `EVENT_BEAT_WHITNEY`, - `EVENT_MADE_WHITNEY_CRY`, `SCENE_GOLDENRODGYM_WHITNEY_STOPS_CRYING`. -45. `GOLDENROD_GYM` - **walk onto (8,5)**. Intent: walk (trip-wire). - Post: `WhitneyCriesScript`, `EVENT_MADE_WHITNEY_CRY` cleared, - `SCENE_GOLDENRODGYM_NOOP`. -46. `GOLDENROD_GYM` - talk to Whitney at (8,3) again. - Post: `ENGINE_PLAINBADGE`, `TM_ATTRACT`, `EVENT_GOT_TM45_ATTRACT`. -47. Optional after the badge: `GOLDENROD_CITY` warp 6 at (33,5) -> - `GOLDENROD_FLOWER_SHOP`, talk to the teacher. Post: `SQUIRTBOTTLE`, - `EVENT_GOT_SQUIRTBOTTLE` (needed for the next section's Sudowoodo). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers / dimensions / connections for every map above | `src/import/RomExtractorGen2.lua` (`readMapGroupEntry`, `mapNameByIds`), `src/world/gen2/Map.lua` | implemented (generic, ROM-driven; nothing map-specific is hand-ported) | -| `def_warp_events` / `def_coord_events` / `def_bg_events` / `def_object_events` decode | `src/import/RomExtractorGen2.lua:804` (`coordEvents`), `:861`, `:974` | implemented | -| Coord-event trip-wires (Azalea rival, Whitney cries) | `src/world/gen2/World.lua:5013` (iterates `self.map.def.coordEvents`) | implemented | -| Scene ids / `setscene` / `setmapscene` | `src/script/gen2/Opcodes.lua` (`0x11`-`0x14`), `src/script/gen2/Vm.lua` | implemented | -| Script VM opcodes used by this section (`verbosegiveitem`, `loadtrainer`, `startbattle`, `winlosstext`, `setlasttalked`, `applymovement`, `moveobject`, `turnobject`, `appear`/`disappear`, `showemote`, `changeblock`, `refreshmap`, `givepoke`, `giveitem`, `checkcoins`/`takecoins`, `askforphonenumber`, `addcellnum`/`checkcellnum`, `giveegg`, `pokemart`, `fruittree`, `checktime`, `readvar`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` | implemented (all present) | -| `readvar VAR_FACING` (the whole Farfetch'd branch logic) | `src/world/gen2/World.lua:95,1219` | implemented | -| Item balls / hidden items | `src/import/RomExtractorGen2.lua:2874,2882` (`readItemBall`, `hiddenitem`), `src/world/gen2/HiddenItems.lua` | implemented | -| Trainer sight lines and `trainer` struct | `src/world/gen2/Trainers.lua:98` (`Trainers.sees`), extractor `:2961` | implemented | -| CUT badge gate + A-press cut (`TryCutOW` / `CutFunction`) | `src/world/gen2/FieldMoves.lua:104` (`CUT = "HIVE"`), `:449`, `:609`; `src/world/gen2/World.lua:4203` (`World:tryCutOW`), `:3965` (`World:runCut`), `:3872` (`CutDownTreeOrGrass`) | implemented | -| STRENGTH badge gate (`PLAINBADGE`) | `src/world/gen2/FieldMoves.lua:108`, `:519` | implemented | -| Bicycle | `src/world/gen2/Bike.lua`, `src/world/gen2/World.lua:1790` | implemented | -| Coin Case / Game Corner slots and prizes | `src/core/gen2/CoinCase.lua`, `src/ui/gen2/SlotMachine.lua`, `src/ui/gen2/PrizeMenu.lua`, `src/script/gen2/Specials.lua:964` (`GameCornerPrizeMonCheckDex`) | implemented | -| Day-Care / breeding / egg handoff (`DayCareMan`, `DayCareLady`, `DayCareManOutside`, `DayCareMon1/2`) | `src/core/gen2/Breeding.lua`, `src/script/gen2/Specials.lua:526-596`, `src/ui/gen2/DayCareMenu.lua`; driver `tests/drivers/gold_egg_hatch.lua` | implemented | -| Phone numbers (Bill, Todd, Gina) | `src/core/gen2/Phone.lua`, `src/script/gen2/Opcodes.lua:156` | implemented | -| Pokegear Radio Card + radio channels | `src/ui/gen2/Pokegear.lua:39` (`{ id = "radio", ..., flag = "radio" }`), `:49` onward | implemented | -| Haircut brothers / weekday NPC schedule | `src/script/gen2/Specials.lua:1661-1662`, `src/world/gen2/World.lua:99` | implemented | -| NPC trade (Mike: Drowzee -> Machop) | `src/core/gen2/NpcTrade.lua`, `src/ui/gen2/TradeMenu.lua`, `src/ui/gen2/TradeAnim.lua` | implemented | -| Happiness -> TM27/TM21 Sunday lady | `src/core/gen2/Happiness.lua`, `src/script/gen2/Specials.lua:1506` (`GetFirstPokemonHappiness`) | implemented | -| Marts (`MARTTYPE_STANDARD` / `_BARGAIN` / `_BITTER`) | `src/ui/gen2/MartMenu.lua` | implemented (verify the bargain/bitter variants by hand - only the standard path has driver coverage) | -| Magnet train (locked here, station map only) | `src/core/gen2/MagnetTrain.lua`, `src/ui/gen2/MagnetTrainRide.lua` | implemented | -| End-to-end driver for this stretch | none - `tests/drivers/gold_*.lua` covers boot, walk, warp, battle, egg hatch, evolution, boulders (`gold_icepath_boulder.lua`), map callbacks | **missing** - there is no Ilex-Forest / Goldenrod driver; the Farfetch'd chase and the Whitney cry-then-badge sequence are unexercised | -| Farfetch'd chase specifically | no dedicated port file; runs on the generic VM | untested (no driver) | -| Whitney badge two-step (`EVENT_MADE_WHITNEY_CRY` + coord event) | generic VM + `World.lua:5013` | untested (no driver) | - ---- - -## 6. Unresolved / verify by hand - -Contradictions between the walkthrough text and `pokegold`: - -1. **"Bug Catcher Wayne" in Ilex Forest does not exist in Gold.** - `grep -rn WAYNE constants/ data/ maps/` returns only `BIKER DWAYNE` on - `maps/Route8.asm`. `maps/IlexForest.asm` has **zero** `OBJECTTYPE_TRAINER` - rows. Wayne (L8 Ledyba / L10 Paras) is Crystal-only content. -2. **"X Attack" and "Antidote" in Ilex Forest do not exist in Gold.** - The only items on the map are the `REVIVE` item ball at (20,32) and hidden - `ETHER` (27,1), `SUPER_POTION` (17,7), `FULL_HEAL` (9,17). The walkthrough's - own item list ("HM01, Revive, TM02, TM12") also omits them, so the prose - contradicts its own header. TM12 is in the gate map, not the forest. -3. **Ilex Forest species list.** The walkthrough lists Weedle (#013). In Gold - the morn/day slots are Caterpie/Metapod (`IF DEF(_GOLD)`); Weedle/Kakuna is - the `ELIF DEF(_SILVER)` arm. Oddish is nite-only. Metapod is Gold-only. -4. **"Youngster Ian will want to trade phone numbers with you."** - `TrainerYoungsterIan.Script` in `maps/Route34.asm` is just - `endifjustbattled` / `writetext YoungsterIanAfterText`. No `askforphonenumber`, - no `PHONE_*` constant. The Route 34 phone trainers are Camper Todd and - Picnicker Gina only. -5. **Rival party order.** The walkthrough prints Gastly / starter / Zubat; the - asm (`Rival1Group` entries 4-6) is Gastly (12), Zubat (14), starter (16). -6. **"There's a PC in the corner of the Daycare."** `maps/DayCare.asm` has two - bookshelf bg events and two NPC objects; no PC bg event or object. -7. **"Level 5: TM02, TM33, TM41, TM48"** is right in spirit but conditional: - `MART_GOLDENROD_5F_1` (the default) is only TM41 ThunderPunch, TM48 Fire - Punch, TM33 Ice Punch. TM02 Headbutt is added only after - `EVENT_GOT_TM02_HEADBUTT`, and TM08 Rock Smash after - `EVENT_GOT_TM08_ROCK_SMASH` (`GoldenrodDeptStore5FClerkScript`). -8. **Game Corner "Ekans/Sandshrew 700"** - the version split is real; Gold's - branch (`.Gold_Ekans`) sells Ekans. `GOLDENRODGAMECORNER_SANDSHREW_COINS` is - the Silver constant, also 700. -9. **"Get Eevee from Bill" is listed under Goldenrod** but `BILLSFAMILYSHOUSE_BILL` - carries the object flag `EVENT_MET_BILL`, which is set in Ecruteak. Only - `BillsYoungerSisterScript` (Bill's phone number) is reachable in this section. -10. **Prose ordering glitch in the walkthrough**: "Now head out of the gym and - we'll hit the east side of town" appears before the gym is ever entered. - Treat the bike shop / radio tower paragraphs as pre-gym. -11. **Whether the Azalea rival coord events at (5,10)/(5,11) can be walked - around** was not verified - it needs a collision read of `maps/AzaleaTown.blk` - plus the tileset collision table, which I did not decode. The two rows are - the only trip-wires on the map, so a bot should assume they can be dodged - and treat `EVENT_RIVAL_AZALEA_TOWN` as the real precondition for later - content rather than relying on the walk-west path. -12. **Exact walkable route through the Farfetch'd chase** (which tile to stand - on so the player's facing lands on the fall-through branch) was not derived; - only the branch table was read from the asm. A bot needs the collision map - of `maps/IlexForest.blk` to turn "face away from `<DIR>`" into concrete - approach tiles. -13. **`data/wild/fish.asm`** was searched for `ILEX_FOREST` / `ROUTE_34` and has - no per-map rows - fishing is selected by the header's `FISHGROUP_*` - (`FISHGROUP_POND` for Ilex Forest, `FISHGROUP_SHORE` for Route 34). The - per-group tables were not transcribed here. -14. **Exp/money numbers** quoted by the walkthrough (e.g. "You get: 960G") were - not verified; they are computed at runtime from base exp and level, not - stored in `data/trainers/parties.asm`. diff --git a/docs/gold-walkthrough/asm-walk/section-06-bug-catching-contest-and-sudowoodo.md b/docs/gold-walkthrough/asm-walk/section-06-bug-catching-contest-and-sudowoodo.md deleted file mode 100644 index 67d8205a..00000000 --- a/docs/gold-walkthrough/asm-walk/section-06-bug-catching-contest-and-sudowoodo.md +++ /dev/null @@ -1,987 +0,0 @@ -# Section 06 - Bug Catching Contest and Sudowoodo - -Source: `../section-06-bug-catching-contest-and-sudowoodo.txt` -Maps covered: `MAP_GOLDENROD_FLOWER_SHOP`, `MAP_ROUTE_35_GOLDENROD_GATE`, `MAP_ROUTE_35`, -`MAP_ROUTE_35_NATIONAL_PARK_GATE`, `MAP_NATIONAL_PARK`, `MAP_NATIONAL_PARK_BUG_CONTEST`, -`MAP_ROUTE_36_NATIONAL_PARK_GATE`, `MAP_ROUTE_36`, `MAP_ROUTE_37` (listed by the -walkthrough header, never entered by its route) - -Badges / key milestones in this section: - -- SQUIRTBOTTLE (key item) - gated behind `ENGINE_PLAINBADGE`, i.e. Whitney must already - be beaten before this section can be started at all. -- Bug-Catching Contest run (Tue/Thu/Sat only) - SUN STONE / EVERSTONE / GOLD BERRY / BERRY. -- `EVENT_FOUGHT_SUDOWOODO` - the single event that unblocks Route 36 west<->east, which - is the only land route between Goldenrod and Violet through Route 36. -- TM08 ROCK SMASH (`EVENT_GOT_TM08_ROCK_SMASH`), TM04 ROLLOUT, TM28 DIG, QUICK CLAW. -- No badge is earned in this section. - -Everything in this file was transcribed from files that were opened; nothing is -reconstructed from memory. Symbol addresses are `bank:addr` from -`pokegold-symbols/pokegold.sym`. - ---- - -## 1. Route order - -The walkthrough's own prose is out of order in one place: it opens by collecting the -SQUIRTBOTTLE in Goldenrod, then much later describes Floria running off to fetch it. -In pokegold the SQUIRTBOTTLE has no Route 36 precondition at all (see -"Unresolved / verify by hand"), so the opening order is the one a bot should follow. - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_GOLDENROD_FLOWER_SHOP` | `maps/GoldenrodFlowerShop.asm` | `GOLDENROD_CITY` warp 6 @ (33, 5) | warp 1/2 @ (2,7)/(3,7) -> `GOLDENROD_CITY` 6 | Talk to the teacher for the SQUIRTBOTTLE | -| 2 | `MAP_ROUTE_35_GOLDENROD_GATE` | `maps/Route35GoldenrodGate.asm` | `GOLDENROD_CITY` warp 13 @ (19, 1) -> gate warp 3 | warp 1/2 @ (4,0)/(5,0) -> `ROUTE_35` 1/2 | Randy hands over Kenya the SPEAROW with FLOWER MAIL | -| 3 | `MAP_ROUTE_35` | `maps/Route35.asm` | gate warp @ (9,33)/(10,33) | warp 3 @ (3, 5) -> `ROUTE_35_NATIONAL_PARK_GATE` 3 | Six trainers + Officer Dirk, walk north | -| 4 | `MAP_ROUTE_35_NATIONAL_PARK_GATE` | `maps/Route35NationalParkGate.asm` | warp 3/4 @ (3,7)/(4,7) | warp 1/2 @ (3,0)/(4,0) -> `NATIONAL_PARK` 3/4 | Contest officer; optional entry to the Contest | -| 5 | `MAP_NATIONAL_PARK_BUG_CONTEST` | `maps/NationalParkBugContest.asm` | script `warp NATIONAL_PARK_BUG_CONTEST, 10, 47` from the south gate (or `33, 18` from the north gate) | contest timer / balls exhausted, or either gate | Optional Tue/Thu/Sat Contest | -| 6 | `MAP_NATIONAL_PARK` | `maps/NationalPark.asm` | warp 3/4 @ (10,47)/(11,47) | warp 1/2 @ (33,18)/(33,19) -> `ROUTE_36_NATIONAL_PARK_GATE` 1/2 | QUICK CLAW, Beverly, Jack, Krise, William, TM28 DIG | -| 7 | `MAP_ROUTE_36_NATIONAL_PARK_GATE` | `maps/Route36NationalParkGate.asm` | warp 1/2 @ (0,4)/(0,5) | warp 3/4 @ (9,4)/(9,5) -> `ROUTE_36` 1/2 | Contest results / prize holding | -| 8 | `MAP_ROUTE_36` | `maps/Route36.asm` | warp 1/2 @ (18,8)/(18,9) | south connection @ y=17 -> `ROUTE_35`; later east connection -> `VIOLET_CITY` | Ice Berry, Psychic Mark, Schoolboy Alan, Sudowoodo, TM08 | -| 9 | `MAP_ROUTE_35` (again) | `maps/Route35.asm` | north connection from `ROUTE_36` (offset 0) | back north to `ROUTE_36` | Cut the tree, Bug Catcher Arnie, Bird Keeper Bryan, TM04 ROLLOUT | -| 10 | `MAP_ROUTE_36` (again) | `maps/Route36.asm` | north connection from `ROUTE_35` | **east connection -> `VIOLET_CITY`** | Sudowoodo cleared, then east into Violet City | - -Spills into the next section: step 10's east connection -(`connection east, VioletCity, VIOLET_CITY, 0` in `data/maps/attributes.asm`) drops the -player into `MAP_VIOLET_CITY`, which belongs to a neighbouring section. Route 37 -(`MAP_ROUTE_37`, north connection off Route 36 with offset 10) is named in this section's -header but the walkthrough never routes into it; its data is recorded below for -completeness because the item list ("Blk/Blu/Red Apricorn", "Ice Berry") mixes 36 and 37. - ---- - -## 2. Maps - -### MAP_GOLDENROD_FLOWER_SHOP - -- Script: `maps/GoldenrodFlowerShop.asm` -- Blocks: `maps/GoldenrodFlowerShop.blk` -- Header (`data/maps/maps.asm:275`): `TILESET_HOUSE, INDOOR, LANDMARK_GOLDENROD_CITY, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:253` -> `map_const GOLDENROD_FLOWER_SHOP, 4, 4` -- Connections: none (indoor) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 6 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 6 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - none. (`FlowerShopShelf1`, `FlowerShopShelf2`, -`FlowerShopRadio` exist in the file but are marked `; unreferenced`.) - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODFLOWERSHOP_TEACHER` | `SPRITE_TEACHER` | 2 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `FlowerShopTeacherScript` | -1 | -| `GOLDENRODFLOWERSHOP_FLORIA` | `SPRITE_LASS` | 5 | 6 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `FlowerShopFloriaScript` | -1 | - -**Scripts of interest** - -- `FlowerShopTeacherScript` (`57:52d1`) - `checkevent EVENT_GOT_SQUIRTBOTTLE` / - `iftrue .Lalala`; then `checkflag ENGINE_PLAINBADGE` / `iffalse .Lalala`. Only with the - badge and without the event does it `faceplayer` / `opentext` / - `writetext GoldenrodFlowerShopTeacherBetterThanWhitneyText` / `promptbutton` / - `verbosegiveitem SQUIRTBOTTLE` / `setevent EVENT_GOT_SQUIRTBOTTLE`. **Note the bug in - the cart**: there is no `iffalse` after `verbosegiveitem`, so a full PACK still sets - `EVENT_GOT_SQUIRTBOTTLE` and the item is lost. A bot must have a free KEY ITEM slot. -- `FlowerShopFloriaScript` (`57:52f4`) - pure flavour, branches on `ENGINE_PLAINBADGE`. - Gives nothing. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_SQUIRTBOTTLE` | `constants/event_flags.asm:101` | read+written by `FlowerShopTeacherScript` | one-shot; set even on a full bag | -| `ENGINE_PLAINBADGE` | `constants/engine_flags.asm:40` | read by `FlowerShopTeacherScript`, `FlowerShopFloriaScript` | Whitney's badge is the hard precondition for the whole section | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `SQUIRTBOTTLE` | talk to teacher at (2,4) | `FlowerShopTeacherScript` `verbosegiveitem` | `EVENT_GOT_SQUIRTBOTTLE` | - -**Trainers** - none. - -**Wild encounters** - none (indoor). - ---- - -### MAP_ROUTE_35_GOLDENROD_GATE - -- Script: `maps/Route35GoldenrodGate.asm` -- Blocks: no `.blk` in `maps/` for this map (gate maps of this group share generated - attributes; `data/maps/attributes.asm:542` carries `map_attributes Route35GoldenrodGate, ROUTE_35_GOLDENROD_GATE, $00`) -- Header (`data/maps/maps.asm:260`): `TILESET_GATE, GATE, LANDMARK_ROUTE_35, MUSIC_ROUTE_36, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:239` -> `map_const ROUTE_35_GOLDENROD_GATE, 5, 4` -- Connections: none. `GATE` environment is why the walkthrough's "ride your bike through" - works. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_35` | 1 | -| 2 | 5 | 0 | `ROUTE_35` | 2 | -| 3 | 4 | 7 | `GOLDENROD_CITY` | 13 | -| 4 | 5 | 7 | `GOLDENROD_CITY` | 13 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE35GOLDENRODGATE_RANDY` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `RandyScript` | -1 | -| `ROUTE35GOLDENRODGATE_POKEFAN_F` | `SPRITE_POKEFAN_F` | 6 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (0,1) | `OBJECTTYPE_SCRIPT` | `Route35GoldenrodGatePokefanFScript` | -1 | - -**Scripts of interest** - -- `RandyScript` (`56:59ce`) - four-way branch, checked in this order: - `EVENT_GOT_HP_UP_FROM_RANDY` -> `.gothpup`; `EVENT_GAVE_KENYA` -> `.questcomplete` - (`verbosegiveitem HP_UP`, `setevent EVENT_GOT_HP_UP_FROM_RANDY`); - `EVENT_GOT_KENYA` -> `.alreadyhavekenya`; otherwise the offer. On YES it prints thanks, - then `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .partyfull`. Only with a free - slot does it run `givepoke SPEAROW, 10, NO_ITEM, GiftSpearowName, GiftSpearowOTName`, - `givepokemail GiftSpearowMail`, `setevent EVENT_GOT_KENYA`. - - `GiftSpearowMail` = `FLOWER_MAIL`, text `"DARK CAVE leads / to another road"`. - - `GiftSpearowName` = `KENYA`, `GiftSpearowOTName` = `RANDY`. - - The delivery target (`EVENT_GAVE_KENYA`) is on Route 31, i.e. a later section. -- `Route35GoldenrodGatePokefanFScript` - flavour, branches on `EVENT_FOUGHT_SUDOWOODO`; - its pre-Sudowoodo text is the in-game hint that the SQUIRTBOTTLE is what wakes the tree. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_KENYA` | `constants/event_flags.asm:89` | `RandyScript` | Spearow accepted | -| `EVENT_GAVE_KENYA` | `constants/event_flags.asm:90` | read here, set on Route 31 | enables the HP UP reward | -| `EVENT_GOT_HP_UP_FROM_RANDY` | `constants/event_flags.asm:91` | `RandyScript` | reward taken | -| `EVENT_FOUGHT_SUDOWOODO` | `constants/event_flags.asm:51` | read here | flavour only on this map | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| SPEAROW "KENYA" (lv 10) + `FLOWER_MAIL` | say YES to Randy with a party slot free | `RandyScript` `givepoke` / `givepokemail` | `EVENT_GOT_KENYA` | -| `HP_UP` | return after delivering to Route 31 | `RandyScript` `.questcomplete` | `EVENT_GOT_HP_UP_FROM_RANDY` | - -**Trainers** - none. **Wild encounters** - none. - ---- - -### MAP_ROUTE_35 - -- Script: `maps/Route35.asm` -- Blocks: `maps/Route35.blk` (180 bytes = 10 x 18 blocks) -- Header (`data/maps/maps.asm:248`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_35, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:227` -> `map_const ROUTE_35, 10, 18` (so cell - coordinates run 0..19 in x and 0..35 in y) -- Connections (`data/maps/attributes.asm:202`): north `ROUTE_36` offset 0, south - `GOLDENROD_CITY` offset -5 - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 33 | `ROUTE_35_GOLDENROD_GATE` | 1 | -| 2 | 10 | 33 | `ROUTE_35_GOLDENROD_GATE` | 2 | -| 3 | 3 | 5 | `ROUTE_35_NATIONAL_PARK_GATE` | 3 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 7 | `BGEVENT_READ` | `Route35Sign` | -| 11 | 31 | `BGEVENT_READ` | `Route35Sign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE35_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 3 | 19 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` sight 4 | `TrainerCamperIvan` | -1 | -| `ROUTE35_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 8 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerCamperElliot` | -1 | -| `ROUTE35_LASS1` | `SPRITE_LASS` | 7 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerPicnickerBrooke` | -1 | -| `ROUTE35_LASS2` | `SPRITE_LASS` | 11 | 24 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerPicnickerKim` | -1 | -| `ROUTE35_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 14 | 28 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 0 | `TrainerBirdKeeperBryan` | -1 | -| `ROUTE35_FISHER` | `SPRITE_FISHER` | 2 | 10 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerFirebreatherWalt` | -1 | -| `ROUTE35_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 16 | 7 | `STANDING_DOWN` (radius 2,0) | `OBJECTTYPE_TRAINER` sight 3 | `TrainerBugCatcherArnie` | -1 | -| `ROUTE35_SUPER_NERD` | `SPRITE_SUPER_NERD` | 5 | 10 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerJugglerIrwin` | -1 | -| `ROUTE35_OFFICER` | `SPRITE_OFFICER` | 5 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TrainerOfficerDirk` | -1 | -| `ROUTE35_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 2 | 25 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route35FruitTree` (`4b:5c07`) | -1 | -| `ROUTE35_POKE_BALL` | `SPRITE_POKE_BALL` | 13 | 16 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route35TMRollout` (`4b:5c05`) | `EVENT_ROUTE_35_TM_ROLLOUT` | - -**Scripts of interest** - -- `TrainerOfficerDirk` - **not** an `OBJECTTYPE_TRAINER` object. It is a plain script that - runs `checktime NITE` / `iffalse .NotNight` first, so Dirk only battles at night. Then - `checkevent EVENT_BEAT_OFFICER_DIRK`, `playmusic MUSIC_OFFICER_ENCOUNTER`, - `winlosstext OfficerDirkBeatenText, 0`, `loadtrainer OFFICER, DIRK`, `startbattle`, - `reloadmapafterbattle`, `setevent EVENT_BEAT_OFFICER_DIRK`. A daytime bot will get - `OfficerDirkPrettyToughText` and no battle. -- `TrainerJugglerIrwin` / `TrainerBugCatcherArnie` - standard phone trainers. After the - win, `checkcellnum PHONE_JUGGLER_IRWIN` / `PHONE_BUG_CATCHER_ARNIE`, then - `setevent EVENT_IRWIN_ASKED_FOR_PHONE_NUMBER` / `EVENT_ARNIE_ASKED_FOR_PHONE_NUMBER` - and `askforphonenumber`. The walkthrough's "head back to Arnie and talk to her for her - phone number" is this second talk. - Rematch tiers: Irwin uses `EVENT_CLEARED_RADIO_TOWER` -> `IRWIN3`, - `ENGINE_FLYPOINT_CIANWOOD` -> `IRWIN2`, else `IRWIN1`. Arnie uses - `ENGINE_FLYPOINT_BLACKTHORN` -> `ARNIE3`, `ENGINE_FLYPOINT_LAKE_OF_RAGE` -> `ARNIE2`, - else `ARNIE1`. -- `Route35TMRollout` -> `itemball TM_ROLLOUT`. `Route35FruitTree` -> `fruittree FRUITTREE_ROUTE_35`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_PICNICKER_KIM` / `_CAMPER_ELLIOT` / `_PICNICKER_BROOKE` / `_CAMPER_IVAN` / `_JUGGLER_IRWIN` / `_FIREBREATHER_WALT` / `_BUG_CATCHER_ARNIE` / `_BIRD_KEEPER_BRYAN` | `constants/event_flags.asm` | `trainer` macro rows | per-trainer defeated bits | -| `EVENT_BEAT_OFFICER_DIRK` | `constants/event_flags.asm` | `TrainerOfficerDirk` | set by hand, not by the `trainer` macro | -| `EVENT_ROUTE_35_TM_ROLLOUT` | `constants/event_flags.asm:1107` | item ball object | TM04 taken | -| `EVENT_IRWIN_ASKED_FOR_PHONE_NUMBER`, `EVENT_IRWIN_READY_FOR_REMATCH` | `constants/event_flags.asm` | Irwin script | phone state machine | -| `EVENT_ARNIE_ASKED_FOR_PHONE_NUMBER`, `EVENT_ARNIE_READY_FOR_REMATCH` | `constants/event_flags.asm` | Arnie script | phone state machine | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_ROLLOUT` (TM04, `constants/item_constants.asm:223`) | item ball at cell (13, 16) | `Route35TMRollout` | `EVENT_ROUTE_35_TM_ROLLOUT` | -| `MYSTERYBERRY` | fruit tree object at (2, 25) | `FRUITTREE_ROUTE_35` -> `data/items/fruit_trees.asm:14` | daily reset (`Apricorns`/`TryResetFruitTrees` equivalent) | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `KIM` | `PICNICKER` (0x35) | 4 | `parties.asm:2601` `KIM` - lv15 VULPIX | `TrainerPicnickerKim` | no | -| `ELLIOT` | `CAMPER` (0x36) | 4 | `parties.asm:2722` `ELLIOT` - lv13 SANDSHREW, lv15 MARILL | `TrainerCamperElliot` | no | -| `BROOKE` | `PICNICKER` (0x35) | 3 | `parties.asm:2596` `BROOKE` `TRAINERTYPE_MOVES` - lv16 PIKACHU (THUNDERSHOCK, GROWL, QUICK_ATTACK, DOUBLE_TEAM) | `TrainerPicnickerBrooke` | no | -| `IVAN` | `CAMPER` (0x36) | 3 | `parties.asm:2715` `IVAN` - lv10 DIGLETT, lv10 ZUBAT, lv14 DIGLETT | `TrainerCamperIvan` | no | -| `IRWIN1` | `JUGGLER` (0x31) | 1 | `parties.asm:2384` `IRWIN` - lv2/6/10/14 VOLTORB | `TrainerJugglerIrwin` | yes (`PHONE_JUGGLER_IRWIN`); IRWIN2 `parties.asm:2407`, IRWIN3 `parties.asm:2415` | -| `WALT` | `FIREBREATHER` (0x30) | 6 | `parties.asm:2365` `WALT` - lv11 MAGMAR, lv13 MAGMAR | `TrainerFirebreatherWalt` | no | -| `DIRK` | `OFFICER` (0x41) | 2 | `parties.asm:3172` `DIRK` - lv14 GROWLITHE, lv14 GROWLITHE | `TrainerOfficerDirk` | night only | -| `ARNIE1` | `BUG_CATCHER` (0x24) | 8 | `parties.asm:1455` `ARNIE` - lv15 VENONAT | `TrainerBugCatcherArnie` | yes (`PHONE_BUG_CATCHER_ARNIE`); ARNIE2 `parties.asm:1487`, ARNIE3 `parties.asm:1492` (lv28 VENOMOTH w/ moves) | -| `BRYAN` | `BIRD_KEEPER` (0x18) | 3 | `parties.asm:534` `BRYAN` - lv12 PIDGEY, lv14 PIDGEOTTO | `TrainerBirdKeeperBryan` | no | - -**Wild encounters** - `data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_35`, -rates 10/10/10 percent (morn/day/nite). Gold table: - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 12 NIDORAN_M | 12 NIDORAN_M | 12 NIDORAN_M | -| 2 | 12 NIDORAN_F | 12 NIDORAN_F | 12 NIDORAN_F | -| 3 | 14 DROWZEE | 14 DROWZEE | 14 DROWZEE | -| 4 | 10 ABRA | 10 ABRA | 10 ABRA | -| 5 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT | -| 6 | 10 DITTO | 10 DITTO | 10 DITTO | -| 7 | 12 YANMA | 12 YANMA | 12 YANMA | - -(Silver swaps slots 1/2 to NIDORAN_F/NIDORAN_M.) - -Water (`data/wild/johto_water.asm:156` `def_water_wildmons ROUTE_35`): 4 percent - -20 PSYDUCK, 15 PSYDUCK, 20 GOLDUCK. Fishing group is `FISHGROUP_POND` -(`data/wild/fish.asm:15`). Headbutt: `data/wild/treemon_maps.asm:16` -> -`TREEMON_SET_FOREST` (`data/wild/treemons.asm:34`; Gold common CATERPIE/METAPOD/ -EXEGGCUTE/BUTTERFREE, rare adds PINECO). Swarm: `data/wild/swarm_grass.asm:6` is the -YANMA swarm table for `ROUTE_35`. - -**Cut tree** - `maps/Route35.blk` has exactly one cuttable block: block id `$5b` at block -(8, 3), i.e. walk cells x 16-17, y 6-7. `data/collision/field_move_blocks.asm:14` gives -`db $5b, $3c, 0` for `TILESET_JOHTO`. That is the tree the walkthrough tells the player to -cut, and Bug Catcher Arnie at (16, 7) sits immediately behind it. Route 36, Route 37 and -National Park have **no** cuttable blocks (National Park's `.park` entry in -`field_move_blocks.asm` is only the two grass ids `$13` and `$03`). - ---- - -### MAP_ROUTE_35_NATIONAL_PARK_GATE - -- Script: `maps/Route35NationalParkGate.asm` -- Blocks: `maps/Route35NationalParkGate.blk` -- Header (`data/maps/maps.asm:261`): `TILESET_GATE, INDOOR, LANDMARK_ROUTE_35, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:240` -> `map_const ROUTE_35_NATIONAL_PARK_GATE, 4, 4` -- Connections: none - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 0 | `NATIONAL_PARK` | 3 | -| 2 | 4 | 0 | `NATIONAL_PARK` | 4 | -| 3 | 3 | 7 | `ROUTE_35` | 3 | -| 4 | 4 | 7 | `ROUTE_35` | 3 | - -**Coord events** - none. The map instead uses scene scripts: - -| scene id | value | script | effect | -|---|---|---|---| -| `SCENE_ROUTE35NATIONALPARKGATE_NOOP` | 0 | `Route35NationalParkGateNoop1Scene` | `end` | -| `SCENE_ROUTE35NATIONALPARKGATE_UNUSED` | 1 | `Route35NationalParkGateNoop2Scene` | `end` | -| `SCENE_ROUTE35NATIONALPARKGATE_LEAVE_CONTEST_EARLY` | 2 | `Route35NationalParkGateLeaveContestEarlyScene` | `sdefer Route35NationalParkGateLeavingContestEarlyScript` | - -(The `SCENE_*` names are generated by the `scene_script` macro, -`macros/scripts/maps.asm:25`, in the order they appear in this file - there is no -separate constants file for them.) - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 0 | `BGEVENT_READ` | `BugCatchingContestExplanationSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE35NATIONALPARKGATE_OFFICER1` | `SPRITE_OFFICER` | 2 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route35OfficerScriptContest` (`56:5e0c`) | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` | -| `ROUTE35NATIONALPARKGATE_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 5 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `Route35NationalParkGateYoungsterScript` | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_YOUNGSTER` | -| `ROUTE35NATIONALPARKGATE_OFFICER2` | `SPRITE_OFFICER` | 0 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route35NationalParkGateOfficerScript` | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_NOT_CONTEST_DAY` | - -**Callbacks** - -- `MAPCALLBACK_NEWMAP` -> `Route35NationalParkGateCheckIfContestRunningCallback`: - `checkflag ENGINE_BUG_CONTEST_TIMER` -> `setscene SCENE_..._LEAVE_CONTEST_EARLY`, - else `setscene SCENE_..._NOOP`. -- `MAPCALLBACK_OBJECTS` -> `Route35NationalParkGateCheckIfContestAvailableCallback` - (`56:5db8`): `readvar VAR_WEEKDAY`; `TUESDAY`/`THURSDAY`/`SATURDAY` -> - `appear OFFICER1`, `disappear YOUNGSTER`, `disappear OFFICER2`. Otherwise - `checkflag ENGINE_BUG_CONTEST_TIMER` (mid-contest keeps the contest officer), else the - non-contest pair appears. - -**Scripts of interest** - -- `Route35OfficerScriptContest` (`56:5e0c`) - the entry point. Order of checks: - 1. `readvar VAR_WEEKDAY`; SUNDAY/MONDAY/WEDNESDAY/FRIDAY -> `Route35NationalParkGate_NoContestToday`. - 2. `checkflag ENGINE_DAILY_BUG_CONTEST` -> `Route35NationalParkGate_ContestIsOver`. - 3. `yesorno`. NO -> `Route35NationalParkGate_DeclinedToParticipate`. - 4. `readvar VAR_PARTYCOUNT` / `ifgreater 1, Route35NationalParkGate_LeaveTheRestBehind`. - With more than one mon it needs box space (`readvar VAR_BOXSPACE`, `ifequal 0` -> - `_NoRoomInBox`), rejects an EGG lead (`special CheckFirstMonIsEgg`) and rejects a - fainted lead (`special ContestDropOffMons` -> `iftrue _FirstMonIsFainted`), then - `setevent EVENT_LEFT_MONS_WITH_CONTEST_OFFICER`. - 5. `Route35NationalParkGate_OkayToProceed`: `setflag ENGINE_BUG_CONTEST_TIMER`, - `special PlayMapMusic`, park ball text, `special GiveParkBalls`, - `special FadeOutToWhite`, `special SelectRandomBugContestContestants`, - `warp NATIONAL_PARK_BUG_CONTEST, 10, 47`. -- `Route35NationalParkGateLeavingContestEarlyScript` - runs when re-entering this gate - while `ENGINE_BUG_CONTEST_TIMER` is set. `readvar VAR_CONTESTMINUTES` / - `addval 1` / `getnum STRING_BUFFER_3`, `yesorno`. YES -> - `jumpstd BugContestResultsWarpScript`. NO -> `warp NATIONAL_PARK_BUG_CONTEST, 10, 47`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_BUG_CONTEST_TIMER` | `constants/engine_flags.asm:26` | set by the officer, cleared by `BugContestResultsScript` | "contest in progress" | -| `ENGINE_DAILY_BUG_CONTEST` | `constants/engine_flags.asm:98` | set by `BugContestResultsScript` | "already ran today" | -| `EVENT_LEFT_MONS_WITH_CONTEST_OFFICER` | `constants/event_flags.asm:439` | officer / results script | party is being held | -| `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` / `_NOT_CONTEST_DAY` / `_YOUNGSTER` | `constants/event_flags.asm:1255-1256` (+ youngster) | `MAPCALLBACK_OBJECTS` | which NPC is visible | -| `VAR_WEEKDAY` | `constants/script_constants.asm` (`; 0b`) | callback + officer | 0=Sunday .. 6=Saturday | -| `VAR_CONTESTMINUTES` | `constants/script_constants.asm:65` (`; 11`) | early-exit script | minutes left | - -**Items** - park balls only (see the Contest map). **Trainers** - none. - ---- - -### MAP_NATIONAL_PARK - -- Script: `maps/NationalPark.asm` -- Blocks: `maps/NationalPark.blk` (540 bytes = 20 x 27 blocks) -- Header (`data/maps/maps.asm:93`): `TILESET_PARK, ROUTE, LANDMARK_NATIONAL_PARK, MUSIC_NATIONAL_PARK, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:80` -> `map_const NATIONAL_PARK, 20, 27` (cells - 0..39 x, 0..53 y) -- Connections: none (`data/maps/attributes.asm:412` has no `connection` rows). Both exits - are warps. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 33 | 18 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 | -| 2 | 33 | 19 | `ROUTE_36_NATIONAL_PARK_GATE` | 2 | -| 3 | 10 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 | -| 4 | 11 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 14 | 44 | `BGEVENT_READ` | `NationalParkRelaxationSquareSign` | -| 27 | 31 | `BGEVENT_READ` | `NationalParkBattleNoticeSign` | -| 6 | 47 | `BGEVENT_ITEM` | `NationalParkHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` | -| 12 | 4 | `BGEVENT_READ` | `NationalParkTrainerTipsSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `NATIONALPARK_LASS1` | `SPRITE_LASS` | 15 | 24 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `NationalParkLassScript` | -1 | -| `NATIONALPARK_POKEFAN_F1` | `SPRITE_POKEFAN_F` | 14 | 4 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkPokefanFScript` | -1 | -| `NATIONALPARK_TEACHER1` | `SPRITE_TEACHER` | 27 | 40 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkTeacher1Script` (`43:4008`) | -1 | -| `NATIONALPARK_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 11 | 41 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `NationalParkYoungster1Script` | -1 | -| `NATIONALPARK_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 10 | 41 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkYoungster2Script` | -1 | -| `NATIONALPARK_TEACHER2` | `SPRITE_TEACHER` | 17 | 41 | `WANDER` (1,2) | `OBJECTTYPE_SCRIPT` | `NationalParkTeacher2Script` | -1 | -| `NATIONALPARK_PERSIAN` | `SPRITE_GROWLITHE` | 26 | 40 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `NationalParkPersian` | -1 | -| `NATIONALPARK_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 27 | 23 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerSchoolboyJack1` | -1 | -| `NATIONALPARK_POKEFAN_F2` | `SPRITE_POKEFAN_F` | 18 | 29 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPokefanfBeverly1` | -1 | -| `NATIONALPARK_POKEFAN_M` | `SPRITE_POKEFAN_M` | 16 | 9 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPokefanmWilliam` | -1 | -| `NATIONALPARK_LASS2` | `SPRITE_LASS` | 8 | 14 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerLassKrise` | -1 | -| `NATIONALPARK_POKE_BALL1` | `SPRITE_POKE_BALL` | 35 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkParlyzHeal` (`43:418f`) | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` | -| `NATIONALPARK_GAMEBOY_KID` | `SPRITE_GAMEBOY_KID` | 26 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkGameboyKidScript` | -1 | -| `NATIONALPARK_POKE_BALL2` | `SPRITE_POKE_BALL` | 1 | 43 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkTMDig` (`43:4191`) | `EVENT_NATIONAL_PARK_TM_DIG` | - -**Scripts of interest** - -- `NationalParkTeacher1Script` (`43:4008`) - the walkthrough's "lady on the bench". - `checkevent EVENT_GOT_QUICK_CLAW` / `iftrue .GotQuickClaw`; else text, - `verbosegiveitem QUICK_CLAW`, `iffalse .NoRoom`, `setevent EVENT_GOT_QUICK_CLAW`. - Unlike the flower shop teacher this one **does** guard the `setevent` with `iffalse`. -- `TrainerSchoolboyJack1` - phone trainer (`PHONE_SCHOOLBOY_JACK`). Rematch tiers: - `EVENT_CLEARED_RADIO_TOWER` -> `JACK3`, `ENGINE_FLYPOINT_OLIVINE` -> `JACK2`, else `JACK1`. -- `TrainerPokefanfBeverly1` - phone trainer (`PHONE_POKEFAN_BEVERLY`), female std scripts - (`AskNumber1FScript` etc). Rematch tiers: `EVENT_CLEARED_RADIO_TOWER` -> `BEVERLY3`, - `ENGINE_FLYPOINT_MAHOGANY` -> `BEVERLY2`, else `BEVERLY1`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_QUICK_CLAW` | `constants/event_flags.asm:96` | `NationalParkTeacher1Script` | one-shot gift | -| `EVENT_NATIONAL_PARK_PARLYZ_HEAL` | `constants/event_flags.asm:1017` | item ball | ball taken | -| `EVENT_NATIONAL_PARK_TM_DIG` | `constants/event_flags.asm:1018` | item ball | ball taken | -| `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:142` | `hiddenitem` | hidden item taken | -| `EVENT_BEAT_SCHOOLBOY_JACK`, `EVENT_BEAT_POKEFANF_BEVERLY`, `EVENT_BEAT_POKEFANM_WILLIAM`, `EVENT_BEAT_LASS_KRISE` | `constants/event_flags.asm` | `trainer` rows | defeated bits | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `QUICK_CLAW` | talk to teacher at (27, 40) | `NationalParkTeacher1Script` | `EVENT_GOT_QUICK_CLAW` | -| `PARLYZ_HEAL` | item ball at (35, 12) | `NationalParkParlyzHeal` | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` | -| `TM_DIG` (TM28) | item ball at (1, 43) | `NationalParkTMDig` | `EVENT_NATIONAL_PARK_TM_DIG` | -| `FULL_HEAL` | hidden, bg_event at (6, 47) | `NationalParkHiddenFullHeal` | `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `BEVERLY1` | `POKEFANF` (0x3e) | 1 | `parties.asm:3115` `BEVERLY` `TRAINERTYPE_ITEM` - lv14 SNUBBULL @ BERRY | `TrainerPokefanfBeverly1` | yes (`PHONE_POKEFAN_BEVERLY`); BEVERLY2 `parties.asm:3125`, BEVERLY3 `parties.asm:3130` | -| `JACK1` | `SCHOOLBOY` (0x17) | 1 | `parties.asm:409` `JACK` - lv12 ODDISH, lv15 VOLTORB | `TrainerSchoolboyJack1` | yes (`PHONE_SCHOOLBOY_JACK`); JACK2 `parties.asm:484`, JACK3 `parties.asm:490` | -| `KRISE` | `LASS` (0x19) | 4 | `parties.asm:658` `KRISE` - lv12 ODDISH, lv15 CUBONE | `TrainerLassKrise` | no | -| `WILLIAM` | `POKEFANM` (0x3b) | 1 | `parties.asm:2961` `WILLIAM` `TRAINERTYPE_ITEM` - lv14 RAICHU @ BERRY | `TrainerPokefanmWilliam` | no | - -**Wild encounters** - `data/wild/johto_grass.asm`, `def_grass_wildmons NATIONAL_PARK`, -rates 10/10/10 percent. Gold table: - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 10 CATERPIE | 10 CATERPIE | 10 HOOTHOOT | -| 2 | 10 METAPOD | 10 METAPOD | 10 HOOTHOOT | -| 3 | 12 CATERPIE | 11 SUNKERN | 12 HOOTHOOT | -| 4 | 12 PIDGEY | 12 PIDGEY | 12 HOOTHOOT | -| 5 | 10 PIDGEY | 13 SUNKERN | 10 HOOTHOOT | -| 6 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT | -| 7 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT | - -(Silver uses WEEDLE/KAKUNA in slots 1-3 morn. The nite block is shared by both versions.) -No `NATIONAL_PARK` entry exists in `data/wild/johto_water.asm`, `data/wild/treemon_maps.asm` -or `data/wild/swarm_grass.asm`. The rare bugs the walkthrough lists (Scyther, Pinsir, -Paras, Venonat, Butterfree, Beedrill) are **Contest-only**; see below. - ---- - -### MAP_NATIONAL_PARK_BUG_CONTEST - -- Script: `maps/NationalParkBugContest.asm` -- Blocks: none of its own; `data/maps/attributes.asm:413` gives - `map_attributes NationalParkBugContest, NATIONAL_PARK_BUG_CONTEST, $00` and the map is a - same-geometry twin of National Park. -- Header (`data/maps/maps.asm:94`): `TILESET_PARK, ROUTE, LANDMARK_NATIONAL_PARK, MUSIC_BUG_CATCHING_CONTEST, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:81` -> `map_const NATIONAL_PARK_BUG_CONTEST, 20, 27` -- Connections: none - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 33 | 18 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 | -| 2 | 33 | 19 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 | -| 3 | 10 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 | -| 4 | 11 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 | - -(Warps 2 and 4 point at dest warp 1, not 2 - that is what the file says.) - -**Coord events** - none. - -**BG events** - same four rows as National Park, aimed at the `...BugContest...` copies: -(14,44) Relaxation Square sign, (27,31) Battle Notice sign, (6,47) `BGEVENT_ITEM` -`hiddenitem FULL_HEAL, EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` (the same flag as the normal -map), (12,4) Trainer Tips sign. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._YOUNGSTER1` | `SPRITE_YOUNGSTER` | 19 | 29 | `WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant1AScript` | `EVENT_BUG_CATCHING_CONTESTANT_1A` | -| `..._YOUNGSTER2` | `SPRITE_YOUNGSTER` | 28 | 22 | `WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant2AScript` | `..._2A` | -| `..._ROCKER` | `SPRITE_ROCKER` | 9 | 18 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant3AScript` | `..._3A` | -| `..._POKEFAN_M` | `SPRITE_POKEFAN_M` | 7 | 13 | `WALK_UP_DOWN` (1,0) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant4AScript` | `..._4A` | -| `..._YOUNGSTER3` | `SPRITE_YOUNGSTER` | 23 | 9 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant5AScript` | `..._5A` | -| `..._YOUNGSTER4` | `SPRITE_YOUNGSTER` | 27 | 13 | `WANDER` (3,3) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant6AScript` | `..._6A` | -| `..._LASS` | `SPRITE_LASS` | 7 | 23 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant7AScript` | `..._7A` | -| `..._YOUNGSTER5` | `SPRITE_YOUNGSTER` | 11 | 27 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant8AScript` | `..._8A` | -| `..._YOUNGSTER6` | `SPRITE_YOUNGSTER` | 16 | 8 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant9AScript` | `..._9A` | -| `..._YOUNGSTER7` | `SPRITE_YOUNGSTER` | 17 | 34 | `WANDER` (3,3) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant10AScript` | `..._10A` | -| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 35 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkBugContestParlyzHeal` | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` | -| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 1 | 43 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkBugContestTMDig` | `EVENT_NATIONAL_PARK_TM_DIG` | - -**Scripts of interest** - -- Every `BugCatchingContestantNAScript` is `faceplayer / opentext / writetext / waitbutton - / closetext / end` - flavour only, no branching. The `B` variants in the north gate are - the ones that branch on `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` (set by - `BugContestResults_FirstPlace`) to say "you won". -- The battle path is `BugCatchingContestBattleScript::` - (`engine/events/bug_contest/contest.asm:9`): `loadvar VAR_BATTLETYPE, BATTLETYPE_CONTEST`, - `randomwildmon`, `startbattle`, `reloadmapafterbattle`, `readmem wParkBallsRemaining`, - `iffalse BugCatchingContestOutOfBallsScript`. -- `GiveParkBalls` (`engine/events/bug_contest/contest.asm:1`) clears `wContestMon`, writes - `BUG_CONTEST_BALLS` into `wParkBallsRemaining` and `farcall StartBugContestTimer`. - -**Contest constants** (`constants/script_constants.asm`) - -| constant | line | value | -|---|---|---| -| `BUG_CONTEST_BALLS` | 283 | 20 | -| `BUG_CONTEST_MINUTES` | 284 | 20 | -| `BUG_CONTEST_PLAYER` | 286 | 1 | -| `NUM_BUG_CONTESTANTS` | 287 | 10 (not counting the player) | -| `VAR_CONTESTMINUTES` | 65 | var id 0x11 | - -**Contest wild table** - `data/wild/bug_contest_mons.asm`, label `ContestMons` -(`25:7bb8`). This table replaces National Park's grass entirely for the duration: - -| % | species | min lv | max lv | -|---|---|---|---| -| 20 | CATERPIE | 7 | 18 | -| 20 | WEEDLE | 7 | 18 | -| 10 | METAPOD | 9 | 18 | -| 10 | KAKUNA | 9 | 18 | -| 5 | BUTTERFREE | 12 | 15 | -| 5 | BEEDRILL | 12 | 15 | -| 10 | VENONAT | 10 | 16 | -| 10 | PARAS | 10 | 17 | -| 5 | SCYTHER | 13 | 14 | -| 5 | PINSIR | 13 | 14 | -| -1 (terminator row) | VENOMOTH | 30 | 40 | - -**Scoring** - `ContestScore` (`04:7cbc`, `engine/events/bug_contest/judging.asm`) sums: -`MaxHP * 4` (high byte only), then Attack, Defense, Speed, SpclAtk, SpclDef, then a DV -bonus assembled from bit 1 of each DV nibble, then `remaining HP / 8`, then `+1` if the -mon is holding an item. Five of the ten AI contestants are rolled by -`ComputeAIContestantScores`, each picking one of three canned mon/score pairs from -`BugContestantPointers` (`data/events/bug_contest_winners.asm`) with a 0..7 random -perturbation. `DetermineContestWinners` keeps a 3-deep podium. - -**Prizes** - `BugContestResultsScript` (`40:420e`, `engine/events/std_scripts.asm:275`): - -| place | item | no-room fallback flag | -|---|---|---| -| 1st | `SUN_STONE` | `EVENT_CONTEST_OFFICER_HAS_SUN_STONE` | -| 2nd | `EVERSTONE` | `EVENT_CONTEST_OFFICER_HAS_EVERSTONE` | -| 3rd | `GOLD_BERRY` | `EVENT_CONTEST_OFFICER_HAS_GOLD_BERRY` | -| consolation | `BERRY` | `EVENT_CONTEST_OFFICER_HAS_BERRY` | - -First place also sets `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`. The tail of the script clears -`ENGINE_BUG_CONTEST_TIMER`, sets all twenty `EVENT_BUG_CATCHING_CONTESTANT_*` flags, sets -`ENGINE_DAILY_BUG_CONTEST`, and resets both gate scenes to their NOOP. - ---- - -### MAP_ROUTE_36_NATIONAL_PARK_GATE - -- Script: `maps/Route36NationalParkGate.asm` -- Blocks: `maps/Route36NationalParkGate.blk` -- Header (`data/maps/maps.asm:263`): `TILESET_GATE, INDOOR, LANDMARK_ROUTE_36, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:242` -> `map_const ROUTE_36_NATIONAL_PARK_GATE, 5, 4` -- Connections: none - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `NATIONAL_PARK` | 1 | -| 2 | 0 | 5 | `NATIONAL_PARK` | 2 | -| 3 | 9 | 4 | `ROUTE_36` | 1 | -| 4 | 9 | 5 | `ROUTE_36` | 2 | - -**Coord events** - none. Scene scripts (same three-slot shape as the south gate): -`SCENE_ROUTE36NATIONALPARKGATE_NOOP` (0), `..._UNUSED` (1), -`..._LEAVE_CONTEST_EARLY` (2) -> `sdefer Route36NationalParkGateLeavingContestEarlyScript`. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 6 | 0 | `BGEVENT_READ` | `BugCatchingContestExplanationSign` (the label defined in `maps/Route35NationalParkGate.asm`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE36NATIONALPARKGATE_OFFICER1` | `SPRITE_OFFICER` | 0 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route36OfficerScriptContest` (`56:67cc`) | `EVENT_ROUTE_36_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` | -| `..._YOUNGSTER1` | `SPRITE_YOUNGSTER` | 2 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant1BScript` | `EVENT_BUG_CATCHING_CONTESTANT_1B` | -| `..._YOUNGSTER2` | `SPRITE_YOUNGSTER` | 4 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant2BScript` | `..._2B` | -| `..._ROCKER` | `SPRITE_ROCKER` | 2 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant3BScript` | `..._3B` | -| `..._POKEFAN_M` | `SPRITE_POKEFAN_M` | 6 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant4BScript` | `..._4B` | -| `..._YOUNGSTER3` | `SPRITE_YOUNGSTER` | 2 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant5BScript` | `..._5B` | -| `..._YOUNGSTER4` | `SPRITE_YOUNGSTER` | 5 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant6BScript` | `..._6B` | -| `..._LASS` | `SPRITE_LASS` | 3 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant7BScript` | `..._7B` | -| `..._YOUNGSTER5` | `SPRITE_YOUNGSTER` | 4 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant8BScript` | `..._8B` | -| `..._YOUNGSTER6` | `SPRITE_YOUNGSTER` | 6 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant9BScript` | `..._9B` | -| `..._YOUNGSTER7` | `SPRITE_YOUNGSTER` | 6 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant10BScript` | `..._10B` | -| `ROUTE36NATIONALPARKGATE_OFFICER2` | `SPRITE_OFFICER` | 3 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route36NationalParkGateOfficerScript` | `EVENT_ROUTE_36_NATIONAL_PARK_GATE_OFFICER_NOT_CONTEST_DAY` | - -**Callbacks** - -- `MAPCALLBACK_NEWMAP` -> `Route36NationalParkGateCheckIfContestRunningCallback` - (same shape as the south gate). -- `MAPCALLBACK_OBJECTS` -> `Route36NationalParkGateCheckIfContestAvailableCallback` - (`56:6710`): first `checkevent EVENT_WARPED_FROM_ROUTE_35_NATIONAL_PARK_GATE` -> return - unchanged (this is how the awards ceremony keeps the contest officer standing there when - you finished from the *south* gate). Then weekday / `ENGINE_BUG_CONTEST_TIMER`, same as - the south gate. - -**Scripts of interest** - -- `Route36OfficerScriptContest` (`56:67cc`) - same structure as the south gate's officer - but warps to `NATIONAL_PARK_BUG_CONTEST, 33, 18` and, if `ENGINE_DAILY_BUG_CONTEST` is - already set, falls into `Route36Officer_ContestHasConcluded`, which is the **prize - holding desk**: it checks `EVENT_CONTEST_OFFICER_HAS_SUN_STONE`, then `_EVERSTONE`, then - `_GOLD_BERRY`, then `_BERRY`, `verbosegiveitem`s the first one it finds and clears that - event. A bot that won with a full PACK collects here. -- `Route36NationalParkGateLeavingContestEarlyScript` - the finish-now flow. - `special FadeOutToBlack`, `.CopyContestants` (turns each unset `..._NA` flag into an - `appear` of the matching `B` object), `disappear OFFICER1` / `appear OFFICER2`, - `applymovement PLAYER, Route36NationalParkGatePlayerWaitWithContestantsMovement` - (`big_step DOWN`, `big_step RIGHT`, `turn_head UP`), then - `jumpstd BugContestResultsScript`. - ---- - -### MAP_ROUTE_36 - -- Script: `maps/Route36.asm` -- Blocks: `maps/Route36.blk` (270 bytes = 30 x 9 blocks) -- Header (`data/maps/maps.asm:249`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_36, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:228` -> `map_const ROUTE_36, 30, 9` (cells - 0..59 x, 0..17 y) -- Connections (`data/maps/attributes.asm:206`): north `ROUTE_37` offset 10, south - `ROUTE_35` offset 0, east `VIOLET_CITY` offset 0 - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 18 | 8 | `ROUTE_36_NATIONAL_PARK_GATE` | 3 | -| 2 | 18 | 9 | `ROUTE_36_NATIONAL_PARK_GATE` | 4 | -| 3 | 47 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 1 | -| 4 | 48 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 2 | - -**Coord events** - none. (Sudowoodo is an object, not a trip-wire.) - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 29 | 1 | `BGEVENT_READ` | `Route36TrainerTips2` | -| 45 | 11 | `BGEVENT_READ` | `RuinsOfAlphNorthSign` | -| 55 | 7 | `BGEVENT_READ` | `Route36Sign` | -| 21 | 7 | `BGEVENT_READ` | `Route36TrainerTips1` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE36_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 20 | 12 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPsychicMark` | -1 | -| `ROUTE36_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 31 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` sight 5 | `TrainerSchoolboyAlan1` | -1 | -| `ROUTE36_WEIRD_TREE` | `SPRITE_WEIRD_TREE` | 35 | 9 | `SPRITEMOVEDATA_SUDOWOODO` | `OBJECTTYPE_SCRIPT` | `SudowoodoScript` (`4b:61aa`) | `EVENT_ROUTE_36_SUDOWOODO` | -| `ROUTE36_LASS1` | `SPRITE_LASS` | 51 | 8 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `Route36LassScript` (`4b:621e`) | -1 | -| `ROUTE36_FISHER` | `SPRITE_FISHER` | 44 | 9 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route36RockSmashGuyScript` (`4b:61f7`) | -1 | -| `ROUTE36_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 21 | 4 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route36FruitTree` (`4b:631a`) | -1 | -| `ROUTE36_ARTHUR` | `SPRITE_YOUNGSTER` | 46 | 6 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `ArthurScript` (`4b:62d4`) | `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` | - -**Callbacks** - -- `MAPCALLBACK_OBJECTS` -> `Route36ArthurCallback` (`4b:619e`): `readvar VAR_WEEKDAY`, - `ifequal THURSDAY, .ArthurAppears` (-> `appear ROUTE36_ARTHUR`), otherwise - `disappear ROUTE36_ARTHUR`. - -**Scripts of interest** - -- `SudowoodoScript` (`4b:61aa`) - the gate of this section. - `checkitem SQUIRTBOTTLE` / `iftrue .Fight`. Without it: `waitsfx`, - `playsound SFX_SANDSTORM`, `applymovement ROUTE36_WEIRD_TREE, SudowoodoShakeMovement` - (`tree_shake`), `end` - the tree just wobbles and stays solid. - With it: `writetext UseSquirtbottleText`, `yesorno`, `iffalse DidntUseSquirtbottleScript`. -- `WateredWeirdTreeScript::` (`4b:61c1`) - exported so that using the SQUIRTBOTTLE from the - PACK reaches the same body (`engine/events/squirtbottle.asm` does - `farsjump WateredWeirdTreeScript`). Body: text, shake, `SudowoodoAttackedText`, - **`loadwildmon SUDOWOODO, 20`**, `startbattle`, `setevent EVENT_FOUGHT_SUDOWOODO`, - `ifequal DRAW, DidntCatchSudowoodo`, `disappear ROUTE36_WEIRD_TREE`, - `variablesprite SPRITE_WEIRD_TREE, SPRITE_TWIN`, `reloadmapafterbattle`. - Note the ordering: `EVENT_FOUGHT_SUDOWOODO` is set **before** the DRAW check, so a run - or a KO still clears the road and still unlocks TM08. -- `DidntCatchSudowoodo` (`4b:61e9`) - `reloadmapafterbattle`, - `applymovement ROUTE36_WEIRD_TREE, WeirdTreeMovement_Flee` (two `fast_jump_step UP`), - `disappear`, `variablesprite`, `special LoadUsedSpritesGFX`. -- `_Squirtbottle` (`14:4763`, `engine/events/squirtbottle.asm`) - the PACK path. - `.CheckCanUseSquirtbottle` requires `wMapGroup == GROUP_ROUTE_36`, - `wMapNumber == MAP_ROUTE_36`, `GetFacingObject` to succeed, and that object's movement - data to be `SPRITEMOVEDATA_SUDOWOODO`. Anything else prints - `_SquirtbottleNothingText`. -- `Route36RockSmashGuyScript` (`4b:61f7`) - `checkevent EVENT_GOT_TM08_ROCK_SMASH` -> - `.AlreadyGotRockSmash`; `checkevent EVENT_FOUGHT_SUDOWOODO` -> `.ClearedSudowoodo` - (`verbosegiveitem TM_ROCK_SMASH`, `iffalse .NoRoomForTM`, - `setevent EVENT_GOT_TM08_ROCK_SMASH`). Before Sudowoodo it just prints - `RockSmashGuyText1`. -- `ArthurScript` (`4b:62d4`) - `checkevent EVENT_GOT_HARD_STONE_FROM_ARTHUR` -> - `.AlreadyGotStone`; `readvar VAR_WEEKDAY` / `ifnotequal THURSDAY, ArthurNotThursdayScript`; - else `setevent EVENT_MET_ARTHUR_OF_THURSDAY`, `verbosegiveitem HARD_STONE`, - `iffalse .BagFull`, `setevent EVENT_GOT_HARD_STONE_FROM_ARTHUR`. -- `Route36LassScript` (`4b:621e`) - flavour, branches on `EVENT_FOUGHT_SUDOWOODO`. This is - the girl at (51, 8). She is **not** Floria and gives nothing. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_36_SUDOWOODO` | `constants/event_flags.asm:1178` | object visibility flag; cleared by `disappear` in `WateredWeirdTreeScript` | tree present on the map | -| `EVENT_FOUGHT_SUDOWOODO` | `constants/event_flags.asm:51` | set by `WateredWeirdTreeScript`; read by the Rock Smash guy, the Route 36 lass, the Route 35 gate pokefan | **the** progress flag of this section | -| `EVENT_GOT_TM08_ROCK_SMASH` | `constants/event_flags.asm:84` | `Route36RockSmashGuyScript` | TM08 taken | -| `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` | `constants/event_flags.asm:1276` | `Route36ArthurCallback` | Arthur visible | -| `EVENT_MET_ARTHUR_OF_THURSDAY` | `constants/event_flags.asm:111` | `ArthurScript` | first-meeting text | -| `EVENT_GOT_HARD_STONE_FROM_ARTHUR` | `constants/event_flags.asm:112` | `ArthurScript` | HARD STONE taken | -| `EVENT_BEAT_PSYCHIC_MARK`, `EVENT_BEAT_SCHOOLBOY_ALAN` | `constants/event_flags.asm` | `trainer` rows | defeated bits | -| `EVENT_ALAN_ASKED_FOR_PHONE_NUMBER`, `EVENT_ALAN_READY_FOR_REMATCH` | `constants/event_flags.asm` | Alan script | phone state machine | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ICE_BERRY` | fruit tree object at (21, 4) | `FRUITTREE_ROUTE_36` -> `data/items/fruit_trees.asm:16` | daily | -| `TM_ROCK_SMASH` (TM08) | talk to the fisher at (44, 9) after Sudowoodo | `Route36RockSmashGuyScript` | `EVENT_GOT_TM08_ROCK_SMASH` | -| `HARD_STONE` | talk to Arthur at (46, 6) on a Thursday | `ArthurScript` | `EVENT_GOT_HARD_STONE_FROM_ARTHUR` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `MARK` | `PSYCHIC_T` (0x34) | 7 | `parties.asm:2545` `MARK` `TRAINERTYPE_MOVES` - lv13 ABRA (TELEPORT, FLASH), lv13 ABRA (TELEPORT, FLASH), lv15 KADABRA (TELEPORT, KINESIS, CONFUSION) | `TrainerPsychicMark` | no | -| `ALAN1` | `SCHOOLBOY` (0x17) | 3 | `parties.asm:423` `ALAN` - lv16 TANGELA | `TrainerSchoolboyAlan1` | yes (`PHONE_SCHOOLBOY_ALAN`); ALAN2 `parties.asm:496`, ALAN3 `parties.asm:502` | - -**Wild** - `data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_36`, 10/10/10 percent. -Gold: - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 12 NIDORAN_M | 12 NIDORAN_M | 12 NIDORAN_M | -| 2 | 12 NIDORAN_F | 12 NIDORAN_F | 12 NIDORAN_F | -| 3 | 13 PIDGEY | 14 PIDGEY | 13 HOOTHOOT | -| 4 | 13 GROWLITHE | 13 GROWLITHE | 13 GROWLITHE | -| 5 | 13 STANTLER | 13 STANTLER | 13 STANTLER | -| 6 | 15 PIDGEY | 15 GROWLITHE | 15 HOOTHOOT | -| 7 | 15 PIDGEY | 15 GROWLITHE | 15 HOOTHOOT | - -(Silver substitutes VULPIX for GROWLITHE and swaps the NIDORAN order.) No `ROUTE_36` -entry in `johto_water.asm`. Headbutt: `data/wild/treemon_maps.asm:17` -> `TREEMON_SET_FOREST`. -`ROUTE_36` is a roam node in `data/wild/roammon_maps.asm:26` with four exits -(`ROUTE_35, ROUTE_31, ROUTE_32, ROUTE_37`). - ---- - -### MAP_ROUTE_37 (header-listed only; not entered by this walkthrough section) - -- Script: `maps/Route37.asm`, Blocks: `maps/Route37.blk` -- Header (`data/maps/maps.asm:250`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_37, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:229` -> `map_const ROUTE_37, 10, 9` -- Connections (`data/maps/attributes.asm:211`): north `ECRUTEAK_CITY` offset -5, - south `ROUTE_36` offset -10 -- **Warps: `def_warp_events` is empty.** Route 37 is reached only by connection. -- BG events: (5,3) `BGEVENT_READ` `Route37Sign`; (4,2) `BGEVENT_ITEM` - `Route37HiddenEther` -> `hiddenitem ETHER, EVENT_ROUTE_37_HIDDEN_ETHER`. -- Objects: Twins Ann & Anne at (6,12) and (7,12) (`ANNANDANNE1` / `ANNANDANNE2`, both - sharing `EVENT_BEAT_TWINS_ANN_AND_ANNE`), Psychic Greg at (9,6), three fruit trees at - (13,5) `FRUITTREE_ROUTE_37_1` = `RED_APRICORN`, (16,5) `_2` = `BLU_APRICORN`, - (15,7) `_3` = `BLK_APRICORN` (`data/items/fruit_trees.asm:20-22`), and Sunny of Sunday - at (16,8) behind `EVENT_ROUTE_37_SUNNY_OF_SUNDAY` (gift `MAGNET`, - `EVENT_GOT_MAGNET_FROM_SUNNY`). -- Callback: `MAPCALLBACK_OBJECTS` -> `Route37SunnyCallback` (weekday == SUNDAY). -- Wild: `data/wild/johto_grass.asm` `def_grass_wildmons ROUTE_37`, 10/10/10; Gold nite - slot 1/5/6/7 are SPINARAK 13/15/15/15, Silver morn is LEDYBA - this is where the - walkthrough's "Ledyba (Silver only, morning only) / Spinarak (Gold only, night only)" - actually lives. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| SQUIRTBOTTLE not obtainable | `maps/GoldenrodFlowerShop.asm:FlowerShopTeacherScript` (`57:52d1`) - `checkflag ENGINE_PLAINBADGE` / `iffalse .Lalala` | Plain Badge (Whitney) | beat Whitney; talk to the teacher at (2,4) | -| Sudowoodo blocks Route 36 | `maps/Route36.asm:SudowoodoScript` (`4b:61aa`) - the object at cell (35,9) with `SPRITEMOVEDATA_SUDOWOODO` is a solid NPC; the script's `checkitem SQUIRTBOTTLE` / `iftrue .Fight` is the only way past | SQUIRTBOTTLE in the PACK | `EVENT_FOUGHT_SUDOWOODO` set by `WateredWeirdTreeScript` (`4b:61c1`); the `disappear` runs on any outcome except a draw-flee, and the flee path disappears it too | -| SQUIRTBOTTLE from the PACK does nothing elsewhere | `engine/events/squirtbottle.asm:_Squirtbottle` (`14:4763`) `.CheckCanUseSquirtbottle` | map must be `ROUTE_36` and the faced object must carry `SPRITEMOVEDATA_SUDOWOODO` | face the tree first | -| TM08 ROCK SMASH withheld | `maps/Route36.asm:Route36RockSmashGuyScript` (`4b:61f7`) - `checkevent EVENT_FOUGHT_SUDOWOODO` | Sudowoodo dealt with | talk to the fisher at (44, 9) | -| East side of Route 35 (Arnie, Bryan, TM04) | `maps/Route35.blk` block `$5b` at block (8,3) = cells x16-17 y6-7; `data/collision/field_move_blocks.asm:14` | CUT, which needs `ENGINE_HIVEBADGE` (`engine/events/overworld.asm:133` `CutFunction.CheckAble`) and a party mon that knows CUT | Azalea's badge + HM01; or enter from Route 36 via the south connection, which is what the walkthrough does | -| Bug Contest closed | `maps/Route35NationalParkGate.asm:Route35OfficerScriptContest` (`56:5e0c`) and `maps/Route36NationalParkGate.asm:Route36OfficerScriptContest` (`56:67cc`) - `readvar VAR_WEEKDAY`, four `ifequal` bail-outs, plus `checkflag ENGINE_DAILY_BUG_CONTEST` | Tuesday, Thursday or Saturday, and not already run today | set the RTC weekday; `ENGINE_DAILY_BUG_CONTEST` clears on the daily reset | -| Contest entry refused | same scripts - `readvar VAR_PARTYCOUNT` / `VAR_BOXSPACE` / `special CheckFirstMonIsEgg` / `special ContestDropOffMons` | lead mon is not an EGG, is not fainted, and there is party-or-box room for a caught bug | reorder the party, heal, free a box slot | -| Kenya not given | `maps/Route35GoldenrodGate.asm:RandyScript` (`56:59ce`) - `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .partyfull` | fewer than 6 party mons | deposit one | -| Arthur / Sunny absent | `Route36ArthurCallback` (`4b:619e`) / `Route37SunnyCallback` - `MAPCALLBACK_OBJECTS` + `VAR_WEEKDAY` | Thursday / Sunday | RTC weekday | -| Officer Dirk will not battle | `maps/Route35.asm:TrainerOfficerDirk` - `checktime NITE` / `iffalse .NotNight` | night time of day | wait, or set the RTC | - ---- - -## 4. Bot checklist - -Coordinates are `(x, y)` walk cells as written in the asm. "talk" means stand adjacent -facing the target and press A. - -1. `MAP_GOLDENROD_CITY` -> warp 6 at (33, 5). Pre: `ENGINE_PLAINBADGE` set. Post: on - `MAP_GOLDENROD_FLOWER_SHOP`. -2. `MAP_GOLDENROD_FLOWER_SHOP`: talk to `GOLDENRODFLOWERSHOP_TEACHER` at (2, 4). - Pre: `!EVENT_GOT_SQUIRTBOTTLE` and a free KEY ITEM slot. Post: - `EVENT_GOT_SQUIRTBOTTLE`, SQUIRTBOTTLE in the PACK. -3. Exit via warp 1 (2, 7) -> `MAP_GOLDENROD_CITY` warp 6. Walk to (19, 1), warp 13 -> - `MAP_ROUTE_35_GOLDENROD_GATE` warp 3. -4. Optional: talk to `ROUTE35GOLDENRODGATE_RANDY` at (0, 4), answer YES. - Pre: `VAR_PARTYCOUNT < 6`. Post: `EVENT_GOT_KENYA`, lv10 SPEAROW "KENYA" with - `FLOWER_MAIL` in the last party slot. -5. Warp 1 at (4, 0) -> `MAP_ROUTE_35` warp 1 (lands at (9, 33)). -6. `MAP_ROUTE_35`, walk north. Battle, in the walkthrough's order: - Kim (11, 24), Elliot (8, 20), Brooke (7, 20), Ivan (3, 19), Irwin (5, 10), - Walt (2, 10). Post: `EVENT_BEAT_*` for each. Sight ranges are 3/3/3/4/2/2. -7. Optional, night only: talk to `ROUTE35_OFFICER` at (5, 6). Pre: `checktime NITE` true - and `!EVENT_BEAT_OFFICER_DIRK`. Post: `EVENT_BEAT_OFFICER_DIRK`. -8. Walk to (3, 5), warp 3 -> `MAP_ROUTE_35_NATIONAL_PARK_GATE` warp 3. -9. Optional Contest: on a Tue/Thu/Sat with `!ENGINE_DAILY_BUG_CONTEST`, talk to - `ROUTE35NATIONALPARKGATE_OFFICER1` at (2, 1) and answer YES. Post: - `ENGINE_BUG_CONTEST_TIMER` set, 20 PARK BALLs, warp to - `MAP_NATIONAL_PARK_BUG_CONTEST` (10, 47). Finish by re-entering either gate and - answering YES, or by running the timer/balls out. Post: `ENGINE_DAILY_BUG_CONTEST`, - prize per placing, `ENGINE_BUG_CONTEST_TIMER` cleared. -10. Warp 1 at (3, 0) -> `MAP_NATIONAL_PARK` warp 3 (lands at (10, 47)). -11. `MAP_NATIONAL_PARK`: talk to `NATIONALPARK_TEACHER1` at (27, 40). - Pre: `!EVENT_GOT_QUICK_CLAW`, PACK room. Post: `EVENT_GOT_QUICK_CLAW`. -12. Optional: item ball at (1, 43) -> `TM_DIG`, flag `EVENT_NATIONAL_PARK_TM_DIG`. - Hidden `FULL_HEAL` at bg (6, 47), flag `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL`. - Item ball at (35, 12) -> `PARLYZ_HEAL`, flag `EVENT_NATIONAL_PARK_PARLYZ_HEAL`. -13. Battle Beverly (18, 29), Jack (27, 23), Krise (8, 14), William (16, 9). -14. Walk to (33, 18), warp 1 -> `MAP_ROUTE_36_NATIONAL_PARK_GATE` warp 1. - If a prize was held: talk to `ROUTE36NATIONALPARKGATE_OFFICER1` at (0, 3) with - `ENGINE_DAILY_BUG_CONTEST` set to collect it. -15. Warp 3 at (9, 4) -> `MAP_ROUTE_36` warp 1 (lands at (18, 8)). -16. `MAP_ROUTE_36`: fruit tree at (21, 4) -> `ICE_BERRY`. Battle Psychic Mark at (20, 12). -17. Leave south (`ROUTE_36` south connection, offset 0) into `MAP_ROUTE_35`'s north edge. -18. `MAP_ROUTE_35` east side: CUT the tree at block (8, 3) (cells x16-17, y6-7). - Pre: `ENGINE_HIVEBADGE` + a mon knowing CUT. Then battle Arnie at (16, 7) and - Bryan at (14, 28), and take the item ball at (13, 16) -> `TM_ROLLOUT` - (`EVENT_ROUTE_35_TM_ROLLOUT`). Talk to Arnie a second time for - `PHONE_BUG_CATCHER_ARNIE`. -19. Return north to `MAP_ROUTE_36`. Battle Schoolboy Alan at (31, 14); talk again for - `PHONE_SCHOOLBOY_ALAN`. -20. Walk to face `ROUTE36_WEIRD_TREE` at (35, 9) and press A. Pre: SQUIRTBOTTLE in the - PACK (`checkitem`). Answer YES. A lv20 SUDOWOODO wild battle starts - (`loadwildmon SUDOWOODO, 20`). Post: `EVENT_FOUGHT_SUDOWOODO` set regardless of - outcome; `EVENT_ROUTE_36_SUDOWOODO` cleared, sprite swapped to `SPRITE_TWIN`. -21. Talk to `ROUTE36_FISHER` at (44, 9). Pre: `EVENT_FOUGHT_SUDOWOODO`, PACK room. - Post: `EVENT_GOT_TM08_ROCK_SMASH`, TM08 ROCK SMASH. -22. Optional, Thursday only: talk to `ROUTE36_ARTHUR` at (46, 6). Post: - `EVENT_MET_ARTHUR_OF_THURSDAY`, `EVENT_GOT_HARD_STONE_FROM_ARTHUR`, HARD STONE. -23. Continue east past x=59 on the `ROUTE_36` east connection into `MAP_VIOLET_CITY` - (next section). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map geometry, warps, bg/object events for all nine maps | `src/import/RomExtractorGen2.lua` (extracted into the cache), consumed by `src/world/gen2/World.lua` / `Map.lua` | implemented (data-driven; nothing map-specific is hand written) | -| Script bytecode for every label above (`FlowerShopTeacherScript`, `RandyScript`, `SudowoodoScript`, `Route36RockSmashGuyScript`, `ArthurScript`, both contest officers, `BugContestResultsScript`) | `src/script/gen2/Vm.lua` + extracted bytecode | implemented - the opcodes these scripts use (`checkevent`, `checkflag`, `checkitem`, `verbosegiveitem`, `givepoke`, `givepokemail`, `loadwildmon`, `variablesprite`, `appear`/`disappear`, `setscene`, `sdefer`, `askforphonenumber`, `checkcellnum`, `checktime`, `fruittree`) all have branches in `Vm.lua` / `Opcodes.lua` | -| `VAR_WEEKDAY` (Arthur, Sunny, contest days) | `src/world/gen2/World.lua:1201` `World:weekday`, `World.lua:101` `VAR_WEEKDAY = 0x0b` | implemented | -| `MAPCALLBACK_OBJECTS` day-of-week NPC swaps | `src/world/gen2/World.lua:5991` `World:runMapCallback`, called from `World.lua:5700` | implemented; assertion driver `tests/drivers/gold_map_callbacks.lua` exists but does **not** cover Route 36 / Route 37 / either park gate | -| Bug Catching Contest rules, timer, park balls, scoring, AI contestants, prizes | `src/core/gen2/BugContest.lua` (815 lines; transcribed from `bug_contest/judging.asm`, `contest.asm`, `contest_2.asm`, `caught_mon.asm`, `bug_contest_mons.asm`) | implemented - `BugContest.score`, `rollContestant`, `judge`, `prizeFor`, `pickContestants`, `dropOffMons`, `returnMons`, `start/stop/tickTimer`, `chooseWild` | -| Contest comparison / debug UI | `src/ui/gen2/ContestMenu.lua` | implemented (a comparison screen, not the cart's HUD) | -| Contest super-tall-grass doubled encounter rate | `src/world/gen2/Permissions.lua:85` `SUPER_TALL_GRASS`, `BugContest.encounterRate` | implemented | -| Squirtbottle field check | `src/script/gen2/CallAsm.lua:411` `H.CheckCanUseSquirtbottle` (registered as `"14:4786"`), tested in `tests/gen2_callasm_test.lua:224-244` | implemented | -| **Using the SQUIRTBOTTLE from the PACK** | `src/world/gen2/World.lua:3294` `World:useFieldItem` | **missing** - the dispatch has arms only for `ITEMFINDER`, `BICYCLE`, `SACRED_ASH`, repels, trophy boxes and rods. There is no `SQUIRTBOTTLE` arm, so `_Squirtbottle` is unreachable from the PACK. The talk-to-the-tree path (`SudowoodoScript`'s `checkitem`) still works, which is the route the walkthrough takes | -| **Item ball pickup** (TM04 on Route 35, TM28 DIG and PARLYZ HEAL in National Park) | extractor reads them (`src/import/RomExtractorGen2.lua:2969` `obj.itemball = readItemBall(...)`) but `src/world/gen2/World.lua:5257` `World:interact` only dispatches on `npc.def.trainer`, strength boulders, `npc.def.scriptKey`, bg events and hidden items | **missing** - `OBJECTTYPE_ITEMBALL` objects have no `scriptKey` by design, and nothing consumes `obj.itemball`. A bot cannot pick up any of this section's three item balls | -| Hidden item (National Park FULL HEAL, Route 37 ETHER) | `src/world/gen2/HiddenItems.lua`, wired into `World:interact` | implemented | -| Fruit trees (MYSTERYBERRY, ICE BERRY, the Route 37 apricorns) | `src/core/gen2/Apricorns.lua` + the VM's `fruittree` branch | implemented | -| Overworld trainers, sight ranges, `loadtrainer` / `startbattle` | `src/world/gen2/Trainers.lua`, `src/battle/gen2/Battle.lua` | implemented | -| Phone numbers and rematch tiers (Irwin, Arnie, Jack, Beverly, Alan) | `src/core/gen2/Phone.lua` (Jack, Beverly, Irwin, Arnie, Alan all have callee/caller script keys and `..._READY_FOR_REMATCH` event ids) | implemented | -| Kenya the SPEAROW + FLOWER MAIL | `src/core/gen2/Mail.lua` + `Vm.lua:439` `givepoke` / `Vm.lua:462` `givepokemail` | implemented (extractor resolves `cmd.mail`) | -| CUT field move + `ENGINE_HIVEBADGE` gate + the Route 35 tree block | `src/world/gen2/FieldMoves.lua:104` (`CUT = "HIVE"`), `FieldMoves.somethingToCut`, `World:tryCutOW` | implemented | -| Sudowoodo wild battle + DRAW-flee branch | `src/script/gen2/Vm.lua:44` (DRAW), `Vm.lua:836` `loadwildmon`, `World.lua:5067` (`variablesprite` with a raw sprite id) | implemented | -| Driver / regression coverage for this specific stretch | `tests/drivers/gold_*.lua` | **missing** - there is no driver that walks Route 35 -> National Park -> Route 36, runs a contest, or fights Sudowoodo. `tests/gen2_contest_test.lua` covers the contest rules headlessly; `tests/gen2_callasm_test.lua` covers only the Squirtbottle facing check | - ---- - -## 6. Unresolved / verify by hand - -1. **"Talk to Floria... while she goes away to get the Squirtbottle."** No such script - exists in pokegold. `maps/GoldenrodFlowerShop.asm` has a `GOLDENRODFLOWERSHOP_FLORIA` - object at (5, 6) whose `FlowerShopFloriaScript` only prints text, and the girl on - Route 36 at (51, 8) is an anonymous `ROUTE36_LASS1` running `Route36LassScript`, which - also only prints text. The SQUIRTBOTTLE comes from `FlowerShopTeacherScript` gated on - `ENGINE_PLAINBADGE` alone, with no Route 36 precondition. The described sequence looks - like Crystal behaviour that leaked into a Gold/Silver walkthrough. -2. **Contest Pokemon list.** The walkthrough lists "Caterpie/Weedle, Nincada, Scyther, - Pinsir, Volbeat, Wurmple" at "Levels 28-31". `data/wild/bug_contest_mons.asm` has - CATERPIE, WEEDLE, METAPOD, KAKUNA, BUTTERFREE, BEEDRILL, VENONAT, PARAS, SCYTHER, - PINSIR at levels 7-18 (plus VENOMOTH 30-40 on the `-1` terminator row, which is never - selected). NINCADA, VOLBEAT and WURMPLE do not exist in Gen 2 at all. Treat the - walkthrough's list and levels as wrong for this game. -3. **"20 Sport Balls."** The item is `PARK_BALL` (`constants/item_constants.asm:185`), - given by `special GiveParkBalls`, count `BUG_CONTEST_BALLS = 20`. "Sport Ball" is the - Gen 3+ name. -4. **"If you win the contest, you can win a Shiny Stone!"** First place gives `SUN_STONE` - (`BugContestResults_FirstPlace`, `engine/events/std_scripts.asm:346`). The - walkthrough's own item list ("Sun Stone (1st Prize)") agrees; the prose line does not. - There is no Shiny Stone in Gen 2. -5. **"If you catch a Scyther at full HP, it may be worth around 342 points."** Not - verifiable from the asm without a concrete mon: `ContestScore` (`04:7cbc`) is - `MaxHP*4 + Atk + Def + Spe + SpA + SpD + DVbonus + HP/8 + heldItem`, all taken from the - high byte of each stat word. The walkthrough's qualitative claim ("score takes HP into - consideration") is confirmed; the specific number is not checked here. -6. **National Park species list.** The walkthrough lists Butterfree, Weedle, Kakuna, - Beedrill, Paras, Venonat, Scyther and Pinsir as "found in National Park". None of them - appear in `def_grass_wildmons NATIONAL_PARK`; they are Contest-only - (`data/wild/bug_contest_mons.asm`). Sunkern, Caterpie, Metapod, Pidgey and Hoothoot are - the only real National Park grass encounters in Gold. -7. **"Routes 36 & 37" item and species lists are merged.** Everything the walkthrough - attributes to "Routes 36 & 37" except the ICE BERRY, HARD STONE and TM08 actually lives - on Route 37 (`RED_APRICORN`, `BLU_APRICORN`, `BLK_APRICORN`; Ledyba/Spinarak/Pidgeotto), - which this section never enters. Vulpix/Growlithe and Stantler appear on both routes. -8. **"Route 35 & National Park" item list omits the hidden FULL HEAL** at National Park - bg (6, 47) and lists no hidden items at all. Not a contradiction, just incomplete. -9. **"Head back to Arnie and talk to *her*."** `BUG_CATCHER, ARNIE1` uses the male phone - std scripts (`AskNumber1MScript`, `RegisteredNumberMScript`). Cosmetic only. -10. **"Then, we're ready to head into Route 35 after Liz's offer for a rematch on Route - 32."** Liz's rematch is a phone event outside this section's maps; no code on any map - in this section references it, so it was not resolved here. -11. **`EVENT_FOUGHT_SUDOWOODO` is set before the DRAW check** in - `WateredWeirdTreeScript`, and both the win branch and `DidntCatchSudowoodo` run - `disappear ROUTE36_WEIRD_TREE`. So the walkthrough's "catch it or else" is a - preference, not a gate - fainting or fleeing still opens the road and still unlocks - TM08. Worth confirming in-game that a *black-out* mid-battle does not leave a - half-applied state, which the asm alone cannot answer. -12. **`MAP_NATIONAL_PARK_BUG_CONTEST` warps 2 and 4 point at destination warp 1**, not 2, - in `maps/NationalParkBugContest.asm:228,230`. Transcribed verbatim; a bot pathing off - those rows should expect the asymmetry rather than assume a typo. diff --git a/docs/gold-walkthrough/asm-walk/section-07-burned-tower-and-ecruteak-city-gym.md b/docs/gold-walkthrough/asm-walk/section-07-burned-tower-and-ecruteak-city-gym.md deleted file mode 100644 index 814e06f1..00000000 --- a/docs/gold-walkthrough/asm-walk/section-07-burned-tower-and-ecruteak-city-gym.md +++ /dev/null @@ -1,1227 +0,0 @@ -# Section 07 - Burned Tower and Ecruteak City Gym - -Source: `../section-07-burned-tower-and-ecruteak-city-gym.txt` -Maps covered: `ROUTE_37`, `ECRUTEAK_CITY`, `ECRUTEAK_POKECENTER_1F`, `DANCE_THEATER`, -`ECRUTEAK_ITEMFINDER_HOUSE`, `BILLS_FAMILYS_HOUSE`, `BURNED_TOWER_1F`, -`BURNED_TOWER_B1F`, `ECRUTEAK_GYM` (plus one line on `ECRUTEAK_TIN_TOWER_ENTRANCE`, -which this section only unlocks). -Badges / key milestones in this section: FOGBADGE (`ENGINE_FOGBADGE`), HM03 SURF, -ITEMFINDER, EEVEE, TM30 SHADOW BALL, TIME CAPSULE (`ENGINE_TIME_CAPSULE`), -Ecruteak fly point, the three roaming beasts (`EVENT_RELEASED_THE_BEASTS`). - -Note on naming: pokegold's `map_const` rows carry no `MAP_` prefix, so the -constants below are written exactly as `constants/map_constants.asm` spells them. - -Note on coordinates: every table below is transcribed verbatim from the map asm. -`warp_event` / `coord_event` / `bg_event` / `object_event` all take `x, y` as -their first two arguments (see the macro comments in `macros/scripts/maps.asm`), -even though the macros emit `y` first. Coordinates are 16x16 walk cells, origin -top-left, so a `map_const W, H` map spans `x = 0 .. 2W-1`, `y = 0 .. 2H-1`. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `VIOLET_CITY` | `maps/VioletCity.asm` | (carried in from section 06) | south to `ROUTE_36` | Heal after Sudowoodo; swap Togepi for Sudowoodo. Belongs to section 06. | -| 1 | `ROUTE_36` | `maps/Route36.asm` | west from Violet City | north connection to `ROUTE_37` | Pass-through only in this section; Route 36's own items/trainers are section 06's. | -| 2 | `ROUTE_37` | `maps/Route37.asm` | south connection from `ROUTE_36` | north connection to `ECRUTEAK_CITY` | Twins Ann & Anne, Psychic Greg, three apricorn trees, Sunny (Sundays). | -| 3 | `ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | south connection from `ROUTE_37` | warp 6 (Pokecenter) | First arrival; sets `ENGINE_FLYPOINT_ECRUTEAK`. | -| 4 | `ECRUTEAK_POKECENTER_1F` | `maps/EcruteakPokecenter1F.asm` | Ecruteak warp 6 at (23,27) | same warp | Bill cutscene, Time Capsule, heal, deposit apricorns/berries. | -| 5 | `ROUTE_37` -> `ROUTE_36` -> `NATIONAL_PARK` -> `ROUTE_35` -> `GOLDENROD_CITY` | - | biking back south | - | Backtrack for the apricorn trees and Bill's Eevee. Only Route 37 is owned here. | -| 6 | `BILLS_FAMILYS_HOUSE` | `maps/BillsFamilysHouse.asm` | Goldenrod warp 4 at (5,25) | same warp | `givepoke EEVEE, 20`. | -| 7 | `ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | back north via Route 35/36/37 | warp 8 (Dance Theater) | Return with Eevee. | -| 8 | `DANCE_THEATER` | `maps/DanceTheater.asm` | Ecruteak warp 8 at (23,21) | warps 1/2 at (5,13)/(6,13) | Five Kimono Girls, then HM03 SURF from the gentleman. | -| 9 | `ECRUTEAK_ITEMFINDER_HOUSE` | `maps/EcruteakItemfinderHouse.asm` | Ecruteak warp 11 at (13,27) | warps 1/2 at (3,7)/(4,7) | ITEMFINDER for answering `yesorno` with YES. | -| 10 | `BURNED_TOWER_1F` | `maps/BurnedTower1F.asm` | Ecruteak warp 13 at (5,5) | warps 1/2 at (9,15)/(10,15) | Rival battle on entry; Rock Smash gates; drop to B1F. | -| 11 | `BURNED_TOWER_B1F` | `maps/BurnedTowerB1F.asm` | fall through a 1F pit | ladder at (7,15) | HP UP, TM ENDURE, and the `ReleaseTheBeasts` coord event. | -| 12 | `ECRUTEAK_GYM` | `maps/EcruteakGym.asm` | Ecruteak warp 10 at (6,27) | warps 1/2 at (4,17)/(5,17) | Four ghost trainers on the invisible floor, then Morty. | - -Spill into the next section: after FOGBADGE the walkthrough points at Olivine -Lighthouse and at Union Cave for Lapras. `EcruteakGymMortyScript` also does -`setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP`, -which is what un-blocks Tin Tower - that map is the next section's problem. - ---- - -## 2. Maps - -### ROUTE_37 - -- Script: `maps/Route37.asm` -- Blocks: `maps/Route37.blk` -- Header: `data/maps/maps.asm:250` -> `TILESET_JOHTO`, `ROUTE`, `LANDMARK_ROUTE_37`, - `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:229` -> `map_const ROUTE_37, 10, 9` - (walk cells `x 0..19`, `y 0..17`) -- Connections: `data/maps/attributes.asm:211` -> north `EcruteakCity` (offset -5), - south `Route36` (offset -10). No east/west. - -**Warps** (`def_warp_events`) - -Empty. Route 37 is reached only through map connections. - -**Coord events** (`def_coord_events`) - -Empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 3 | `BGEVENT_READ` | `Route37Sign` | -| 4 | 2 | `BGEVENT_ITEM` | `Route37HiddenEther` -> `hiddenitem ETHER, EVENT_ROUTE_37_HIDDEN_ETHER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE37_WEIRD_TREE1` | `SPRITE_WEIRD_TREE` | 6 | 12 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerTwinsAnnandanne1` | -1 | -| `ROUTE37_WEIRD_TREE2` | `SPRITE_WEIRD_TREE` | 7 | 12 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerTwinsAnnandanne2` | -1 | -| `ROUTE37_YOUNGSTER` | `SPRITE_YOUNGSTER` | 9 | 6 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPsychicGreg` | -1 | -| `ROUTE37_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 13 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree1` | -1 | -| `ROUTE37_SUNNY` | `SPRITE_BUG_CATCHER` | 16 | 8 | `WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `SunnyScript` | `EVENT_ROUTE_37_SUNNY_OF_SUNDAY` | -| `ROUTE37_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 16 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree2` | -1 | -| `ROUTE37_FRUIT_TREE3` | `SPRITE_FRUIT_TREE` | 15 | 7 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree3` | -1 | - -The two twin objects really are declared `SPRITE_WEIRD_TREE` with object consts -named `ROUTE37_WEIRD_TREE1/2`; `engine/events/std_scripts.asm` carries -`variablesprite SPRITE_WEIRD_TREE, SPRITE_SUDOWOODO`, so the sprite slot is -shared with Sudowoodo. Flagged under section 6 as well - see "Unresolved". - -**Scripts of interest** - -- `Route37SunnyCallback` (`MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY`, - `ifequal SUNDAY` -> `appear ROUTE37_SUNNY`, otherwise `disappear ROUTE37_SUNNY`. - This is the only reason Sunny exists on the map, so a bot must fake or wait - for Sunday. -- `SunnyScript` (`4c:404d`): `checkevent EVENT_GOT_MAGNET_FROM_SUNNY` -> already - done; else `readvar VAR_WEEKDAY`, `ifnotequal SUNDAY` -> refusal text; else - `setevent EVENT_MET_SUNNY_OF_SUNDAY`, `verbosegiveitem MAGNET`, - `setevent EVENT_GOT_MAGNET_FROM_SUNNY`. -- `TrainerTwinsAnnandanne1` / `TrainerTwinsAnnandanne2`: both use the *same* - `EVENT_BEAT_TWINS_ANN_AND_ANNE` flag, so beating either twin marks both beaten - and the second never battles. `EVENT_BEAT_TWINS_ANN_AND_ANNE2` exists in - `constants/event_flags.asm:610` but nothing on this map references it. -- `Route37FruitTree1/2/3` -> `fruittree FRUITTREE_ROUTE_37_1/2/3`. - `data/items/fruit_trees.asm:20-22` gives `RED_APRICORN`, `BLU_APRICORN`, - `BLK_APRICORN` in that order. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_37_SUNNY_OF_SUNDAY` | `constants/event_flags.asm:1277` | `Route37SunnyCallback` (`appear`/`disappear`) | Set = Sunny hidden. Object masks when the flag is SET (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32`). | -| `EVENT_MET_SUNNY_OF_SUNDAY` | `constants/event_flags.asm:113` | `SunnyScript` | First-meeting text guard. | -| `EVENT_GOT_MAGNET_FROM_SUNNY` | `constants/event_flags.asm:114` | `SunnyScript` | One-time MAGNET. | -| `EVENT_ROUTE_37_HIDDEN_ETHER` | `constants/event_flags.asm:179` | `Route37HiddenEther` | Hidden ETHER at (4,2). | -| `EVENT_BEAT_TWINS_ANN_AND_ANNE` | `constants/event_flags.asm:609` | both twin `trainer` rows | One flag, two objects. | -| `EVENT_BEAT_PSYCHIC_GREG` | `constants/event_flags.asm:567` | `TrainerPsychicGreg` | - | -| `ENGINE_BIKE_SHOP_CALL_ENABLED` | `constants/engine_flags.asm:29` | set by `maps/GoldenrodBikeShop.asm:28`, cleared by `engine/phone/scripts/bike_shop.asm:3` | The "keep the bicycle" call the walkthrough hits here. | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `RED_APRICORN` | fruit tree (daily) | object (13,5) -> `Route37FruitTree1` -> `FRUITTREE_ROUTE_37_1` | none (daily reset) | -| `BLU_APRICORN` | fruit tree (daily) | object (16,5) -> `Route37FruitTree2` -> `FRUITTREE_ROUTE_37_2` | none | -| `BLK_APRICORN` | fruit tree (daily) | object (15,7) -> `Route37FruitTree3` -> `FRUITTREE_ROUTE_37_3` | none | -| `ETHER` | hidden | `bg_event 4, 2, BGEVENT_ITEM` | `EVENT_ROUTE_37_HIDDEN_ETHER` | -| `MAGNET` | Sunny, Sundays only | `SunnyScript` `verbosegiveitem MAGNET` | `EVENT_GOT_MAGNET_FROM_SUNNY` | - -The walkthrough's "Hard Stone / Ice Berry / TM08 Rock Smash" for "Route 36 & 37" -are all on `ROUTE_36`, not Route 37. That map belongs to section 06. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `TWINS` / `ANNANDANNE1` | `TWINS` | `ANNANDANNE1` (`TwinsGroup` entry 2) | L16 CLEFAIRY (GROWL, ENCORE, DOUBLESLAP, METRONOME); L16 JIGGLYPUFF (SING, DEFENSE_CURL, POUND, DISABLE) | `TrainerTwinsAnnandanne1` | none | -| `TWINS` / `ANNANDANNE2` | `TWINS` | `ANNANDANNE2` (`TwinsGroup` entry 3) | L16 JIGGLYPUFF; L16 CLEFAIRY (same moves, reversed order) | `TrainerTwinsAnnandanne2` | none | -| `PSYCHIC_T` / `GREG` | `PSYCHIC_T` | `GREG` (`PsychicGroup` entry 5) | L17 DROWZEE (HYPNOSIS, DISABLE, DREAM_EATER) | `TrainerPsychicGreg` | none | - -**Wild encounters** - -`data/wild/johto_grass.asm:1959`, `def_grass_wildmons ROUTE_37`, encounter rate -10% morn / 10% day / 10% nite. - -Gold (`IF DEF(_GOLD)`): - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L13 PIDGEY | L13 PIDGEY | L13 SPINARAK | -| 2 | L15 STANTLER | L15 STANTLER | L15 STANTLER | -| 3 | L15 PIDGEY | L15 PIDGEY | L15 HOOTHOOT | -| 4 | L14 GROWLITHE | L14 GROWLITHE | L14 GROWLITHE | -| 5 | L15 PIDGEY | L15 PIDGEOTTO | L15 SPINARAK | -| 6 | L15 PIDGEY | L16 GROWLITHE | L15 SPINARAK | -| 7 | L15 PIDGEY | L16 GROWLITHE | L15 SPINARAK | - -Silver (`ELIF DEF(_SILVER)`) swaps GROWLITHE -> VULPIX and SPINARAK -> LEDYBA -(morn) / HOOTHOOT (nite); slot 1 morn is L13 LEDYBA, slot 1 nite L13 HOOTHOOT. - -Headbutt: `data/wild/treemon_maps.asm:18` -> `treemon_map ROUTE_37, TREEMON_SET_FOREST`. -Roaming beasts: `data/wild/roammon_maps.asm:27` -> `roam_map ROUTE_37, ROUTE_36, ROUTE_38, ROUTE_42`. -No water, no fishing. - ---- - -### ECRUTEAK_CITY - -- Script: `maps/EcruteakCity.asm` -- Blocks: `maps/EcruteakCity.blk` -- Header: `data/maps/maps.asm:173` -> `TILESET_JOHTO`, `TOWN`, `LANDMARK_ECRUTEAK_CITY`, - `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:159` -> `map_const ECRUTEAK_CITY, 20, 18` - (walk cells `x 0..39`, `y 0..35`) -- Connections: `data/maps/attributes.asm:147` -> south `Route37` (offset 5), - west `Route38` (offset 5), east `Route42` (offset 9) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 35 | 26 | `ROUTE_42_ECRUTEAK_GATE` | 1 | -| 2 | 35 | 27 | `ROUTE_42_ECRUTEAK_GATE` | 2 | -| 3 | 18 | 11 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 1 | -| 4 | 20 | 2 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 1 | -| 5 | 20 | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 2 | -| 6 | 23 | 27 | `ECRUTEAK_POKECENTER_1F` | 1 | -| 7 | 5 | 21 | `ECRUTEAK_LUGIA_SPEECH_HOUSE` | 1 | -| 8 | 23 | 21 | `DANCE_THEATER` | 1 | -| 9 | 29 | 21 | `ECRUTEAK_MART` | 2 | -| 10 | 6 | 27 | `ECRUTEAK_GYM` | 1 | -| 11 | 13 | 27 | `ECRUTEAK_ITEMFINDER_HOUSE` | 1 | -| 12 | 37 | 7 | `TIN_TOWER_1F` | 1 | -| 13 | 5 | 5 | `BURNED_TOWER_1F` | 1 | -| 14 | 0 | 18 | `ROUTE_38_ECRUTEAK_GATE` | 3 | -| 15 | 0 | 19 | `ROUTE_38_ECRUTEAK_GATE` | 4 | - -**Coord events** (`def_coord_events`) - -Empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 15 | 21 | `BGEVENT_READ` | `EcruteakCitySign` | -| 38 | 10 | `BGEVENT_READ` | `TinTowerSign` | -| 8 | 28 | `BGEVENT_READ` | `EcruteakGymSign` | -| 21 | 21 | `BGEVENT_READ` | `EcruteakDanceTheaterSign` | -| 2 | 10 | `BGEVENT_READ` | `BurnedTowerSign` | -| 24 | 27 | `BGEVENT_READ` | `EcruteakCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 30 | 21 | `BGEVENT_READ` | `EcruteakCityMartSign` (`jumpstd MartSignScript`) | -| 23 | 14 | `BGEVENT_ITEM` | `EcruteakCityHiddenHyperPotion` -> `hiddenitem HYPER_POTION, EVENT_ECRUTEAK_CITY_HIDDEN_HYPER_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ECRUTEAKCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 15 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakCityGramps1Script` | -1 | -| `ECRUTEAKCITY_GRAMPS2` | `SPRITE_GRAMPS` | 20 | 21 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `EcruteakCityGramps2Script` | -1 | -| `ECRUTEAKCITY_LASS1` | `SPRITE_LASS` | 21 | 29 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `EcruteakCityLass1Script` | -1 | -| `ECRUTEAKCITY_LASS2` | `SPRITE_LASS` | 3 | 9 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `EcruteakCityLass2Script` | -1 | -| `ECRUTEAKCITY_FISHER` | `SPRITE_FISHER` | 9 | 22 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `EcruteakCityFisherScript` | -1 | -| `ECRUTEAKCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 10 | 14 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakCityYoungsterScript` | -1 | - -**Scripts of interest** - -- `EcruteakCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): a bare - `setflag ENGINE_FLYPOINT_ECRUTEAK` / `endcallback`. Arriving on the map at all - registers the fly point. -- `EcruteakCityLass2Script`: branches on `EVENT_RELEASED_THE_BEASTS` - a cheap, - in-world way for a bot to confirm the Burned Tower cutscene fired. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_ECRUTEAK` | `constants/engine_flags.asm:86` | `EcruteakCityFlypointCallback` | Fly destination unlocked on first entry. | -| `EVENT_ECRUTEAK_CITY_HIDDEN_HYPER_POTION` | `constants/event_flags.asm:190` | `EcruteakCityHiddenHyperPotion` | Hidden HYPER POTION at (23,14). | -| `EVENT_RELEASED_THE_BEASTS` | `constants/event_flags.asm:132` | read here, written by `ReleaseTheBeasts` in `maps/BurnedTowerB1F.asm:64` | Beasts cutscene done. | -| `EVENT_JASMINE_RETURNED_TO_GYM` | `constants/event_flags.asm` | read by `EcruteakCityFisherScript` | Later-section state; not touched here. | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HYPER_POTION` | hidden, needs ITEMFINDER to find comfortably | `bg_event 23, 14, BGEVENT_ITEM` | `EVENT_ECRUTEAK_CITY_HIDDEN_HYPER_POTION` | - -**Trainers** - -None on the overworld map. - -**Wild encounters** - -Grass: none (`data/wild/johto_grass.asm` has no `ECRUTEAK_CITY` entry). -Water: `data/wild/johto_water.asm:246` -> `def_water_wildmons ECRUTEAK_CITY`, -2% encounter rate, L20 POLIWAG / L15 POLIWAG / L20 POLIWHIRL. Reachable only -after SURF is usable, i.e. after Morty. -Fishing group `FISHGROUP_POND` (`data/maps/maps.asm:173`), see `data/wild/fish.asm`. -Headbutt: `data/wild/treemon_maps.asm:35` -> `TREEMON_SET_CITY`. - ---- - -### ECRUTEAK_POKECENTER_1F - -- Script: `maps/EcruteakPokecenter1F.asm` -- Blocks: none (`data/maps/attributes.asm` uses the shared Pokecenter layout) -- Header: `data/maps/maps.asm:167` -> `TILESET_POKECENTER`, `INDOOR`, - `LANDMARK_ECRUTEAK_CITY`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:153` -> `map_const ECRUTEAK_POKECENTER_1F, 5, 4` -- Scene var: `data/maps/scenes.asm:43` -> `scene_var ECRUTEAK_POKECENTER_1F, wEcruteakPokecenter1FSceneID` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ECRUTEAK_CITY` | 6 | -| 2 | 4 | 7 | `ECRUTEAK_CITY` | 6 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events / BG events** - -Both empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ECRUTEAKPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakPokecenter1FNurseScript` | -1 | -| `ECRUTEAKPOKECENTER1F_POKEFAN_M` | `SPRITE_POKEFAN_M` | 7 | 6 | `SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | `EcruteakPokecenter1FPokefanMScript` | -1 | -| `ECRUTEAKPOKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 1 | 4 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `EcruteakPokecenter1FCooltrainerFScript` | -1 | -| `ECRUTEAKPOKECENTER1F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakPokecenter1FGymGuideScript` | -1 | -| `ECRUTEAKPOKECENTER1F_BILL` | `SPRITE_BILL` | 0 | 7 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_ECRUTEAK_POKE_CENTER_BILL` | - -**Scripts of interest** - -- Scene table: `scene_script EcruteakPokecenter1FMeetBillScene, SCENE_ECRUTEAKPOKECENTER1F_MEET_BILL` - (scene id 0, the wram default) then `..._NOOP` (scene id 1). Scene constants are - generated positionally by the `def_scene_scripts` / `scene_script` macros in - `macros/scripts/maps.asm:12-33`; there is no `SCENE_*` constants file. -- `EcruteakPokecenter1FMeetBillScene` -> `sdefer EcruteakPokcenter1FBillActivatesTimeCapsuleScript` - (`52:4299`). This is the "you cannot move for a second" the walkthrough - describes: it fires on map load, not on a coord trip-wire. -- `EcruteakPokcenter1FBillActivatesTimeCapsuleScript`: - `appear ECRUTEAKPOKECENTER1F_BILL` -> - `applymovement BILL` (UP x4, RIGHT x3, `turn_head UP`) and - `applymovement PLAYER` (UP x3) -> two text blocks -> - `applymovement BILL` (RIGHT, DOWN x4) -> `disappear` -> - **`clearevent EVENT_MET_BILL`** -> `setflag ENGINE_TIME_CAPSULE` -> - `setscene SCENE_ECRUTEAKPOKECENTER1F_NOOP`. - -`EVENT_MET_BILL` is *set* by the new-game initialisation block in -`engine/events/std_scripts.asm` (the long `setevent` run around line 514), so -Bill's object in `BILLS_FAMILYS_HOUSE` is masked until this cutscene clears it. -Talking to Bill in Goldenrod is impossible before this scene runs - this is the -hard ordering constraint behind the walkthrough's backtrack. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ECRUTEAK_POKE_CENTER_BILL` | `constants/event_flags.asm:1205` | `appear`/`disappear` in the cutscene | Set = Bill hidden here. Set at new game. | -| `EVENT_MET_BILL` | `constants/event_flags.asm:1204` | cleared here; read by `BillsFamilysHouse` object row and `BillsMomScript` | Cleared = Bill visible in Goldenrod. | -| `ENGINE_TIME_CAPSULE` | `constants/engine_flags.asm:100` | set here | Time Capsule menu option. | - ---- - -### DANCE_THEATER - -- Script: `maps/DanceTheater.asm` -- Blocks: `maps/DanceTheater.blk` -- Header: `data/maps/maps.asm:169` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`, - `LANDMARK_ECRUTEAK_CITY`, `MUSIC_DANCING_HALL`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:155` -> `map_const DANCE_THEATER, 6, 7` - (walk cells `x 0..11`, `y 0..13`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 13 | `ECRUTEAK_CITY` | 8 | -| 2 | 6 | 13 | `ECRUTEAK_CITY` | 8 | - -**Coord events** - -Empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 6 | `BGEVENT_UP` | `DanceTheaterFancyPanel` | -| 6 | 6 | `BGEVENT_UP` | `DanceTheaterFancyPanel` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DANCETHEATER_KIMONO_GIRL1` | `SPRITE_KIMONO_GIRL` | 0 | 2 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerKimonoGirlNaoko` | -1 | -| `DANCETHEATER_KIMONO_GIRL2` | `SPRITE_KIMONO_GIRL` | 2 | 1 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerKimonoGirlSayo` | -1 | -| `DANCETHEATER_KIMONO_GIRL3` | `SPRITE_KIMONO_GIRL` | 6 | 2 | `SPINRANDOM_SLOW` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerKimonoGirlZuki` | -1 | -| `DANCETHEATER_KIMONO_GIRL4` | `SPRITE_KIMONO_GIRL` | 9 | 1 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerKimonoGirlKuni` | -1 | -| `DANCETHEATER_KIMONO_GIRL5` | `SPRITE_KIMONO_GIRL` | 11 | 2 | `SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerKimonoGirlMiki` | -1 | -| `DANCETHEATER_GENTLEMAN` | `SPRITE_GENTLEMAN` | 7 | 10 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `DanceTheaterSurfGuy` | -1 | -| `DANCETHEATER_RHYDON` | `SPRITE_RHYDON` | 6 | 8 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `DanceTheaterRhydon` | -1 | -| `DANCETHEATER_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 10 | 10 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `DanceTheaterCooltrainerMScript` | -1 | -| `DANCETHEATER_GRANNY` | `SPRITE_GRANNY` | 3 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `DanceTheaterGrannyScript` | -1 | - -All five Kimono Girls have **sight range 0** - they never initiate. A bot must -walk adjacent and press A on each one. - -**Scripts of interest** - -- `DanceTheaterSurfGuy` (`52:485c`): `faceplayer`, `opentext`, - `writetext SurfGuyNeverLeftAScratchText`, then - `checkevent EVENT_GOT_HM03_SURF` -> `SurfGuyAlreadyGaveSurf`, else five - `checkevent` / `iffalse .KimonoGirlsUndefeated` in the order - NAOKO, SAYO, ZUKI, KUNI, MIKI; all five set -> - `.GetSurf` (`52:488f`): `verbosegiveitem HM_SURF`, - `setevent EVENT_GOT_HM03_SURF`. Note there is no `iffalse` after - `verbosegiveitem HM_SURF`, so a full bag still sets the flag - see "Unresolved". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_KIMONO_GIRL_NAOKO` | `constants/event_flags.asm:744` | `TrainerKimonoGirlNaoko` | gate 1/5 for SURF | -| `EVENT_BEAT_KIMONO_GIRL_SAYO` | `:745` | `TrainerKimonoGirlSayo` | gate 2/5 | -| `EVENT_BEAT_KIMONO_GIRL_ZUKI` | `:746` | `TrainerKimonoGirlZuki` | gate 3/5 | -| `EVENT_BEAT_KIMONO_GIRL_KUNI` | `:747` | `TrainerKimonoGirlKuni` | gate 4/5 | -| `EVENT_BEAT_KIMONO_GIRL_MIKI` | `:748` | `TrainerKimonoGirlMiki` | gate 5/5 | -| `EVENT_GOT_HM03_SURF` | `constants/event_flags.asm:25` | `DanceTheaterSurfGuy` | HM03 in the bag | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HM_SURF` | talk to the gentleman after all five Kimono Girls | `DanceTheaterSurfGuy.GetSurf` | `EVENT_GOT_HM03_SURF` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`, `KimonoGirlGroup`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `KIMONO_GIRL` / `NAOKO` | `KIMONO_GIRL` | entry 2 "NAOKO" | L17 FLAREON (`TRAINERTYPE_NORMAL`) | `TrainerKimonoGirlNaoko` | none | -| `KIMONO_GIRL` / `SAYO` | `KIMONO_GIRL` | entry 3 "SAYO" | L17 ESPEON | `TrainerKimonoGirlSayo` | none | -| `KIMONO_GIRL` / `ZUKI` | `KIMONO_GIRL` | entry 4 "ZUKI" | L17 UMBREON | `TrainerKimonoGirlZuki` | none | -| `KIMONO_GIRL` / `KUNI` | `KIMONO_GIRL` | entry 5 "KUNI" | L17 VAPOREON | `TrainerKimonoGirlKuni` | none | -| `KIMONO_GIRL` / `MIKI` | `KIMONO_GIRL` | entry 6 "MIKI" | L17 JOLTEON | `TrainerKimonoGirlMiki` | none | - -Constants at `constants/trainer_constants.asm:614-618`. All are -`TRAINERTYPE_NORMAL`, i.e. level-up movesets, no custom moves. - -**Wild encounters** - -None (indoor). - ---- - -### ECRUTEAK_ITEMFINDER_HOUSE - -- Script: `maps/EcruteakItemfinderHouse.asm` -- Blocks: none of its own -- Header: `data/maps/maps.asm:172` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`, - `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:158` -> `map_const ECRUTEAK_ITEMFINDER_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ECRUTEAK_CITY` | 11 | -| 2 | 4 | 7 | `ECRUTEAK_CITY` | 11 | - -**Coord events**: empty. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 1 | `BGEVENT_READ` | `ItemFinderHouseRadio` (`jumpstd Radio2Script`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ECRUTEAKITEMFINDERHOUSE_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 2 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakItemfinderGuy` | -1 | -| `ECRUTEAKITEMFINDERHOUSE_POKEDEX` | `SPRITE_POKEDEX` | 3 | 3 | `STILL` | `OBJECTTYPE_SCRIPT` | `EcruteakHistoryBook` | -1 | - -**Scripts of interest** - -- `EcruteakItemfinderGuy` (`52:588b`): `checkevent EVENT_GOT_ITEMFINDER` -> - already done; else `writetext EcruteakItemfinderAdventureText`, `yesorno`, - `iffalse .no`, then `verbosegiveitem ITEMFINDER`, - `setevent EVENT_GOT_ITEMFINDER`. A bot must answer YES. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ITEMFINDER` | answer YES to the cooltrainer | `EcruteakItemfinderGuy` | `EVENT_GOT_ITEMFINDER` (`constants/event_flags.asm:99`) | - ---- - -### BILLS_FAMILYS_HOUSE - -Not an Ecruteak map - it is in Goldenrod, warp 4 of `GOLDENROD_CITY` at (5,25). -Included because this section's Eevee comes from here and is ordering-locked by -the Ecruteak Pokecenter cutscene. - -- Script: `maps/BillsFamilysHouse.asm` -- Header: `data/maps/maps.asm:273` -> `TILESET_HOUSE`, `INDOOR`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_GOLDENROD_CITY`, phone `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:251` -> `map_const BILLS_FAMILYS_HOUSE, 4, 4` - -Do not confuse with `maps/BillsHouse.asm` (`BILLS_HOUSE`, Kanto Route 25, -`constants/map_constants.asm:197`) - that is Bill's grandfather and the -stone-for-showing-a-mon chain, nothing to do with Eevee. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 4 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 4 | - -**Coord events**: empty. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `BillsHouseBookshelf2` (`jumpstd MagazineBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `BillsHouseBookshelf1` (`jumpstd PictureBookshelfScript`) | -| 7 | 1 | `BGEVENT_READ` | `BillsHouseRadio` (`jumpstd Radio2Script`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BILLSFAMILYSHOUSE_BILL` | `SPRITE_BILL` | 2 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `BillScript` | `EVENT_MET_BILL` | -| `BILLSFAMILYSHOUSE_POKEFAN_F` | `SPRITE_POKEFAN_F` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BillsMomScript` | -1 | -| `BILLSFAMILYSHOUSE_TWIN` | `SPRITE_TWIN` | 5 | 4 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BillsYoungerSisterScript` | -1 | - -**Scripts of interest** - -- `BillScript` (`57:4bee`): `checkevent EVENT_GOT_EEVEE` -> done; else - `writetext BillTakeThisEeveeText`, `yesorno`, `iffalse .Refused`, - then `readvar VAR_PARTYCOUNT`, **`ifequal PARTY_LENGTH, .NoRoom`** - - this is the walkthrough's "clear one Pokémon spot" - then - `givepoke EEVEE, 20` and `setevent EVENT_GOT_EEVEE`. -- `BillsYoungerSisterScript`: `checkcellnum PHONE_BILL` / `askforphonenumber` / - `addcellnum PHONE_BILL`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_MET_BILL` | `constants/event_flags.asm:1204` | object mask here; cleared by the Ecruteak Pokecenter cutscene | Set (new-game default) = Bill absent. | -| `EVENT_GOT_EEVEE` | `constants/event_flags.asm:88` | `BillScript` | One-time L20 EEVEE. | - -**Items / Pokemon** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| EEVEE (L20) | `givepoke EEVEE, 20` after YES with a free party slot | `BillScript` | `EVENT_GOT_EEVEE` | - ---- - -### BURNED_TOWER_1F - -- Script: `maps/BurnedTower1F.asm` -- Blocks: `maps/BurnedTower1F.blk` (90 bytes = 10x9 blocks) -- Header: `data/maps/maps.asm:91` -> `TILESET_TOWER`, `DUNGEON`, - `LANDMARK_BURNED_TOWER`, `MUSIC_BURNED_TOWER`, phone `FALSE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:78` -> `map_const BURNED_TOWER_1F, 10, 9` - (walk cells `x 0..19`, `y 0..17`) -- Scene var: `data/maps/scenes.asm:48` -> `wBurnedTower1FSceneID` -- Scene ids (positional, from `def_scene_scripts`): 0 = - `SCENE_BURNEDTOWER1F_RIVAL_BATTLE`, 1 = `SCENE_BURNEDTOWER1F_FIREBREATHER_DICK`, - 2 = `SCENE_BURNEDTOWER1F_NOOP` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `ECRUTEAK_CITY` | 13 | -| 2 | 10 | 15 | `ECRUTEAK_CITY` | 13 | -| 3 | 5 | 4 | `BURNED_TOWER_B1F` | 1 | -| 4 | 5 | 5 | `BURNED_TOWER_B1F` | 1 | -| 5 | 5 | 6 | `BURNED_TOWER_B1F` | 1 | -| 6 | 4 | 6 | `BURNED_TOWER_B1F` | 1 | -| 7 | 15 | 4 | `BURNED_TOWER_B1F` | 2 | -| 8 | 15 | 5 | `BURNED_TOWER_B1F` | 2 | -| 9 | 10 | 7 | `BURNED_TOWER_B1F` | 3 | -| 10 | 5 | 14 | `BURNED_TOWER_B1F` | 4 | -| 11 | 4 | 14 | `BURNED_TOWER_B1F` | 4 | -| 12 | 14 | 14 | `BURNED_TOWER_B1F` | 5 | -| 13 | 15 | 14 | `BURNED_TOWER_B1F` | 5 | -| 14 | 7 | 15 | `BURNED_TOWER_B1F` | 6 | - -Warps 1/2 are the `WARP_CARPET_DOWN` doorway. Warps 3-13 are `PIT` tiles (one-way -falls). Warp 14 is the only `LADDER` and the only two-way link to B1F. - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_BURNEDTOWER1F_FIREBREATHER_DICK` (1) | 8 | 1 | `FirebreatherDickFight` | Ambush battle with Firebreather Dick. | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 8 | 7 | `BGEVENT_ITEM` | `BurnedTower1FHiddenEther` -> `hiddenitem ETHER, EVENT_BURNED_TOWER_1F_HIDDEN_ETHER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BURNEDTOWER1F_FIREBREATHER_DICK` | `SPRITE_FISHER` | 8 | 3 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `FirebreatherDickPostBattle` | `EVENT_BURNED_TOWER_FIREBREATHER_DICK_NORMAL` | -| `BURNEDTOWER1F_FIREBREATHER_NED` | `SPRITE_FISHER` | 16 | 8 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerFirebreatherNed` | -1 | -| `BURNEDTOWER1F_ROCK1` | `SPRITE_ROCK` | 4 | 3 | `SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `BurnedTower1FRock` | -1 | -| `BURNEDTOWER1F_ROCK2` | `SPRITE_ROCK` | 16 | 13 | `SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `BurnedTower1FRock` | -1 | -| `BURNEDTOWER1F_RIVAL` | `SPRITE_RIVAL` | 9 | 12 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `ObjectEvent` | `EVENT_RIVAL_BURNED_TOWER` | -| `BURNEDTOWER1F_FIREBREATHER_DICK_ASHES` | `SPRITE_FISHER` | 8 | 2 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `FirebreatherDickPostBattle` | `EVENT_BURNED_TOWER_FIREBREATHER_DICK_ASHES` | -| `BURNEDTOWER1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 15 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `BurnedTower1FBurnHeal` (`itemball BURN_HEAL, 1`) | `EVENT_BURNED_TOWER_1F_X_SPEED` | -| `BURNEDTOWER1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 7 | 5 | `STILL` | `OBJECTTYPE_ITEMBALL` | `BurnedTower1FXSpeed` (`itemball X_SPEED, 1`) | `EVENT_BURNED_TOWER_1F_BURN_HEAL` | - -The two item ball rows have their one-time flags crossed: the ball that gives -`BURN_HEAL` is masked by `EVENT_BURNED_TOWER_1F_X_SPEED` and vice versa. It is -harmless (each ball still disappears exactly once) but a bot reading flags to -decide "do I already have the Burn Heal" must use the *row's* flag, not the item -name. - -**Derived collision map** (`maps/BurnedTower1F.blk` + `data/tilesets/tower_collision.asm`; -`.` floor, `#` wall, `O` pit/warp, `L` ladder, `v` door) - -``` - 0 1 - 0123456789012345678 - 1 ##........##....### - 2 ###..##...###...### - 3 #.#..#....#.....### - 4 #...O###...##.O#### - 5 #...O#.#....#.O.### - 6 #..OO#.##...#..#### - 7 #....#..#O..#..#.## - 8 #...##.####.#....## - 9 #...##..##..#....## -10 ##.###.....###...## -11 ##..########.#...## -12 ###..........##..## -13 #.............#..## -14 ###OO........OO.### -15 ##....L.vv......### -``` -(column 0 and rows 0/16-17 are solid wall and are trimmed.) - -**Scripts of interest** - -- `BurnedTower1FRivalBattleScene` (scene 0, the wram default) -> - `sdefer BurnedTower1FRivalBattleScript` (`42:4e40`). Scene scripts run at map - load, so **the rival battle fires the moment you first walk in the front door**, - with the player at (9,15) and the rival at (9,12) walking two steps DOWN to - (9,14). It is not at the far end of a spiral. -- `BurnedTower1FRivalBattleScript`: `turnobject PLAYER, UP`, - `showemote EMOTE_SHOCK`, `special FadeOutMusic`, rival turn/pause choreography, - `applymovement BURNEDTOWER1F_RIVAL, BurnedTowerMovement_RivalWalksToPlayer` - (DOWN, DOWN), `playmusic MUSIC_RIVAL_ENCOUNTER`, then the starter branch: - `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `loadtrainer RIVAL1, RIVAL1_3_CHIKORITA`; - `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `loadtrainer RIVAL1, RIVAL1_3_CYNDAQUIL`; - otherwise (player took Cyndaquil) `loadtrainer RIVAL1, RIVAL1_3_TOTODILE`. - After: `playmusic MUSIC_RIVAL_AFTER`, - `applymovement ... BurnedTowerMovement_RivalLeaves` (RIGHT, DOWN), - `disappear BURNEDTOWER1F_RIVAL`, - `setscene SCENE_BURNEDTOWER1F_FIREBREATHER_DICK`. -- `FirebreatherDickFight` (`42:4ec1`), reached by the (8,1) coord event once the - scene is 1: `showemote`, `applymovement` (Dick steps UP), - `loadtrainer FIREBREATHER, DICK`, `startbattle`; on a win - `disappear ..._DICK` / `appear ..._DICK_ASHES`, - `setevent EVENT_BEAT_FIREBREATHER_DICK`, - `setscene SCENE_BURNEDTOWER1F_NOOP`, - `clearevent EVENT_BURNED_TOWER_FIREBREATHER_DICK_NORMAL`, - `setevent EVENT_BURNED_TOWER_FIREBREATHER_DICK_ASHES`. -- `BurnedTower1FRock` -> `jumpstd SmashRockScript` - (`engine/events/std_scripts.asm:199`) -> `farsjump AskRockSmashScript` - (`engine/events/overworld.asm:1365`) -> `callasm HasRockSmash` - (`CheckPartyMove ROCK_SMASH`). **No badge is required for Rock Smash**, only a - party member that knows the move. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RIVAL_BURNED_TOWER` | `constants/event_flags.asm:1127` | masks the rival object; **set** by `UndergroundRivalBattleScript` in `maps/GoldenrodUndergroundSwitchRoomEntrances.asm:125` | If you fight the rival in the Goldenrod Underground first, that script also does `setmapscene BURNED_TOWER_1F, SCENE_BURNEDTOWER1F_FIREBREATHER_DICK` and the Burned Tower rival battle never happens. Do this section before the Underground. | -| `EVENT_BEAT_FIREBREATHER_DICK` | `:577` | `FirebreatherDickFight` | - | -| `EVENT_BEAT_FIREBREATHER_NED` | `:578` | `TrainerFirebreatherNed` | - | -| `EVENT_BURNED_TOWER_FIREBREATHER_DICK_NORMAL` | `:1286` | cleared after the fight | Masks the pre-fight Dick at (8,3). | -| `EVENT_BURNED_TOWER_FIREBREATHER_DICK_ASHES` | `:1287` | set at new game, cleared/set here | Masks the post-fight Dick at (8,2). | -| `EVENT_BURNED_TOWER_1F_HIDDEN_ETHER` | `:138` | `BurnedTower1FHiddenEther` | Hidden ETHER at (8,7). | -| `EVENT_BURNED_TOWER_1F_X_SPEED` | `:1013` | ball at (15,2) that gives BURN_HEAL | Crossed name. | -| `EVENT_BURNED_TOWER_1F_BURN_HEAL` | `:1014` | ball at (7,5) that gives X_SPEED | Crossed name. | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BURN_HEAL` | item ball, NE pocket (15,2) | `BurnedTower1FBurnHeal` | `EVENT_BURNED_TOWER_1F_X_SPEED` | -| `X_SPEED` | item ball, mid-west (7,5) | `BurnedTower1FXSpeed` | `EVENT_BURNED_TOWER_1F_BURN_HEAL` | -| `ETHER` | hidden at (8,7) | `bg_event 8, 7, BGEVENT_ITEM` | `EVENT_BURNED_TOWER_1F_HIDDEN_ETHER` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL1` / `RIVAL1_3_CHIKORITA` | `RIVAL1` | `constants/trainer_constants.asm:56` | `Rival1Group` entry 7: L20 HAUNTER (LICK, SPITE, MEAN_LOOK, CURSE); L18 MAGNEMITE (TACKLE, THUNDERSHOCK, SUPERSONIC, SONICBOOM); L20 ZUBAT (LEECH_LIFE, SUPERSONIC, BITE, CONFUSE_RAY); L22 BAYLEEF (GROWL, REFLECT, RAZOR_LEAF, POISONPOWDER) | `BurnedTower1FRivalBattleScript` | none | -| `RIVAL1` / `RIVAL1_3_CYNDAQUIL` | `RIVAL1` | `:57` | `Rival1Group` entry 8: same first three, L22 QUILAVA (LEER, SMOKESCREEN, EMBER, QUICK_ATTACK) | same | none | -| `RIVAL1` / `RIVAL1_3_TOTODILE` | `RIVAL1` | `:58` | `Rival1Group` entry 9: same first three, L22 CROCONAW (LEER, RAGE, WATER_GUN, BITE) | same | none | -| `FIREBREATHER` / `DICK` | `FIREBREATHER` | `:479` | `FirebreatherGroup` entry 2: L17 CHARMELEON | `FirebreatherDickFight` | none | -| `FIREBREATHER` / `NED` | `FIREBREATHER` | `:480` | `FirebreatherGroup` entry 3: L15 KOFFING, L16 GROWLITHE, L15 KOFFING | `TrainerFirebreatherNed` | none | - -The rival's mon is the starter that beats yours: player Totodile -> rival -Chikorita line, player Chikorita -> rival Cyndaquil line, player Cyndaquil -> -rival Totodile line. - -**Wild encounters** - -`data/wild/johto_grass.asm:285`, `def_grass_wildmons BURNED_TOWER_1F`, -4% / 4% / 4%. Identical morn/day/nite table: -L13 RATTATA, L14 KOFFING, L15 RATTATA, L14 ZUBAT, L16 KOFFING, L15 RATICATE, -L15 RATICATE. (The walkthrough's "Rattata / Zubat / Koffing" is correct as far -as it goes; Raticate is also in the table.) - ---- - -### BURNED_TOWER_B1F - -- Script: `maps/BurnedTowerB1F.asm` -- Blocks: `maps/BurnedTowerB1F.blk` (90 bytes = 10x9 blocks) -- Header: `data/maps/maps.asm:92` -> `TILESET_CAVE`, `CAVE`, - `LANDMARK_BURNED_TOWER`, `MUSIC_BURNED_TOWER`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:79` -> `map_const BURNED_TOWER_B1F, 10, 9` -- Scene var: `data/maps/scenes.asm:49` -> `wBurnedTowerB1FSceneID` -- Scene ids: 0 = `SCENE_BURNEDTOWERB1F_RELEASE_THE_BEASTS`, 1 = `SCENE_BURNEDTOWERB1F_NOOP` - (both scene scripts are `end`; the work is done by the coord event) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `BURNED_TOWER_1F` | 3 | -| 2 | 17 | 7 | `BURNED_TOWER_1F` | 7 | -| 3 | 10 | 8 | `BURNED_TOWER_1F` | 9 | -| 4 | 3 | 13 | `BURNED_TOWER_1F` | 10 | -| 5 | 17 | 14 | `BURNED_TOWER_1F` | 12 | -| 6 | 7 | 15 | `BURNED_TOWER_1F` | 14 | - -Only warp 6 sits on a `LADDER` tile. Warps 1-5 are landing anchors for the 1F -pits; their own tiles are `FLOOR` or ledge tiles, so `CheckWarpCollision` -(`engine/overworld/tile_events.asm:1`) never fires on them and you cannot go back -up through them. **The ladder at (7,15) is the only exit from B1F.** - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_BURNEDTOWERB1F_RELEASE_THE_BEASTS` (0) | 9 | 5 | `ReleaseTheBeasts` | The Raikou/Entei/Suicune cutscene. | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 13 | `BGEVENT_ITEM` | `BurnedTowerB1FHiddenUltraBall` -> `hiddenitem ULTRA_BALL, EVENT_BURNED_TOWER_B1F_HIDDEN_ULTRA_BALL` | -| 17 | 14 | `BGEVENT_ITEM` | `BurnedTowerB1FHiddenBurnHeal` -> `hiddenitem BURN_HEAL, EVENT_BURNED_TOWER_B1F_HIDDEN_BURN_HEAL` | -| 8 | 3 | `BGEVENT_ITEM` | `BurnedTowerB1FHiddenNugget` -> `hiddenitem NUGGET, EVENT_BURNED_TOWER_B1F_HIDDEN_NUGGET` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BURNEDTOWERB1F_BOULDER` | `SPRITE_BOULDER` | 17 | 4 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `BurnedTowerB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 | -| `BURNEDTOWERB1F_RAIKOU1` | `SPRITE_GROWLITHE` | 10 | 3 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_1` | -| `BURNEDTOWERB1F_ENTEI1` | `SPRITE_GROWLITHE` | 8 | 4 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_1` | -| `BURNEDTOWERB1F_SUICUNE1` | `SPRITE_GROWLITHE` | 7 | 2 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_1` | -| `BURNEDTOWERB1F_RAIKOU2` | `SPRITE_GROWLITHE` | 10 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_2` | -| `BURNEDTOWERB1F_ENTEI2` | `SPRITE_GROWLITHE` | 8 | 4 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_2` | -| `BURNEDTOWERB1F_SUICUNE2` | `SPRITE_GROWLITHE` | 7 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_BURNED_TOWER_B1F_BEASTS_2` | -| `BURNEDTOWERB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 4 | 3 | `STILL` | `OBJECTTYPE_ITEMBALL` | `BurnedTowerB1FHPUp` (`itemball HP_UP`) | `EVENT_BURNED_TOWER_B1F_HP_UP` | -| `BURNEDTOWERB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 15 | 3 | `STILL` | `OBJECTTYPE_ITEMBALL` | `BurnedTowerB1FTMEndure` (`itemball TM_ENDURE`) | `EVENT_BURNED_TOWER_B1F_TM_ENDURE` | - -The two `SPRITE_GROWLITHE` triples are the same three beasts drawn twice: the -`..._1` set (`SPRITEMOVEDATA_POKEMON`, coloured `PAL_NPC_BROWN/RED/BLUE`) is the -animated set, the `..._2` set (`STANDING_DOWN`, `PAL_NPC_EMOTE`) is the static -set. `EVENT_BURNED_TOWER_B1F_BEASTS_1` is **set** by the new-game init block in -`engine/events/std_scripts.asm:525`, so the static set is what you first see; -`ReleaseTheBeasts` does `appear`/`disappear` to swap them. - -**Scripts of interest** - -- `ReleaseTheBeasts` (`42:53c4`), triggered by the (9,5) coord event on scene 0: - `playmusic MUSIC_NONE`, three `appear`/`disappear`/`cry` beats for RAIKOU, - ENTEI, SUICUNE, then three `playsound SFX_WARP_FROM` + `applymovement` - (`BurnedTowerRaikouMovement` right, `BurnedTowerEnteiMovement` down, - `BurnedTowerSuicuneMovement` left) + `disappear`, then - `special RestartMapMusic`, `setscene SCENE_BURNEDTOWERB1F_NOOP`, - `setevent EVENT_RELEASED_THE_BEASTS`, **`special InitRoamMons`**. -- `UnusedEnteiScript` is marked `; unreferenced` - a `loadwildmon ENTEI, 40` - encounter that never runs. Do not build a bot around it. -- `BurnedTowerB1FBoulder` -> `jumpstd StrengthBoulderScript` -> - `farsjump AskStrengthScript` (`engine/events/overworld.asm:1001`), which needs - STRENGTH (PLAINBADGE gated) - not obtainable in this section, and not needed - for anything here. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RELEASED_THE_BEASTS` | `constants/event_flags.asm:132` | set by `ReleaseTheBeasts`; read by `EcruteakCityLass2Script` | Cutscene done, roamers live. | -| `EVENT_BURNED_TOWER_B1F_BEASTS_1` | `:1260` | set at new game (`std_scripts.asm:525`), cleared by `appear` | Set = animated beasts hidden. | -| `EVENT_BURNED_TOWER_B1F_BEASTS_2` | `:1261` | set by `disappear` in the cutscene | Set = static beasts hidden. | -| `EVENT_BURNED_TOWER_B1F_HP_UP` | `:1015` | item ball (4,3) | - | -| `EVENT_BURNED_TOWER_B1F_TM_ENDURE` | `:1016` | item ball (15,3) | - | -| `EVENT_BURNED_TOWER_B1F_HIDDEN_ULTRA_BALL` | `:139` | bg event (2,13) | - | -| `EVENT_BURNED_TOWER_B1F_HIDDEN_BURN_HEAL` | `:140` | bg event (17,14) | - | -| `EVENT_BURNED_TOWER_B1F_HIDDEN_NUGGET` | `:141` | bg event (8,3) | - | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HP_UP` | item ball at (4,3), right next to the warp-1 landing spot | `BurnedTowerB1FHPUp` | `EVENT_BURNED_TOWER_B1F_HP_UP` | -| `TM_ENDURE` | item ball at (15,3), east ledge pocket | `BurnedTowerB1FTMEndure` | `EVENT_BURNED_TOWER_B1F_TM_ENDURE` | -| `ULTRA_BALL` | hidden at (2,13) | bg event | `EVENT_BURNED_TOWER_B1F_HIDDEN_ULTRA_BALL` | -| `BURN_HEAL` | hidden at (17,14) | bg event | `EVENT_BURNED_TOWER_B1F_HIDDEN_BURN_HEAL` | -| `NUGGET` | hidden at (8,3) | bg event | `EVENT_BURNED_TOWER_B1F_HIDDEN_NUGGET` | - -**Trainers** - -None. - -**Wild encounters** - -`data/wild/johto_grass.asm:313`, `def_grass_wildmons BURNED_TOWER_B1F`, -6% / 6% / 6%. - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L14 RATTATA | L14 RATTATA | L14 RATTATA | -| 2 | L14 KOFFING | L14 KOFFING | L14 KOFFING | -| 3 | L16 KOFFING | L16 KOFFING | L16 KOFFING | -| 4 | L16 RATTATA | L16 MAGMAR | L16 RATTATA | -| 5 | L15 ZUBAT | L15 ZUBAT | L15 ZUBAT | -| 6 | L14 MAGMAR | L14 RATTATA | L14 MAGMAR | -| 7 | L14 MAGMAR | L14 RATTATA | L14 MAGMAR | - -**Movement note**: B1F is built on `HOP_*` ledge tiles (`data/tilesets/cave_collision.asm`). -Landing anchors (17,7) and (10,8) are themselves ledge tiles; walking off a ledge -tile in the ledge's direction becomes a two-cell jump -(`.TryJump` / `.ledge_table`, `engine/overworld/player_movement.asm:350-392`). -The route to the beasts is: fall through 1F's centre pit at (10,7) -> land at -B1F (10,8) -> walk UP to (10,7), (10,6) -> LEFT to (9,6) -> UP onto (9,5) -> -`ReleaseTheBeasts`. - ---- - -### ECRUTEAK_GYM - -- Script: `maps/EcruteakGym.asm` -- Blocks: `maps/EcruteakGym.blk` (45 bytes = 5x9 blocks) -- Header: `data/maps/maps.asm:171` -> `TILESET_TOWER`, `INDOOR`, - `LANDMARK_ECRUTEAK_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:157` -> `map_const ECRUTEAK_GYM, 5, 9` - (walk cells `x 0..9`, `y 0..17`) -- No scene scripts, no coord events. - -**Warps** (`def_warp_events`) - 33 rows, almost all of them the invisible floor - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `ECRUTEAK_CITY` | 10 | -| 2 | 5 | 17 | `ECRUTEAK_CITY` | 10 | -| 3 | 4 | 14 | `ECRUTEAK_GYM` | 4 | -| 4 | 2 | 4 | `ECRUTEAK_GYM` | 3 | -| 5 | 3 | 4 | `ECRUTEAK_GYM` | 3 | -| 6 | 4 | 4 | `ECRUTEAK_GYM` | 3 | -| 7 | 4 | 5 | `ECRUTEAK_GYM` | 3 | -| 8 | 6 | 7 | `ECRUTEAK_GYM` | 3 | -| 9 | 7 | 4 | `ECRUTEAK_GYM` | 3 | -| 10 | 2 | 6 | `ECRUTEAK_GYM` | 3 | -| 11 | 3 | 6 | `ECRUTEAK_GYM` | 3 | -| 12 | 4 | 6 | `ECRUTEAK_GYM` | 3 | -| 13 | 5 | 6 | `ECRUTEAK_GYM` | 3 | -| 14 | 7 | 6 | `ECRUTEAK_GYM` | 3 | -| 15 | 7 | 7 | `ECRUTEAK_GYM` | 3 | -| 16 | 4 | 8 | `ECRUTEAK_GYM` | 3 | -| 17 | 5 | 8 | `ECRUTEAK_GYM` | 3 | -| 18 | 6 | 8 | `ECRUTEAK_GYM` | 3 | -| 19 | 7 | 8 | `ECRUTEAK_GYM` | 3 | -| 20 | 2 | 8 | `ECRUTEAK_GYM` | 3 | -| 21 | 2 | 9 | `ECRUTEAK_GYM` | 3 | -| 22 | 2 | 10 | `ECRUTEAK_GYM` | 3 | -| 23 | 2 | 11 | `ECRUTEAK_GYM` | 3 | -| 24 | 4 | 10 | `ECRUTEAK_GYM` | 3 | -| 25 | 5 | 10 | `ECRUTEAK_GYM` | 3 | -| 26 | 2 | 12 | `ECRUTEAK_GYM` | 3 | -| 27 | 3 | 12 | `ECRUTEAK_GYM` | 3 | -| 28 | 4 | 12 | `ECRUTEAK_GYM` | 3 | -| 29 | 5 | 12 | `ECRUTEAK_GYM` | 3 | -| 30 | 7 | 10 | `ECRUTEAK_GYM` | 3 | -| 31 | 7 | 11 | `ECRUTEAK_GYM` | 3 | -| 32 | 7 | 12 | `ECRUTEAK_GYM` | 3 | -| 33 | 7 | 13 | `ECRUTEAK_GYM` | 3 | - -Every hole (warps 4-33) dumps you at warp 3, i.e. (4,14), just inside the door - -the walkthrough's "fall off the path anywhere and it'll drop you back to the gym -entrance". Warp 3's own destination (warp 4 at (2,4)) is never used, because -(4,14) is a `FLOOR` tile and `CheckWarpCollision` only fires on pit/warp -collisions. - -**Coord events**: empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `EcruteakGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `EcruteakGymStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ECRUTEAKGYM_MORTY` | `SPRITE_MORTY` | 5 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakGymMortyScript` | -1 | -| `ECRUTEAKGYM_SAGE1` | `SPRITE_SAGE` | 2 | 7 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSageJeffrey` | -1 | -| `ECRUTEAKGYM_SAGE2` | `SPRITE_SAGE` | 3 | 13 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSagePing` | -1 | -| `ECRUTEAKGYM_GRANNY1` | `SPRITE_GRANNY` | 7 | 5 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerMediumMartha` | -1 | -| `ECRUTEAKGYM_GRANNY2` | `SPRITE_GRANNY` | 7 | 9 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerMediumGrace` | -1 | -| `ECRUTEAKGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 15 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakGymGuideScript` | -1 | - -**Derived collision map** (`maps/EcruteakGym.blk` + `data/tilesets/tower_collision.asm`; -`.` floor, `#` wall, `O` pit, `v` exit carpet) - -``` - 0123456789 - 0 ##......## - 1 ##..M...## M = Morty (5,1) - 2 ##......## - 3 ##......## - 4 ##OOO.O.## (6,4) is a PIT with NO warp row -> safe to stand on - 5 ##..O..G## G = Medium Martha (7,5) - 6 ##OOOO.O## - 7 ##J...F O## J = Sage Jeffrey (2,7); F = (6,7) FLOOR with a stray warp row - 8 ##O.OOOO## - 9 ##O....G## G = Medium Grace (7,9) -10 ##O.OO.O## -11 ##O....O## -12 ##OOOO.O## -13 ##P....O## P = Sage Ping (3,13) -14 ##......## (4,14) = warp 3, the landing tile for every hole -15 ##.##..##. statues at (3,15) and (6,15) -16 ##........ -17 ####vv#### exit at (4,17)/(5,17) -``` -(rows 15-17 above are schematic for the statue/door area; the pit rows 4-13 are -exact.) - -Two data quirks, both verified by diffing the warp rows against the block -collision: - -- **(6,7) has a warp row but is a `FLOOR` tile**, so it never triggers. This is - what makes the walkthrough's "go right 3" step from (3,7) land safely on (6,7). -- **(6,4) is a `PIT` tile with no warp row at all.** `CheckWarpCollision` sets - carry, `GetDestinationWarpNumber` finds no match, `CheckWarpTile` returns no - carry, so you simply stand on it. This is what makes "move up two squares to - get past the platform" work. It looks like the (6,4) row was mistyped as (6,7). - -**The verified zig-zag path** (every cell below is FLOOR unless noted): - -`(4,17) -> (4,16) -> (4,15) -> (4,14) -> (4,13)` [face LEFT: **Sage Ping** at (3,13)] -`-> (5,13) -> (6,13) -> (6,12) -> (6,11) -> (6,10) -> (6,9)` [face RIGHT: **Medium Grace** at (7,9)] -`-> (5,9) -> (4,9) -> (3,9) -> (3,8) -> (3,7)` [face LEFT: **Sage Jeffrey** at (2,7)] -`-> (4,7) -> (5,7) -> (6,7) -> (6,6) -> (6,5)` [face RIGHT: **Medium Martha** at (7,5)] -`-> (6,4)` (the harmless pit) `-> (6,3) -> (5,3) -> (5,2)` [face UP: **Morty** at (5,1)] - -**Scripts of interest** - -- `EcruteakGymMortyScript` (`52:508f`): `checkevent EVENT_BEAT_MORTY` -> - `.FightDone`; else `writetext MortyIntroText`, - `winlosstext MortyWinLossText, 0`, `loadtrainer MORTY, MORTY1`, `startbattle`, - `reloadmapafterbattle`, `setevent EVENT_BEAT_MORTY`, - `writetext Text_ReceivedFogBadge`, `playsound SFX_GET_BADGE`, - **`setflag ENGINE_FOGBADGE`**, `readvar VAR_BADGES`, - `scall EcruteakGymActivateRockets`, - `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP`. - Then `.FightDone` (`52:50bd`): `checkevent EVENT_GOT_TM30_SHADOW_BALL` -> - `.GotShadowBall`; else it **force-sets all four gym trainer flags** - (`EVENT_BEAT_SAGE_JEFFREY`, `EVENT_BEAT_SAGE_PING`, `EVENT_BEAT_MEDIUM_MARTHA`, - `EVENT_BEAT_MEDIUM_GRACE`), then `verbosegiveitem TM_SHADOW_BALL`, - `iffalse .NoRoomForShadowBall`, `setevent EVENT_GOT_TM30_SHADOW_BALL`. - So TM30 is safely re-obtainable if the bag was full: the flag is only set on a - successful give. -- `EcruteakGymActivateRockets`: `ifequal 7, .RadioTowerRockets` / - `ifequal 6, .GoldenrodRockets` on `VAR_BADGES`. Irrelevant on a linear run - (Fog is badge 4), but a bot that badge-skips can trip it. -- `EcruteakGymStatue`: `checkflag ENGINE_FOGBADGE` -> `GymStatue2Script` with - `gettrainername STRING_BUFFER_4, MORTY, MORTY1`; else `GymStatue1Script`. -- `EcruteakGymGuideScript` branches on `EVENT_BEAT_MORTY`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_MORTY` | `constants/event_flags.asm:709` | `EcruteakGymMortyScript` | Badge fight done. | -| `ENGINE_FOGBADGE` | `constants/engine_flags.asm:41` | set here; read by `SurfFunction.TrySurf` (`engine/events/overworld.asm:340`), `TrySurfOW` (`:490`), `EcruteakGymStatue`, `EcruteakTinTowerEntranceSageScript` | Field SURF, obedience to L50, `data/types/badge_type_boosts.asm:6` GHOST boost. | -| `EVENT_GOT_TM30_SHADOW_BALL` | `constants/event_flags.asm:18` | `EcruteakGymMortyScript` | TM30 given. | -| `EVENT_BEAT_SAGE_JEFFREY` | `:522` | `TrainerSageJeffrey`, force-set by Morty | - | -| `EVENT_BEAT_SAGE_PING` | `:523` | `TrainerSagePing`, force-set by Morty | - | -| `EVENT_BEAT_MEDIUM_MARTHA` | `:930` | `TrainerMediumMartha`, force-set by Morty | - | -| `EVENT_BEAT_MEDIUM_GRACE` | `:931` | `TrainerMediumGrace`, force-set by Morty | - | -| `SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP` | positional (id 1) in `maps/EcruteakTinTowerEntrance.asm` | `setmapscene` from Morty's script | Disables the Tin Tower sage-block coord events at (4,7) and (5,7). | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SHADOW_BALL` (TM30) | beat Morty, then keep talking | `EcruteakGymMortyScript.FightDone` | `EVENT_GOT_TM30_SHADOW_BALL` | -| FOGBADGE | beat Morty | `setflag ENGINE_FOGBADGE` | `EVENT_BEAT_MORTY` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SAGE` / `PING` | `SAGE` (`constants/trainer_constants.asm:579`) | `SageGroup` entry 6 "PING" | 5x L16 GASTLY, `TRAINERTYPE_NORMAL` | `TrainerSagePing` | none | -| `MEDIUM` / `GRACE` | `MEDIUM` (`:586`) | `MediumGroup` entry 2 "GRACE" | L20 HAUNTER, L20 HAUNTER | `TrainerMediumGrace` | none | -| `SAGE` / `JEFFREY` | `SAGE` (`:578`) | `SageGroup` entry 5 "JEFFREY" | L22 HAUNTER | `TrainerSageJeffrey` | none | -| `MEDIUM` / `MARTHA` | `MEDIUM` (`:585`) | `MediumGroup` entry 1 "MARTHA" | L18 GASTLY, L20 HAUNTER, L20 GASTLY | `TrainerMediumMartha` | none | -| `MORTY` / `MORTY1` | `MORTY` (`:36-37`) | `MortyGroup`, `TRAINERTYPE_MOVES` | L21 GASTLY (LICK, SPITE, MEAN_LOOK, CURSE); L21 HAUNTER (HYPNOSIS, MIMIC, CURSE, NIGHT_SHADE); L25 GENGAR (HYPNOSIS, SHADOW_BALL, MEAN_LOOK, DREAM_EATER); L23 HAUNTER (SPITE, MEAN_LOOK, MIMIC, NIGHT_SHADE) | `EcruteakGymMortyScript` | none | - -`MortyGroup` sends the mons in file order: GASTLY 21, HAUNTER 21, GENGAR 25, -HAUNTER 23. The walkthrough's "L23 Haunter, L25 Gengar, L23 Haunter" ordering and -its "Level 23 Haunter" second slot do not match the asm - the second slot is -**level 21**, and Gengar comes third, not last. - -**Wild encounters** - -None (indoor). - ---- - -### ECRUTEAK_TIN_TOWER_ENTRANCE (one line, unlocked here) - -`maps/EcruteakTinTowerEntrance.asm` has -`coord_event 4, 7, SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS, EcruteakTinTowerEntranceSageBlocksLeft` -and `coord_event 5, 7, ..., ...SageBlocksRight`; Morty's `setmapscene` to the -NOOP scene is what stops the sages from side-stepping in front of you. Everything -past that door belongs to the next section. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Bill is not in Goldenrod | `maps/BillsFamilysHouse.asm` object row `EVENT_MET_BILL`; flag set at new game by `engine/events/std_scripts.asm` (init block) | Ecruteak Pokecenter cutscene | `EcruteakPokcenter1FBillActivatesTimeCapsuleScript` does `clearevent EVENT_MET_BILL` | -| No Eevee | `maps/BillsFamilysHouse.asm:BillScript` `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .NoRoom` | a free party slot, and answer YES | `setevent EVENT_GOT_EEVEE` | -| No HM03 SURF | `maps/DanceTheater.asm:DanceTheaterSurfGuy` - five `checkevent EVENT_BEAT_KIMONO_GIRL_*` / `iffalse .KimonoGirlsUndefeated` | beat NAOKO, SAYO, ZUKI, KUNI, MIKI | `.GetSurf` -> `verbosegiveitem HM_SURF`, `setevent EVENT_GOT_HM03_SURF` | -| SURF unusable in the field | `engine/events/overworld.asm:340` (`SurfFunction.TrySurf`: `ld de, ENGINE_FOGBADGE` / `CheckBadge`) and `:490` (`TrySurfOW`: `CheckEngineFlag`) | FOGBADGE | `setflag ENGINE_FOGBADGE` in `EcruteakGymMortyScript` | -| Burned Tower 1F north half (Firebreather Dick coord event at (8,1), X SPEED at (7,5), hidden ETHER at (8,7)) and the centre pit at (10,7) that reaches the beasts | rock object at (4,3), `SPRITEMOVEDATA_SMASHABLE_ROCK` -> `BurnedTower1FRock` -> `jumpstd SmashRockScript` -> `AskRockSmashScript` -> `callasm HasRockSmash` (`CheckPartyMove ROCK_SMASH`) | a party mon that knows ROCK_SMASH. **No badge check.** | smash the rock | -| Burned Tower 1F east loop (BURN HEAL at (15,2), Firebreather Ned at (16,8), the (15,4)/(15,5) pits) | rock object at (16,13), same std script | ROCK_SMASH | smash the rock | -| Burned Tower B1F return to 1F | only `warp_event 7, 15` sits on a `LADDER` tile; warps 1-5 are on non-warp collisions so `CheckWarpCollision` (`engine/overworld/tile_events.asm:1`) rejects them | reach (7,15) | walk there | -| B1F boulder at (17,4) | `jumpstd StrengthBoulderScript` -> `AskStrengthScript` (`engine/events/overworld.asm:1001`) -> `callasm TryStrengthOW` | STRENGTH + PLAINBADGE | not obtainable in this section; nothing needed here is behind it | -| Ecruteak Gym invisible floor | 30 `warp_event` rows on `COLL_PIT` tiles, all pointing at warp 3 (4,14) | walk the exact zig-zag above | none - it is pure navigation | -| Rival battle in the Burned Tower | rival object masked by `EVENT_RIVAL_BURNED_TOWER`; `maps/GoldenrodUndergroundSwitchRoomEntrances.asm:125` sets it and `setmapscene BURNED_TOWER_1F, SCENE_BURNEDTOWER1F_FIREBREATHER_DICK` | do Burned Tower **before** the Goldenrod Underground rival fight | ordering only | -| Tin Tower | `maps/EcruteakTinTowerEntrance.asm` coord events (4,7)/(5,7) on `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` | FOGBADGE | `setmapscene ... NOOP` from `EcruteakGymMortyScript` | -| Bike shop "keep the bicycle" call | `engine/overworld/events.asm:1267 DoBikeStep` - needs `STATUSFLAGS2_BIKE_SHOP_CALL_F`, `wPlayerState == PLAYER_BIKE`, `GetMapPhoneService` == 0, and `wBikeStep` high byte >= `HIGH(1024)` | 1024 bike steps in phone-service territory | queues `SPECIALCALL_BIKESHOP`, then `engine/phone/scripts/bike_shop.asm` clears `ENGINE_BIKE_SHOP_CALL_ENABLED` | - ---- - -## 4. Bot checklist - -Preconditions carried in from section 06: Sudowoodo beaten/caught, bicycle owned -(`EVENT_GOT_BICYCLE`), `ENGINE_BIKE_SHOP_CALL_ENABLED` set. - -1. `ROUTE_37`, walk from the `ROUTE_36` south connection. Optional: fight - `TrainerTwinsAnnandanne1` at (6,12) (sight 1) - pre `EVENT_BEAT_TWINS_ANN_AND_ANNE` - clear, post set (this also disables the twin at (7,12)). -2. `ROUTE_37` (9,6): fight `TrainerPsychicGreg` (sight 3). Post - `EVENT_BEAT_PSYCHIC_GREG`. -3. `ROUTE_37`: talk to the fruit trees at (13,5), (16,5), (15,7) -> RED / BLU / - BLK apricorn. Precondition: bag space. No flag; resets daily. -4. `ROUTE_37` (4,2): ITEMFINDER-less hidden `ETHER`, flag - `EVENT_ROUTE_37_HIDDEN_ETHER`. -5. Optional, Sundays only: `ROUTE_37` (16,8) talk `SunnyScript` -> `MAGNET`. - Precondition `VAR_WEEKDAY == SUNDAY`; post `EVENT_GOT_MAGNET_FROM_SUNNY`. -6. Walk north into `ECRUTEAK_CITY`. Postcondition `ENGINE_FLYPOINT_ECRUTEAK` set - by `EcruteakCityFlypointCallback` (`MAPCALLBACK_NEWMAP`). -7. `ECRUTEAK_CITY` warp 6 at (23,27) -> `ECRUTEAK_POKECENTER_1F`. On map load the - scene script fires (scene 0). Do not press anything for the length of the - cutscene. Post: `EVENT_MET_BILL` **cleared**, `ENGINE_TIME_CAPSULE` set, scene - -> 1. Then heal at the nurse (3,1) and deposit apricorns/berries at the PC. -8. Ride back south: `ROUTE_37` -> `ROUTE_36` -> `NATIONAL_PARK` -> `ROUTE_35` -> - `GOLDENROD_CITY`. Somewhere in here the bike-shop call fires - (`DoBikeStep`, 1024 bike steps). -9. `GOLDENROD_CITY` warp 4 at (5,25) -> `BILLS_FAMILYS_HOUSE`. Precondition: - party count < 6. Talk to Bill at (2,3), answer YES. Post `EVENT_GOT_EEVEE`, - L20 EEVEE in party. -10. Optional: talk to the twin at (5,4) to register `PHONE_BILL`. -11. Ride back north to `ECRUTEAK_CITY`. -12. `ECRUTEAK_CITY` warp 8 at (23,21) -> `DANCE_THEATER`. -13. In `DANCE_THEATER`, walk adjacent and press A on each Kimono Girl - they all - have sight 0 and will not start a battle themselves: - (0,2) NAOKO / FLAREON, (2,1) SAYO / ESPEON, (6,2) ZUKI / UMBREON, - (9,1) KUNI / VAPOREON, (11,2) MIKI / JOLTEON. Each posts its own - `EVENT_BEAT_KIMONO_GIRL_*`. -14. `DANCE_THEATER` (7,10): talk to the gentleman. Precondition: all five flags - set and bag space for an HM. Post `EVENT_GOT_HM03_SURF` + `HM_SURF` in bag. - Teach SURF now; it will not work in the field until step 24. -15. Leave via warp 1/2 at (5,13)/(6,13). -16. `ECRUTEAK_CITY` warp 11 at (13,27) -> `ECRUTEAK_ITEMFINDER_HOUSE`. Talk to - (2,3), answer **YES** to the `yesorno`. Post `EVENT_GOT_ITEMFINDER`. -17. Optional: with ITEMFINDER, pick up the hidden `HYPER_POTION` at - `ECRUTEAK_CITY` (23,14). -18. Heal, stock Escape Ropes / Repels / Great Balls, then `ECRUTEAK_CITY` - warp 13 at (5,5) -> `BURNED_TOWER_1F`. -19. **On entry the rival scene fires immediately** (scene 0 -> - `sdefer BurnedTower1FRivalBattleScript`). Precondition: - `EVENT_RIVAL_BURNED_TOWER` **clear** (i.e. the Goldenrod Underground rival - fight has not happened). Battle is `RIVAL1` / - `RIVAL1_3_{CHIKORITA|CYNDAQUIL|TOTODILE}` chosen from - `EVENT_GOT_TOTODILE_FROM_ELM` / `EVENT_GOT_CHIKORITA_FROM_ELM`. - Post: rival disappears, scene -> `SCENE_BURNEDTOWER1F_FIREBREATHER_DICK`. -20. `BURNED_TOWER_1F`: walk (9,15) -> (9,14) -> row 13 west to (2,13), north up - column 2/3 to the (4,3) rock. Use ROCK_SMASH on it (field move or - `AskRockSmashScript` yes-prompt). This opens the whole north half. -21. Step on the (8,1) tile to trigger `FirebreatherDickFight` - (`FIREBREATHER`/`DICK`, L17 CHARMELEON). Post `EVENT_BEAT_FIREBREATHER_DICK`, - scene -> `SCENE_BURNEDTOWER1F_NOOP`. -22. Pick up `X_SPEED` at (7,5) and the hidden `ETHER` at (8,7). Then step onto - the pit at (10,7) -> `BURNED_TOWER_B1F` warp 3 at (10,8). -23. `BURNED_TOWER_B1F`: walk UP to (10,7), (10,6), LEFT to (9,6), UP to (9,5). - Precondition scene 0. This runs `ReleaseTheBeasts`. Post - `EVENT_RELEASED_THE_BEASTS`, scene -> 1, `special InitRoamMons` seeds the - three roamers. -24. Optional B1F loot: `NUGGET` hidden at (8,3). Then leave by the ladder at - (7,15) (the only exit). -25. Optional, back on 1F: smash the (16,13) rock to open the east loop - - `FIREBREATHER`/`NED` at (16,8) and the `BURN_HEAL` ball at (15,2). -26. Optional HP UP: from 1F step on any of the NW pits (5,4), (5,5), (5,6), - (4,6) -> B1F (3,3), grab `HP_UP` at (4,3), hop the ledges south and out via - the (7,15) ladder. -27. Leave `BURNED_TOWER_1F` via warp 1/2 at (9,15)/(10,15). Heal at the - Pokecenter, save. -28. `ECRUTEAK_CITY` warp 10 at (6,27) -> `ECRUTEAK_GYM`. Walk the exact path in - the ECRUTEAK_GYM section above. Fight, in order: - `SAGE`/`PING` at (3,13), `MEDIUM`/`GRACE` at (7,9), `SAGE`/`JEFFREY` at - (2,7), `MEDIUM`/`MARTHA` at (7,5). -29. Talk to Morty at (5,1) from (5,2). Battle `MORTY`/`MORTY1`. Post - `EVENT_BEAT_MORTY`, `ENGINE_FOGBADGE` set, - `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, ...NOOP`. -30. Keep talking to Morty (same script, `.FightDone` arm) with bag space to get - `TM_SHADOW_BALL`. Post `EVENT_GOT_TM30_SHADOW_BALL`. If the bag was full the - flag is not set and the give can be retried. -31. Field SURF is now legal (`TrySurfOW` `CheckEngineFlag ENGINE_FOGBADGE` - passes). The Ecruteak water encounter table (`ECRUTEAK_CITY`, 2%, POLIWAG / - POLIWHIRL) becomes reachable. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map/warp/bg/object tables decoded from the ROM (no hand-authored map data) | `src/import/RomExtractorGen2.lua` (`OBJECTTYPE_ITEMBALL`/`OBJECTTYPE_TRAINER` at :71, `readItemBall` at :2874-2969) | implemented | -| Warps triggered by pit/warp tile collision | `src/world/gen2/Permissions.lua:164` (cites `COLL_PIT` / `COLL_PIT_68` / `HI_NYBBLE_WARPS`, `CheckWarpCollision`) | implemented | -| Coord events gated on the map's scene id | `src/world/gen2/World.lua:5005 World:tryCoordScript` | implemented | -| Scene scripts run on map load; per-map scene ids saved | `src/world/gen2/World.lua:5021 World:trySceneScript`, `:1161 World:scene`, `:740/:746 setScene/setMapScene`, `src/core/gen2/Save.lua` | implemented | -| `sdefer` (the Bill and rival cutscenes) | `src/script/gen2/Vm.lua:92` - explicitly runs it inline instead of deferring past the map settle | partial (documented deviation; cutscene ordering may differ by a frame or two) | -| Object masking by event flag (`appear`/`disappear`, Bill, the beasts, the rival) | `src/script/gen2/Vm.lua` (`variablesprite` :353 etc.), `src/world/gen2/Npc.lua` | implemented | -| Trainer sight lines / walk-up battles | `src/world/gen2/Trainers.lua:98 Trainers.sees` | implemented | -| `loadtrainer` overriding the object's own trainer id (rival branch) | `src/script/gen2/Vm.lua:806` | implemented | -| `startbattle` result plumbing, incl. the `iftrue .next` arm this map uses | `src/script/gen2/Vm.lua:42`, `:602`, and the comment at `:825` which cites `maps/BurnedTower1F.asm` by name | implemented | -| `verbosegiveitem` (HM03, ITEMFINDER, TM30, MAGNET) | `src/script/gen2/Vm.lua:490` | implemented | -| `givepoke EEVEE, 20` | `src/script/gen2/Vm.lua:439` | implemented | -| Item balls | `src/import/RomExtractorGen2.lua:2874/2968` + object dispatch in `src/world/gen2/Events.lua` | implemented | -| Hidden items (`BGEVENT_ITEM` -> `hiddenitem`) and ITEMFINDER sweep | `src/world/gen2/HiddenItems.lua` | implemented | -| Fruit trees / apricorns | `src/script/gen2/Opcodes.lua:160` (`fruittree`), `src/core/gen2/Apricorns.lua` | implemented | -| Rock Smash field move (no badge) | `src/world/gen2/FieldMoves.lua` (badge table at :106-118, `ROCK_SMASH` has no badge entry) | implemented | -| SURF field move gated on FOGBADGE | `src/world/gen2/FieldMoves.lua:106` (`SURF = "FOG"`), `:477 SurfFunction`, `:663 trySurfOW` | implemented | -| Strength / PLAINBADGE (B1F boulder) | `src/world/gen2/FieldMoves.lua:108`, `:695 tryStrengthOW` | implemented | -| Badge flags and the L50 obedience/type-boost effects | `src/world/gen2/FieldMoves.lua:118` badge list; battle side in `src/battle/gen2/` | implemented | -| `special InitRoamMons` + roamer movement/encounters | `src/script/gen2/Specials.lua:1845`, `src/core/gen2/Roamers.lua`, driver `tests/drivers/gold_roamers.lua` | implemented | -| `special FadeOutMusic` / `RestartMapMusic` (both cutscenes) | `src/script/gen2/Specials.lua:1059`, `:1066` | implemented | -| `showemote`, `applymovement`, `turnobject`, `cry` choreography | `src/script/gen2/Vm.lua:961`, `src/script/gen2/Movement.lua` | implemented | -| Bike step counter / bike shop special call | `src/world/gen2/StepEvents.lua:48`, `:104-112`, `src/world/gen2/Bike.lua:4`, `src/world/gen2/World.lua:1992` (`specialphonecall`) | implemented | -| Time Capsule (`ENGINE_TIME_CAPSULE` is set, but the feature itself) | `src/script/gen2/Specials.lua:2215-2220` - `EnterTimeCapsule`, `TimeCapsule`, `CheckTimeCapsuleCompatibility` are all stubbed "link cable: no Time Capsule" | missing (flag set, menu inert) | -| Map callbacks (`MAPCALLBACK_NEWMAP` fly point, `MAPCALLBACK_OBJECTS` Sunny) | `src/world/gen2/World.lua` + driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| A driver that actually walks Ecruteak / Burned Tower / the gym | none - the `tests/drivers/gold_*.lua` set covers boot, walk, warp, battle, roamers, but no Ecruteak route | missing | -| Ledge hops (`HOP_*`) as used all over Burned Tower B1F | not found in `src/world/gen2/Permissions.lua` or `Player.lua` under those names | unverified - grep for `HOP_` returned nothing; needs a hands-on check before a bot relies on B1F traversal | - ---- - -## 6. Unresolved / verify by hand - -1. **"Head left and all the way around the spiral and you will now fight your - rival."** The asm disagrees. `BurnedTower1FRivalBattleScene` is scene 0 (the - wram default) and `sdefer`s the battle script, so it runs on map load; the - rival object sits at (9,12), three cells straight up from the (9,15) door, and - walks two steps DOWN to meet you. The battle happens as soon as you step - inside. -2. **"Equipping the TM Rock Smash will give you the HP Up at the northeastern - part of the floor."** Conflated. The northeast item on 1F is a `BURN_HEAL` - ball at (15,2) behind the (16,13) rock. The `HP_UP` is on B1F at (4,3), - reached by falling through the northwest pits - which the walkthrough gets - right later ("grab a HP Up by dropping down the northwest hole"). -3. **Rock Smash is not optional if you want the beasts.** Flood-filling - `maps/BurnedTower1F.blk` against `data/tilesets/tower_collision.asm` from the - (9,15) door shows that with both rocks intact you can only reach the four - northwest pits, the ladder, and the (4,14)/(5,14)/(14,14)/(15,14) pits - not - the centre pit at (10,7), which is the only route to B1F's (9,5) beast - trigger. This is a derived result, not a line of asm; worth confirming on - hardware/emulator before a bot depends on it. -4. **Ecruteak Gym (6,7)** carries a `warp_event` row but its block collision is - `FLOOR`, so the warp never fires. **(6,4)** is the mirror image: a `PIT` tile - with no `warp_event` row, so you can stand on it. Both are consistent with the - walkthrough's step counts working, and both look like one transposed row in - the source data. Derived from the `.blk`; verify visually. -5. **Morty's party order.** The walkthrough lists "L21 Gastly, L23 Haunter, L25 - Gengar, L23 Haunter". `MortyGroup` in `data/trainers/parties.asm:35` is - L21 GASTLY, **L21** HAUNTER, L25 GENGAR, L23 HAUNTER. The second Haunter is - level 21, and its EXP figure in the walkthrough (567) is therefore suspect. -6. **`DanceTheaterSurfGuy.GetSurf` has no `iffalse` after - `verbosegiveitem HM_SURF`.** Every other give in this section - (`ITEMFINDER`, `TM_SHADOW_BALL`, the Bill's-grandpa stones) checks the return. - If the bag genuinely cannot take an HM the flag would still be set. Whether - the bag can ever be full for the HM pocket in GS is worth checking before a - bot risks it - keep at least one free slot. -7. **Twins Ann & Anne share one flag.** Both objects use - `EVENT_BEAT_TWINS_ANN_AND_ANNE`; `EVENT_BEAT_TWINS_ANN_AND_ANNE2` - (`constants/event_flags.asm:610`) is defined but unreferenced by this map. - A bot that expects two battles on Route 37 will hang. -8. **Route 37's twins are declared `SPRITE_WEIRD_TREE`** with object consts - `ROUTE37_WEIRD_TREE1/2`, and `SPRITE_WEIRD_TREE` is `variablesprite`d to - `SPRITE_SUDOWOODO` by the new-game init block. Transcribed verbatim from - `maps/Route37.asm:237-238`; what actually renders on screen was not verified. -9. **Burned Tower 1F item ball flags are crossed** (`BURN_HEAL` ball carries - `EVENT_BURNED_TOWER_1F_X_SPEED` and vice versa). Verbatim from - `maps/BurnedTower1F.asm:311-312`. Functionally harmless, but do not infer the - item from the flag name. -10. **Ledge (`HOP_*`) traversal on B1F** could not be found in the port. The asm - side is `engine/overworld/player_movement.asm:350-392` (`.TryJump` / - `.ledge_table`, a two-cell jump when leaving a ledge tile in its direction). - Marked "unverified" in the port table above. -11. **`EcruteakGymActivateRockets`** branches on `VAR_BADGES == 6` / `== 7`. On a - linear run Fog is the fourth badge so neither arm fires; a badge-skipping bot - will trigger `GoldenrodRocketsScript` / `RadioTowerRocketsScript` out of - order. Not exercised in this section. -12. **Walkthrough EXP and money figures** were not checked against - `engine/battle/` at all; they are reproduced nowhere in this document for - that reason. diff --git a/docs/gold-walkthrough/asm-walk/section-08-olivine-lighthouse-and-cianwood-city-gym.md b/docs/gold-walkthrough/asm-walk/section-08-olivine-lighthouse-and-cianwood-city-gym.md deleted file mode 100644 index cfebb197..00000000 --- a/docs/gold-walkthrough/asm-walk/section-08-olivine-lighthouse-and-cianwood-city-gym.md +++ /dev/null @@ -1,1632 +0,0 @@ -# Section 08 - Olivine Lighthouse and Cianwood City Gym - -Source: `../section-08-olivine-lighthouse-and-cianwood-city-gym.txt` - -Maps covered: `MAP_ROUTE_38_ECRUTEAK_GATE`, `MAP_ROUTE_38`, `MAP_ROUTE_39`, -`MAP_ROUTE_39_BARN`, `MAP_ROUTE_39_FARMHOUSE`, `MAP_OLIVINE_CITY`, -`MAP_OLIVINE_POKECENTER_1F`, `MAP_OLIVINE_GOOD_ROD_HOUSE`, `MAP_OLIVINE_CAFE`, -`MAP_OLIVINE_MART`, `MAP_OLIVINE_LIGHTHOUSE_1F` .. `MAP_OLIVINE_LIGHTHOUSE_6F`, -`MAP_ROUTE_40`, `MAP_ROUTE_41`, `MAP_CIANWOOD_CITY`, `MAP_MANIAS_HOUSE`, -`MAP_CIANWOOD_PHARMACY`, `MAP_CIANWOOD_PHOTO_STUDIO`, -`MAP_CIANWOOD_POKECENTER_1F`, `MAP_CIANWOOD_LUGIA_SPEECH_HOUSE`, -`MAP_CIANWOOD_GYM` - -Badges / key milestones in this section: **Storm Badge** (`ENGINE_STORMBADGE`, -badge 5, from `CianwoodGymChuckScript`). Other milestones, in the order the -walkthrough hits them: `EVENT_GOT_GOOD_ROD`, `EVENT_GOT_HM04_STRENGTH` -(HM04 = the Cianwood Gym boulder puzzle and every later Strength rock), -`EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` (the flag that unlocks the Cianwood -pharmacist), `EVENT_GOT_SHUCKIE`, `EVENT_GOT_SECRETPOTION_FROM_PHARMACY`, -`EVENT_BEAT_CHUCK`, `EVENT_GOT_TM01_DYNAMICPUNCH`, `EVENT_GOT_HM02_FLY`, and -the two fly-point engine flags `ENGINE_FLYPOINT_OLIVINE` / -`ENGINE_FLYPOINT_CIANWOOD`. - -Coordinate note: every `warp_event` / `coord_event` / `bg_event` / -`object_event` x,y below is copied verbatim from the map asm. Those are 0-based -walk-grid cells (2 per map block, so a `W`-block-wide map runs x = 0..2W-1), -the same grid `src/world/gen2/Map.lua` calls a cell. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_38_ECRUTEAK_GATE` | `maps/Route38EcruteakGate.asm` | warp 3/4 at (9,4)/(9,5) from `ECRUTEAK_CITY` warps 14/15 | warp 1/2 at (0,4)/(0,5) | "Well, go through the building into Route 38" | -| 2 | `MAP_ROUTE_38` | `maps/Route38.asm` | warp 1/2 at (35,8)/(35,9) | west connection to `ROUTE_39` (`data/maps/attributes.asm:215`) | "You're heading west. There are trainers" - Toby, Harry, Dana, Valerie, Chad, plus the Berry tree | -| 3 | `MAP_ROUTE_39` | `maps/Route39.asm` | east connection from `ROUTE_38` | south connection to `OLIVINE_CITY` (`data/maps/attributes.asm:219`) | "Welcome to Route 39! It's the home of Moo Moo Farm" - Norman, Derek, Ruth, Eugene, Mint Berry | -| 3a | `MAP_ROUTE_39_BARN` | `maps/Route39Barn.asm` | Route 39 warp 1 at (1,3) | warp 1/2 at (3,7)/(4,7) | "you will first have to feed berries to their Miltank" | -| 3b | `MAP_ROUTE_39_FARMHOUSE` | `maps/Route39Farmhouse.asm` | Route 39 warp 2 at (5,3) | warp 1/2 at (2,7)/(3,7) | Moomoo Milk at 500 gold; TM13 SNORE once the Miltank is healed | -| 4 | `MAP_OLIVINE_CITY` | `maps/OlivineCity.asm` | north connection from `ROUTE_39` (`data/maps/attributes.asm:143`) | warp 9 at (29,27) | "As soon as you enter Olivine City and, when you are about to pass by the gym your rival will appear" - `coord_event` at (13,12)/(13,13) | -| 4a | `MAP_OLIVINE_GOOD_ROD_HOUSE` | `maps/OlivineGoodRodHouse.asm` | Olivine warp 6 at (13,15) | warp 1/2 at (2,7)/(3,7) | "Talk to the fisherman and he will give you a Good Rod" | -| 4b | `MAP_OLIVINE_POKECENTER_1F` | `maps/OlivinePokecenter1F.asm` | Olivine warp 1 at (13,21) | warp 1/2 at (3,7)/(4,7) | "Then heal at the Pokémon Center" | -| 4c | `MAP_OLIVINE_CAFE` | `maps/OlivineCafe.asm` | Olivine warp 7 at (7,21) | warp 1/2 at (2,7)/(3,7) | "talk to the sailor at the table for HM04 Strength!" | -| 5 | `MAP_OLIVINE_LIGHTHOUSE_1F` | `maps/OlivineLighthouse1F.asm` | Olivine warp 9 at (29,27) | warp 3 at (3,11) | "go up the Olivine City Lighthouse in the southeast part of town (not the dock)" | -| 6 | `MAP_OLIVINE_LIGHTHOUSE_2F` | `maps/OlivineLighthouse2F.asm` | warp 1 at (3,11) | warp 2 at (5,3) | Gentleman Alfred, Sailor Huey | -| 7 | `MAP_OLIVINE_LIGHTHOUSE_3F` | `maps/OlivineLighthouse3F.asm` | warp 2 at (5,3) | warp 1 at (13,3) | Bird Keeper Theo, Gentleman Preston, Ether | -| 8 | `MAP_OLIVINE_LIGHTHOUSE_4F` | `maps/OlivineLighthouse4F.asm` | warp 1 at (13,3) | warp 2 at (3,5) | Lass Connie, Sailor Kent, "hole next to Lass Connie" = warps 7/8 at (8,3)/(9,3) | -| 9 | `MAP_OLIVINE_LIGHTHOUSE_5F` | `maps/OlivineLighthouse5F.asm` | warp 2 at (3,5) | warp 1 at (9,15) | TM34 Swagger, Rare Candy, Great Ball, Bird Keeper Denis, Sailor Ernest | -| 10 | `MAP_OLIVINE_LIGHTHOUSE_6F` | `maps/OlivineLighthouse6F.asm` | warp 1 at (9,15) | warps 2/3 at (16,5)/(17,5) - the right-wall drop chain | "Talk to the person on that level, and it is Gym Leader Jasmine" | -| 11 | `MAP_OLIVINE_CITY` | `maps/OlivineCity.asm` | lighthouse 1F warps 1/2 at (10,17)/(11,17) | west connection to `ROUTE_40` (`data/maps/attributes.asm:145`) | heal, restock at the Mart (warp 8 at (19,17)), teach HM03 Surf | -| 12 | `MAP_ROUTE_40` | `maps/Route40.asm` | east connection from `OLIVINE_CITY` | south connection to `ROUTE_41` (`data/maps/attributes.asm:223`) | "activate Surf by clicking A at the water" - Simon, Elaine, Paula, Randall | -| 13 | `MAP_ROUTE_41` | `maps/Route41.asm` | north connection from `ROUTE_40` | west connection to `CIANWOOD_CITY` (`data/maps/attributes.asm:227`) | the nine open-sea swimmers; the four Whirl Islands warps are optional detours | -| 14 | `MAP_CIANWOOD_CITY` | `maps/CianwoodCity.asm` | east connection from `ROUTE_41` (`data/maps/attributes.asm:136`) | warp 2 at (8,43) | "You will reach land after that battle. Welcome to Cianwood City." | -| 14a | `MAP_CIANWOOD_POKECENTER_1F` | `maps/CianwoodPokecenter1F.asm` | Cianwood warp 3 at (23,43) | warp 1/2 at (3,7)/(4,7) | "Heal at the Pokémon Center and clear a space so you can acquire another Pokémon" | -| 14b | `MAP_MANIAS_HOUSE` | `maps/ManiasHouse.asm` | Cianwood warp 1 at (17,41) | warp 1/2 at (2,7)/(3,7) | "a PokeManiac will give you Level 15 Shuckie the Shuckle" | -| 14c | `MAP_CIANWOOD_PHARMACY` | `maps/CianwoodPharmacy.asm` | Cianwood warp 4 at (15,47) | warp 1/2 at (2,7)/(3,7) | "It's the Pharmacy. A guy inside the house, wearing sunglasses will give you some medicine" | -| 14d | `MAP_CIANWOOD_PHOTO_STUDIO` | `maps/CianwoodPhotoStudio.asm` | Cianwood warp 5 at (9,31) | warp 1/2 at (2,7)/(3,7) | "he will offer to take pictures of your Pokémon" (optional) | -| 15 | `MAP_CIANWOOD_GYM` | `maps/CianwoodGym.asm` | Cianwood warp 2 at (8,43) | warp 1/2 at (4,17)/(5,17) | "face Chuck, the fighting gym leader" -> Storm Badge + TM01 | -| 16 | `MAP_CIANWOOD_CITY` | `maps/CianwoodCity.asm` | gym warp 1/2 | (section ends) | "Talk to the lady outside the gym and she will give you HM02 Fly" | - -Spills into the next section: the walkthrough's last line, "You'll want to -return to Olivine now so that you can give the medicine to Jasmine and the -Ampharos", starts the Olivine Gym / `MAP_OLIVINE_GYM` beat. The Secretpotion -hand-off itself is `OlivineLighthouseJasmine` on `MAP_OLIVINE_LIGHTHOUSE_6F` -(documented below, because the flag it clears is what makes Jasmine appear in -the gym), but the gym fight belongs to section 09. Likewise the walkthrough's -"Items found in Olivine City: … TM23" is Jasmine's TM in `maps/OlivineGym.asm`, -not anything reachable on `MAP_OLIVINE_CITY`. - -## 2. Maps - -### MAP_ROUTE_38_ECRUTEAK_GATE - -- Script: `maps/Route38EcruteakGate.asm` -- Blocks: none (no `.blk`; gate maps share `TILESET_GATE` layout) -- Header (`data/maps/maps.asm:58`): `map Route38EcruteakGate, TILESET_GATE, GATE, LANDMARK_ROUTE_38, MUSIC_ROUTE_37, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:47`): `map_const ROUTE_38_ECRUTEAK_GATE, 5, 4` (5x4 blocks = 10x8 cells) -- Attributes (`data/maps/attributes.asm:480`): no connections -- Scene var: none (empty `def_scene_scripts`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ROUTE_38` | 1 | -| 2 | 0 | 5 | `ROUTE_38` | 2 | -| 3 | 9 | 4 | `ECRUTEAK_CITY` | 14 | -| 4 | 9 | 5 | `ECRUTEAK_CITY` | 15 | - -**Coord events** - none. - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE38ECRUTEAKGATE_OFFICER` | `SPRITE_OFFICER` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route38EcruteakGateOfficerScript` | -1 | - -The officer is `jumptextfaceplayer` only. Nothing here blocks passage. - ---- - -### MAP_ROUTE_38 - -- Script: `maps/Route38.asm` -- Blocks: `maps/Route38.blk` -- Header (`data/maps/maps.asm:61`): `map Route38, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_38, MUSIC_ROUTE_37, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:50`): `map_const ROUTE_38, 20, 9` (40x18 cells) -- Connections (`data/maps/attributes.asm:215`): west `Route39` / `ROUTE_39` offset 0; east `EcruteakCity` / `ECRUTEAK_CITY` offset -5 -- Scene var: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 35 | 8 | `ROUTE_38_ECRUTEAK_GATE` | 1 | -| 2 | 35 | 9 | `ROUTE_38_ECRUTEAK_GATE` | 2 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 33 | 7 | `BGEVENT_READ` | `Route38Sign` | -| 5 | 13 | `BGEVENT_READ` | `Route38TrainerTips` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE38_STANDING_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 4 | 1 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSchoolboyChad1` | -1 | -| `ROUTE38_LASS` | `SPRITE_LASS` | 15 | 3 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerLassDana1` | -1 | -| `ROUTE38_STANDING_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 15 | 10 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 2 | `TrainerBirdKeeperToby` | -1 | -| `ROUTE38_BEAUTY` | `SPRITE_BEAUTY` | 9 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 2 | `TrainerBeautyValerie` | -1 | -| `ROUTE38_SAILOR` | `SPRITE_SAILOR` | 25 | 5 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` | 2 | `TrainerSailorHarry` | -1 | -| `ROUTE38_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 12 | 10 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | 0 | `Route38FruitTree` | -1 | - -**Scripts of interest** - -- `Route38FruitTree`: single opcode `fruittree FRUITTREE_ROUTE_38`. - `data/items/fruit_trees.asm:6` maps that index to `BERRY`. This is the - walkthrough's "Go left and you'll get a Berry". -- `TrainerLassDana1.Script`: after the win it runs the standard phone-number - offer. `checkevent EVENT_DANA_READY_FOR_REMATCH` -> rematch arm; - `checkcellnum PHONE_LASS_DANA` -> already registered; otherwise it prints - `LassDanaMoomooMilkText`, sets `EVENT_DANA_ASKED_FOR_PHONE_NUMBER`, and calls - `askforphonenumber PHONE_LASS_DANA`. The rematch arm picks the party by - progress: `checkevent EVENT_CLEARED_RADIO_TOWER` -> `LASS, DANA3`, else - `checkflag ENGINE_FLYPOINT_CIANWOOD` -> `LASS, DANA2`, else `LASS, DANA1`. - Note that flag: the moment a bot walks into Cianwood in this section, Dana's - rematch upgrades to `DANA2`. -- `TrainerSchoolboyChad1.Script`: same shape, `PHONE_SCHOOLBOY_CHAD`, rematch - tier keyed on `EVENT_CLEARED_RADIO_TOWER` -> `CHAD3`, else - `checkflag ENGINE_FLYPOINT_MAHOGANY` -> `CHAD2`, else `CHAD1`. -- Toby, Valerie and Harry are plain `endifjustbattled / opentext / writetext / - waitbutton / closetext / end` after-battle scripts. No flags beyond their own - `EVENT_BEAT_*`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BIRD_KEEPER_TOBY` | `constants/event_flags.asm` (via `trainer` macro) | `TrainerBirdKeeperToby` | set on win; suppresses the sight-range trigger | -| `EVENT_BEAT_SAILOR_HARRY` | as above | `TrainerSailorHarry` | as above | -| `EVENT_BEAT_LASS_DANA` | as above | `TrainerLassDana1` | as above | -| `EVENT_BEAT_SCHOOLBOY_CHAD` | as above | `TrainerSchoolboyChad1` | as above | -| `EVENT_BEAT_BEAUTY_VALERIE` | as above | `TrainerBeautyValerie` | as above | -| `EVENT_DANA_ASKED_FOR_PHONE_NUMBER` | `constants/event_flags.asm` | `TrainerLassDana1.Script` | second-ask text branch | -| `EVENT_DANA_READY_FOR_REMATCH` | `constants/event_flags.asm` | set by the phone (`src/core/gen2/Phone.lua` mirrors `data/phone/`), cleared by `.DanaRematch` | rematch gate | -| `EVENT_CHAD_ASKED_FOR_PHONE_NUMBER` / `EVENT_CHAD_READY_FOR_REMATCH` | `constants/event_flags.asm` | `TrainerSchoolboyChad1.Script` | as above | -| `ENGINE_FLYPOINT_CIANWOOD` | `constants/engine_flags.asm:83` | set by `CianwoodCityFlypointCallback`, read by `.DanaRematch` | rematch tier switch | -| `ENGINE_FLYPOINT_MAHOGANY` | `constants/engine_flags.asm` | read by `.ChadRematch` | rematch tier switch | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `BERRY` | headbutt-free fruit tree, press A facing it | `Route38FruitTree` / `FRUITTREE_ROUTE_38` (`data/items/fruit_trees.asm:6`) | none - refreshes daily (`engine/events/fruit_trees.asm`) | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `TOBY` | `BIRD_KEEPER` | 5 | `BirdKeeperGroup` "TOBY", `TRAINERTYPE_NORMAL`: 15 DODUO, 16 DODUO, 17 DODUO | `TrainerBirdKeeperToby` | no | -| `HARRY` | `SAILOR` | 10 | `SailorGroup` "HARRY", `TRAINERTYPE_NORMAL`: 19 WOOPER | `TrainerSailorHarry` | no | -| `DANA1` | `LASS` | 10 | `LassGroup` "DANA", `TRAINERTYPE_MOVES`: 18 FLAAFFY (TACKLE, GROWL, THUNDERSHOCK, THUNDER_WAVE), 18 PSYDUCK (SCRATCH, TAIL_WHIP, DISABLE, CONFUSION) | `TrainerLassDana1` | `PHONE_LASS_DANA`; `DANA2` = LASS 14 (21 FLAAFFY / 21 PSYDUCK), `DANA3` = LASS 15 (29 PSYDUCK / 29 AMPHAROS) | -| `VALERIE` | `BEAUTY` | 16 | `BeautyGroup` "VALERIE", `TRAINERTYPE_MOVES`: 17 HOPPIP (SYNTHESIS, TAIL_WHIP, TACKLE, POISONPOWDER), 17 SKIPLOOM (SYNTHESIS, TAIL_WHIP, TACKLE, STUN_SPORE) | `TrainerBeautyValerie` | no | -| `CHAD1` | `SCHOOLBOY` | 10 | `SchoolboyGroup` "CHAD", `TRAINERTYPE_NORMAL`: 19 MR__MIME | `TrainerSchoolboyChad1` | `PHONE_SCHOOLBOY_CHAD`; `CHAD2` = SCHOOLBOY 17, `CHAD3` = SCHOOLBOY 18 | - -**Wild encounters** - -`data/wild/johto_grass.asm:2014`, `def_grass_wildmons ROUTE_38`, rates -`10 percent, 10 percent, 10 percent` (morn/day/nite). Gold build -(`IF DEF(_GOLD)`): - -- morn and day, in slot order: 16 RATTATA, 16 RATICATE, 16 MAGNEMITE, - 16 FARFETCH_D, 13 MILTANK, 13 TAUROS, 13 SNUBBULL -- nite: identical except slot 4 is 16 RATTATA instead of FARFETCH_D - -Silver swaps RATTATA for MEOWTH in slots 1 and (at night) 4. Headbutt: -`data/wild/treemon_maps.asm:19` `treemon_map ROUTE_38, TREEMON_SET_FOREST`. - ---- - -### MAP_ROUTE_39 - -- Script: `maps/Route39.asm` -- Blocks: `maps/Route39.blk` -- Header (`data/maps/maps.asm:62`): `map Route39, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_39, MUSIC_ROUTE_37, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:51`): `map_const ROUTE_39, 10, 18` (20x36 cells) -- Connections (`data/maps/attributes.asm:219`): south `OlivineCity` / `OLIVINE_CITY` offset -5; east `Route38` / `ROUTE_38` offset 0 -- Scene var: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 1 | 3 | `ROUTE_39_BARN` | 1 | -| 2 | 5 | 3 | `ROUTE_39_FARMHOUSE` | 1 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 31 | `BGEVENT_READ` | `Route39TrainerTips` | -| 9 | 5 | `BGEVENT_READ` | `MoomooFarmSign` | -| 15 | 7 | `BGEVENT_READ` | `Route39Sign` | -| 5 | 13 | `BGEVENT_ITEM` | `Route39HiddenNugget` -> `hiddenitem NUGGET, EVENT_ROUTE_39_HIDDEN_NUGGET` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE39_SAILOR` | `SPRITE_SAILOR` | 13 | 29 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerSailorEugene` | -1 | -| `ROUTE39_POKEFAN_M` | `SPRITE_POKEFAN_M` | 11 | 19 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerPokefanmDerek` | -1 | -| `ROUTE39_POKEFAN_F` | `SPRITE_POKEFAN_F` | 13 | 22 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerPokefanfRuth` | -1 | -| `ROUTE39_MILTANK1` | `SPRITE_TAUROS` | 3 | 12 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `Route39Miltank` | -1 | -| `ROUTE39_MILTANK2` | `SPRITE_TAUROS` | 6 | 11 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `Route39Miltank` | -1 | -| `ROUTE39_MILTANK3` | `SPRITE_TAUROS` | 4 | 15 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `Route39Miltank` | -1 | -| `ROUTE39_MILTANK4` | `SPRITE_TAUROS` | 8 | 13 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | 0 | `Route39Miltank` | -1 | -| `ROUTE39_PSYCHIC_NORMAN` | `SPRITE_YOUNGSTER` | 13 | 6 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPsychicNorman` | -1 | -| `ROUTE39_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 9 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | 0 | `Route39FruitTree` | -1 | - -**Scripts of interest** - -- `Route39FruitTree`: `fruittree FRUITTREE_ROUTE_39` -> - `data/items/fruit_trees.asm:18` = `MINT_BERRY`. This is the walkthrough's - "grab the Mint Berry". -- `TrainerPokefanmDerek.Script`: phone registration for - `PHONE_POKEFANM_DEREK`; rematch tier is `EVENT_BEAT_ELITE_FOUR` -> `DEREK3`, - else `checkflag ENGINE_FLYPOINT_LAKE_OF_RAGE` -> `DEREK2`, else `DEREK1`. -- Ruth, Eugene and Norman are plain after-battle scripts. - -**Flags and events** - trainer `EVENT_BEAT_*` bits plus -`EVENT_ROUTE_39_HIDDEN_NUGGET` (`constants/event_flags.asm:180`), -`EVENT_DEREK_ASKED_FOR_PHONE_NUMBER` / `EVENT_DEREK_READY_FOR_REMATCH`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MINT_BERRY` | fruit tree at (9,3) | `Route39FruitTree` | none (daily) | -| `NUGGET` | hidden, stand on/face (5,13) and press A | `Route39HiddenNugget` bg_event | `EVENT_ROUTE_39_HIDDEN_NUGGET` | - -The walkthrough does not mention the Nugget. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `NORMAN` | `PSYCHIC_T` | 6 | `PsychicGroup` "NORMAN", `TRAINERTYPE_MOVES`: 17 SLOWPOKE (TACKLE, GROWL, WATER_GUN, -), 20 SLOWPOKE (CURSE, BODY_SLAM, WATER_GUN, CONFUSION) | `TrainerPsychicNorman` | no | -| `DEREK1` | `POKEFANM` | 2 | `PokefanMGroup` "DEREK", `TRAINERTYPE_ITEM`: 17 PIKACHU holding BERRY | `TrainerPokefanmDerek` | `PHONE_POKEFANM_DEREK`; `DEREK2` = POKEFANM 10 (19 PIKACHU/BERRY), `DEREK3` = POKEFANM 11 (36 PIKACHU/BERRY) | -| `RUTH` | `POKEFANF` | 2 | `PokefanFGroup` "RUTH", `TRAINERTYPE_ITEM`: 17 PIKACHU holding BERRY | `TrainerPokefanfRuth` | no | -| `EUGENE` | `SAILOR` | 1 | `SailorGroup` "EUGENE", `TRAINERTYPE_NORMAL`: 17 POLIWHIRL, 17 RATICATE, 19 KRABBY | `TrainerSailorEugene` | no | - -**Wild encounters** - -`data/wild/johto_grass.asm:2069`, `def_grass_wildmons ROUTE_39`. Gold rates are -`2 percent, 2 percent, 10 percent` (Silver: 2/2/2 - Gold gets a much higher -night rate here). - -- morn and day: 16 RATTATA, 17 RATICATE, 16 MAGNEMITE, 16 FARFETCH_D, - 15 MILTANK, 15 TAUROS, 15 TAUROS -- nite: slot 4 becomes 16 RATTATA - -Headbutt: `data/wild/treemon_maps.asm:20` `TREEMON_SET_FOREST`. - ---- - -### MAP_ROUTE_39_BARN - -- Script: `maps/Route39Barn.asm` -- Blocks: `maps/Route39Barn.blk` -- Header (`data/maps/maps.asm:59`): `map Route39Barn, TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_ROUTE_39, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:48`): `map_const ROUTE_39_BARN, 4, 4` -- Connections (`data/maps/attributes.asm:481`): none - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ROUTE_39` | 1 | -| 2 | 4 | 7 | `ROUTE_39` | 1 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE39BARN_TWIN1` | `SPRITE_TWIN` | 2 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route39BarnTwin1Script` | -1 | -| `ROUTE39BARN_TWIN2` | `SPRITE_TWIN` | 4 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route39BarnTwin2Script` | -1 | -| `ROUTE39BARN_MOOMOO` | `SPRITE_TAUROS` | 3 | 3 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `MoomooScript` | -1 | - -**Scripts of interest** - -- `MoomooScript` (`pokegold.sym` 51:4c80). Control flow, verbatim from the - opcodes: - 1. `checkevent EVENT_HEALED_MOOMOO` -> `.HappyCow` (just a cry) if set. - 2. Otherwise print `MoomooWeakMooText`, `setval MILTANK`, - `special PlaySlowCry`, print `Route39BarnItsCryIsWeakText`. - 3. `checkevent EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO` -> `.GiveBerry`. - **This is the gate.** Until you have talked to - `PokefanM_DairyFarmer` in the farmhouse, the Miltank will not accept a - Berry at all. - 4. `.GiveBerry`: `yesorno`, `checkitem BERRY`, `takeitem BERRY`, - `readmem wMooMooBerries`, `addval 1`, `writemem wMooMooBerries`, then - `ifequal 3` / `ifequal 5` / `ifequal 7` for the three progress texts. - At **7** berries it plays `MUSIC_HEAL` and - `setevent EVENT_HEALED_MOOMOO`. -- `Route39BarnTwin1Script` / `Route39BarnTwin2Script`: text only, branch on - `EVENT_HEALED_MOOMOO`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO` | `constants/event_flags.asm:72` | set by `PokefanM_DairyFarmer`, read by `MoomooScript` | must be set before any Berry is accepted | -| `EVENT_HEALED_MOOMOO` | `constants/event_flags.asm:70` | set by `MoomooScript.SevenBerries` | unlocks Moomoo Milk sales and TM13 | -| `wMooMooBerries` | WRAM counter, written via `writemem` | `MoomooScript` | 0..7; 7 is the finish line | - ---- - -### MAP_ROUTE_39_FARMHOUSE - -- Script: `maps/Route39Farmhouse.asm` -- Blocks: none -- Header (`data/maps/maps.asm:60`): `map Route39Farmhouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_39, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:49`): `map_const ROUTE_39_FARMHOUSE, 4, 4` -- Connections (`data/maps/attributes.asm:482`): none - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_39` | 2 | -| 2 | 3 | 7 | `ROUTE_39` | 2 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `FarmhouseBookshelf` | -| 1 | 1 | `BGEVENT_READ` | `FarmhouseBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE39FARMHOUSE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 3 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokefanM_DairyFarmer` | -1 | -| `ROUTE39FARMHOUSE_POKEFAN_F` | `SPRITE_POKEFAN_F` | 5 | 4 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PokefanF_SnoreFarmer` | -1 | - -**Scripts of interest** - -- `PokefanM_DairyFarmer`: `checkevent EVENT_HEALED_MOOMOO` -> the Moomoo Milk - shop (`FarmerMScript_SellMilk`). Otherwise prints `FarmerMText_SickCow` and - `setevent EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO`. **Talk to him before going to - the barn.** -- `FarmerMScript_SellMilk`: `checkitem MOOMOO_MILK` -> refuse if you already - hold one; else `yesorno`, `checkmoney YOUR_MONEY, 500` - (`DEF ROUTE39FARMHOUSE_MILK_PRICE EQU 500` at line 1), - `giveitem MOOMOO_MILK`, `takemoney YOUR_MONEY, 500`. One at a time. -- `PokefanF_SnoreFarmer` (`pokegold.sym` 51:4ee3): - `checkevent EVENT_GOT_TM13_SNORE_FROM_MOOMOO_FARM` -> speech only; - `checkevent EVENT_HEALED_MOOMOO` -> `verbosegiveitem TM_SNORE` then - `setevent EVENT_GOT_TM13_SNORE_FROM_MOOMOO_FARM`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SNORE` (TM13) | talk to `ROUTE39FARMHOUSE_POKEFAN_F` after `EVENT_HEALED_MOOMOO` | `FarmerFScript_GiveSnore` | `EVENT_GOT_TM13_SNORE_FROM_MOOMOO_FARM` (`constants/event_flags.asm:71`) | -| `MOOMOO_MILK` | buy for 500 from `ROUTE39FARMHOUSE_POKEFAN_M` after `EVENT_HEALED_MOOMOO` | `FarmerMScript_SellMilk` | repeatable, but only one in the bag at a time | - ---- - -### MAP_OLIVINE_CITY - -- Script: `maps/OlivineCity.asm` -- Blocks: `maps/OlivineCity.blk` -- Header (`data/maps/maps.asm:63`): `map OlivineCity, TILESET_JOHTO, TOWN, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:52`): `map_const OLIVINE_CITY, 20, 18` (40x36 cells) -- Connections (`data/maps/attributes.asm:143`): north `Route39` / `ROUTE_39` offset 5; west `Route40` / `ROUTE_40` offset 9 -- Scene var: `wOlivineCitySceneID` (`data/maps/scenes.asm:40`). Scene ids come - from the map's own `def_scene_scripts` block: `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` - = 0 (the default on a new game), `SCENE_OLIVINECITY_NOOP` = 1. -- Spawn (`data/maps/spawn_points.asm:34`): `spawn OLIVINE_CITY, 13, 22`. - Fly point `LANDMARK_OLIVINE_CITY` -> `SPAWN_OLIVINE` (`data/maps/flypoints.asm:11`). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 21 | `OLIVINE_POKECENTER_1F` | 1 | -| 2 | 10 | 11 | `OLIVINE_GYM` | 1 | -| 3 | 25 | 11 | `OLIVINE_TIMS_HOUSE` | 1 | -| 4 | 0 | 0 | `OLIVINE_HOUSE_BETA` | 1 (marked `; inaccessible` in the asm) | -| 5 | 29 | 11 | `OLIVINE_PUNISHMENT_SPEECH_HOUSE` | 1 | -| 6 | 13 | 15 | `OLIVINE_GOOD_ROD_HOUSE` | 1 | -| 7 | 7 | 21 | `OLIVINE_CAFE` | 1 | -| 8 | 19 | 17 | `OLIVINE_MART` | 2 | -| 9 | 29 | 27 | `OLIVINE_LIGHTHOUSE_1F` | 1 | -| 10 | 19 | 27 | `OLIVINE_PORT_PASSAGE` | 1 | -| 11 | 20 | 27 | `OLIVINE_PORT_PASSAGE` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (0) | 13 | 12 | `OlivineCityRivalSceneTop` | rival cut-scene, north variant | -| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (0) | 13 | 13 | `OlivineCityRivalSceneBottom` | rival cut-scene, south variant | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 11 | `BGEVENT_READ` | `OlivineCitySign` | -| 20 | 24 | `BGEVENT_READ` | `OlivineCityPortSign` | -| 7 | 11 | `BGEVENT_READ` | `OlivineGymSign` | -| 30 | 28 | `BGEVENT_READ` | `OlivineLighthouseSign` | -| 14 | 21 | `BGEVENT_READ` | `OlivineCityPokecenterSign` | -| 20 | 17 | `BGEVENT_READ` | `OlivineCityMartSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINECITY_SAILOR1` | `SPRITE_SAILOR` | 26 | 27 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor1Script` | -1 | -| `OLIVINECITY_STANDING_YOUNGSTER` | `SPRITE_YOUNGSTER` | 20 | 13 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `OlivineCityStandingYoungsterScript` | -1 | -| `OLIVINECITY_SAILOR2` | `SPRITE_SAILOR` | 17 | 21 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor2Script` | -1 | -| `OLIVINECITY_OLIVINE_RIVAL` | `SPRITE_OLIVINE_RIVAL` | 10 | 11 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_OLIVINE_CITY` | - -The rival object sits on the gym door tile and is **masked** while -`EVENT_RIVAL_OLIVINE_CITY` is set (`CheckObjectFlag`, -`engine/overworld/map_objects_2.asm:32` - a set flag hides the object). -`InitializeEventsScript` sets it on a new game -(`engine/events/std_scripts.asm:520`), so the rival is invisible until the -scene's `appear` clears it. - -**Scripts of interest** - -- `OlivineCityFlypointCallback` (`callback MAPCALLBACK_NEWMAP`, - `pokegold.sym` 49:400f): a single `setflag ENGINE_FLYPOINT_OLIVINE`. - Walking into Olivine at all registers the fly point. -- `OlivineCityRivalSceneTop` (`pokegold.sym` 49:4013) and - `OlivineCityRivalSceneBottom` (49:404b). Both: - `turnobject PLAYER, LEFT` / `showemote EMOTE_SHOCK, PLAYER, 15` / - `special FadeOutMusic` / `pause 15` / `playsound SFX_ENTER_DOOR` / - `appear OLIVINECITY_OLIVINE_RIVAL` / - `applymovement OLIVINECITY_OLIVINE_RIVAL, <approach>` / - `playmusic MUSIC_RIVAL_ENCOUNTER` / `writetext OlivineCityRivalText` / - `applymovement PLAYER, <step aside>` / - `applymovement OLIVINECITY_OLIVINE_RIVAL, <leaves>` / - `setscene SCENE_OLIVINECITY_NOOP` / `disappear OLIVINECITY_OLIVINE_RIVAL` / - `special RestartMapMusic` / - `variablesprite SPRITE_OLIVINE_RIVAL, SPRITE_SWIMMER_GUY` / - `special LoadUsedSpritesGFX`. - **There is no battle.** The walkthrough is right: he only talks. - The `variablesprite` at the end is load-bearing - it re-points the - `SPRITE_OLIVINE_RIVAL` slot at `SPRITE_SWIMMER_GUY`, which is why the Route 40 - and Route 41 swimmers use `SPRITE_OLIVINE_RIVAL` in their `object_event` rows. - Movements: top variant approach `DOWN, RIGHT, RIGHT`, player steps - `DOWN` + `turn_head UP`, rival leaves `RIGHT x6, UP x6`. Bottom variant - approach `DOWN, DOWN, RIGHT, RIGHT`, player steps `UP` + `turn_head DOWN`, - rival leaves `RIGHT x6, UP x5`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_OLIVINE` | `constants/engine_flags.asm:85` | `OlivineCityFlypointCallback` | Fly destination unlocked on first entry | -| `EVENT_RIVAL_OLIVINE_CITY` | `constants/event_flags.asm:1125` | set by `InitializeEventsScript`, cleared by `appear`, re-set by `disappear` | hides the rival object outside the scene | -| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` = 0 | `maps/OlivineCity.asm:9` (`scene_script`) | `wOlivineCitySceneID` | default; arms the two coord events | -| `SCENE_OLIVINECITY_NOOP` = 1 | `maps/OlivineCity.asm:10` | `setscene` in both scene scripts | disarms them permanently | - -**Items** - none on the overworld map itself. The section's three Olivine items -come from interiors: `GOOD_ROD` (Good Rod House), `HM_STRENGTH` (Cafe), and -TM23 from Jasmine in the gym (section 09). - -**Trainers** - none. - -**Wild encounters** - `data/wild/johto_water.asm:239`, -`def_water_wildmons OLIVINE_CITY`, rate `6 percent`: 20 TENTACOOL, -15 TENTACOOL, 20 TENTACRUEL. Fishing group is `FISHGROUP_SHORE` -(`data/maps/maps.asm:63`); with the Good Rod that is -`data/wild/fish.asm` `.Shore_Good`: 35% MAGIKARP 20, 35% KRABBY 20, -20%+1 KRABBY 20, remainder `time_group 0` = CORSOLA 20 by day / STARYU 20 at -night (`data/wild/fish.asm:212`). No grass table exists for Olivine City. - ---- - -### MAP_OLIVINE_GOOD_ROD_HOUSE - -- Script: `maps/OlivineGoodRodHouse.asm` -- Header (`data/maps/maps.asm:55`): `map OlivineGoodRodHouse, TILESET_HOUSE, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:44`): `map_const OLIVINE_GOOD_ROD_HOUSE, 4, 4` -- Attributes (`data/maps/attributes.asm:477`): no connections - -**Warps**: 1 (2,7) -> `OLIVINE_CITY` 6; 2 (3,7) -> `OLIVINE_CITY` 6. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINEGOODRODHOUSE_FISHING_GURU` | `SPRITE_FISHING_GURU` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `GoodRodGuru` | -1 | - -**Scripts of interest** - -- `GoodRodGuru` (`pokegold.sym` 51:46fc): `checkevent EVENT_GOT_GOOD_ROD` -> - already-got text; otherwise `writetext OfferGoodRodText`, `yesorno`, - **iffalse -> refusal, no item**, then `verbosegiveitem GOOD_ROD` and - `setevent EVENT_GOT_GOOD_ROD`. A bot must answer YES. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `GOOD_ROD` | talk, answer YES | `GoodRodGuru` | `EVENT_GOT_GOOD_ROD` (`constants/event_flags.asm:32`) | - ---- - -### MAP_OLIVINE_CAFE - -- Script: `maps/OlivineCafe.asm` -- Blocks: `maps/OlivineCafe.blk` -- Header (`data/maps/maps.asm:56`): `map OlivineCafe, TILESET_GAME_CORNER, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:45`): `map_const OLIVINE_CAFE, 4, 4` -- Attributes (`data/maps/attributes.asm:478`): no connections - -**Warps**: 1 (2,7) -> `OLIVINE_CITY` 7; 2 (3,7) -> `OLIVINE_CITY` 7. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINECAFE_SAILOR` | `SPRITE_SAILOR` | 4 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `OlivineCafeStrengthSailorScript` | -1 | -| `OLIVINECAFE_FISHING_GURU` | `SPRITE_FISHING_GURU` | 1 | 5 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1) | `OBJECTTYPE_SCRIPT` | `OlivineCafeFishingGuruScript` | -1 | - -**Scripts of interest** - -- `OlivineCafeStrengthSailorScript` (`pokegold.sym` 51:48a2): - `checkevent EVENT_GOT_HM04_STRENGTH` -> already-got text; else - `writetext OlivineCafeStrengthSailorText`, `promptbutton`, - `verbosegiveitem HM_STRENGTH`, `setevent EVENT_GOT_HM04_STRENGTH`. - **No prerequisite at all** - no badge check, no event check. The sailor's own - text is the reminder that using Strength in the field needs Goldenrod's badge, - but the HM itself is unconditional. -- `OlivineCafeFishingGuruScript`: flavour text warning about the Route 41 - whirlpools. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HM_STRENGTH` (HM04) | talk to the sailor at (4,3) | `OlivineCafeStrengthSailorScript` | `EVENT_GOT_HM04_STRENGTH` (`constants/event_flags.asm:26`) | - ---- - -### MAP_OLIVINE_POKECENTER_1F - -- Script: `maps/OlivinePokecenter1F.asm` -- Header (`data/maps/maps.asm:50`): `map OlivinePokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:39`): `map_const OLIVINE_POKECENTER_1F, 5, 4` - -**Warps**: 1 (3,7) -> `OLIVINE_CITY` 1; 2 (4,7) -> `OLIVINE_CITY` 1; -3 (0,7) -> `POKECENTER_2F` 1. - -**Object events** - -| const | sprite | x | y | movement | type | script label | -|---|---|---|---|---|---|---| -| `OLIVINEPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivinePokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -| `OLIVINEPOKECENTER1F_FISHING_GURU` | `SPRITE_FISHING_GURU` | 8 | 4 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `jumpstd HappinessCheckScript` | -| `OLIVINEPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivinePokecenter1FFisherScript` | -| `OLIVINEPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 7 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivinePokecenter1FTeacherScript` | - -The happiness-check guru at (8,4) is worth knowing about: it is the standard -`HappinessCheckScript` and the closest one to Shuckie. - ---- - -### MAP_OLIVINE_MART - -- Script: `maps/OlivineMart.asm` -- Header (`data/maps/maps.asm:57`): `map OlivineMart, TILESET_MART, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:46`): `map_const OLIVINE_MART, 6, 4` -- **Warps**: 1 (2,7) -> `OLIVINE_CITY` 8; 2 (3,7) -> `OLIVINE_CITY` 8. -- `OlivineMartClerkScript`: `pokemart MARTTYPE_STANDARD, MART_OLIVINE`. - `MartOlivine` (`data/items/marts.asm:168`), 9 items: GREAT_BALL, - SUPER_POTION, HYPER_POTION, ANTIDOTE, PARLYZ_HEAL, AWAKENING, ICE_HEAL, - SUPER_REPEL, SURF_MAIL. This is the walkthrough's "restock on items, like - Great Balls". - ---- - -### MAP_OLIVINE_LIGHTHOUSE_1F .. 6F - -All six floors share: - -- Header (`data/maps/maps.asm:112-117`): - `map OlivineLighthouseNF, TILESET_LIGHTHOUSE, DUNGEON, LANDMARK_LIGHTHOUSE, MUSIC_LIGHTHOUSE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` - - **exception**: 6F uses `MUSIC_VIOLET_CITY`, not `MUSIC_LIGHTHOUSE` - (`data/maps/maps.asm:117`). -- Dimensions (`constants/map_constants.asm:99-104`): - `map_const OLIVINE_LIGHTHOUSE_NF, 10, 9` for all six (20x18 cells). -- Attributes (`data/maps/attributes.asm:431-436`): no connections. -- No scene vars, no callbacks, no coord events on any floor. - -Floor-to-floor structure, from the six `def_warp_events` blocks. There are two -vertical routes: the interior stairs (varying coordinates) and a right-wall -column at x = 16/17 that the walkthrough calls "the holes on the right side of -every floor". Each right-wall pair is mirrored at identical coordinates on the -floor it links to. - -| from | at (x,y) | to | dest warp | role | -|---|---|---|---|---| -| 1F w3 | 3,11 | 2F | 1 | stairs up | -| 1F w4/w5 | 16,13 / 17,13 | 2F | 3 / 4 | right-wall pair | -| 2F w2 | 5,3 | 3F | 2 | stairs up | -| 2F w3/w4 | 16,13 / 17,13 | 1F | 4 / 5 | right-wall drop to 1F | -| 2F w5/w6 | 16,11 / 17,11 | 3F | 4 / 5 | right-wall pair | -| 3F w1 | 13,3 | 4F | 1 | stairs up | -| 3F w3 | 9,5 | 4F | 4 | stairs | -| 3F w4/w5 | 16,11 / 17,11 | 2F | 5 / 6 | right-wall drop to 2F | -| 3F w6/w7 | 16,9 / 17,9 | 4F | 5 / 6 | right-wall pair | -| 3F w8/w9 | 8,3 / 9,3 | 4F | 7 / 8 | the pair the 4F "hole next to Connie" lands on | -| 4F w2 | 3,5 | 5F | 2 | stairs up | -| 4F w3 | 9,7 | 5F | 3 | stairs | -| 4F w7/w8 | 8,3 / 9,3 | 3F | 8 / 9 | **the hole beside Lass Connie** - drops next to Sailor Terrell and the Ether | -| 4F w9/w10 | 16,7 / 17,7 | 5F | 4 / 5 | right-wall pair | -| 5F w1 | 9,15 | 6F | 1 | stairs up to Jasmine | -| 5F w4/w5 | 16,7 / 17,7 | 4F | 9 / 10 | right-wall drop to 4F | -| 5F w6/w7 | 16,5 / 17,5 | 6F | 2 / 3 | right-wall pair | -| 6F w2/w3 | 16,5 / 17,5 | 5F | 6 / 7 | start of the descent chain out | - -Descent chain the walkthrough describes, in order: -6F (16,5) -> 5F (16,5); walk to 5F (16,7) -> 4F (16,7); walk to 4F (16,9) -> -3F (16,9); walk to 3F (16,11) -> 2F (16,11); walk to 2F (16,13) -> -1F (16,13); then 1F warp 1/2 at (10,17)/(11,17) back to Olivine. - -#### MAP_OLIVINE_LIGHTHOUSE_1F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 10 | 17 | `OLIVINE_CITY` | 9 | -| 2 | 11 | 17 | `OLIVINE_CITY` | 9 | -| 3 | 3 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 1 | -| 4 | 16 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 3 | -| 5 | 17 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 4 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE1F_SAILOR` | `SPRITE_SAILOR` | 8 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FSailorScript` | -1 | -| `OLIVINELIGHTHOUSE1F_POKEFAN_F` | `SPRITE_POKEFAN_F` | 16 | 9 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FPokefanFScript` | -1 | - -No trainers, no items, no gate on 1F. - -#### MAP_OLIVINE_LIGHTHOUSE_2F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 11 | `OLIVINE_LIGHTHOUSE_1F` | 3 | -| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 2 | -| 3 | 16 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 4 | -| 4 | 17 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 5 | -| 5 | 16 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 4 | -| 6 | 17 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 5 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE2F_SAILOR` | `SPRITE_SAILOR` | 9 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSailorHuey` | -1 | -| `OLIVINELIGHTHOUSE2F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 17 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerGentlemanAlfred` | -1 | - -**Scripts of interest** - -- `TrainerSailorHuey.Script`: phone registration for `PHONE_SAILOR_HUEY` - (`EVENT_HUEY_ASKED_FOR_PHONE_NUMBER`), rematch keyed on - `EVENT_BEAT_ELITE_FOUR` -> `HUEY3`, else `EVENT_CLEARED_RADIO_TOWER` -> - `HUEY2`, else `HUEY1`. The walkthrough's mention of a Liz phone call here is - ambient phone traffic, not this script. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `ALFRED` | `GENTLEMAN` | 5 | `GentlemanGroup` "ALFRED", `TRAINERTYPE_NORMAL`: 20 NOCTOWL | `TrainerGentlemanAlfred` | no | -| `HUEY1` | `SAILOR` | 2 | `SailorGroup` "HUEY", `TRAINERTYPE_NORMAL`: 18 POLIWAG, 18 POLIWHIRL | `TrainerSailorHuey` | `PHONE_SAILOR_HUEY`; `HUEY2` = SAILOR 11 (28 POLIWHIRL x2), `HUEY3` = SAILOR 12 (34 POLIWHIRL, 34 POLIWRATH) | - -#### MAP_OLIVINE_LIGHTHOUSE_3F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 1 | -| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_2F` | 2 | -| 3 | 9 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 4 | -| 4 | 16 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 5 | -| 5 | 17 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 6 | -| 6 | 16 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 5 | -| 7 | 17 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 6 | -| 8 | 8 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 7 | -| 9 | 9 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 8 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE3F_SAILOR` | `SPRITE_SAILOR` | 9 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 1 | `TrainerSailorTerrell` | -1 | -| `OLIVINELIGHTHOUSE3F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 13 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerGentlemanPreston` | -1 | -| `OLIVINELIGHTHOUSE3F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 3 | 9 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBirdKeeperTheo` | -1 | -| `OLIVINELIGHTHOUSE3F_POKE_BALL` | `SPRITE_POKE_BALL` | 8 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `OlivineLighthouse3FEther` (`itemball ETHER`) | `EVENT_OLIVINE_LIGHTHOUSE_3F_ETHER` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ETHER` | item ball at (8,2), reached by dropping through 4F (8,3)/(9,3) | `OlivineLighthouse3FEther` | `EVENT_OLIVINE_LIGHTHOUSE_3F_ETHER` (`constants/event_flags.asm:1029`) | - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `THEO` | `BIRD_KEEPER` | 4 | `TRAINERTYPE_NORMAL`: 17 PIDGEY, 15 PIDGEY, 19 PIDGEY, 15 PIDGEY, 15 PIDGEY | `TrainerBirdKeeperTheo` | -| `PRESTON` | `GENTLEMAN` | 1 | `TRAINERTYPE_NORMAL`: 18 GROWLITHE, 18 GROWLITHE | `TrainerGentlemanPreston` | -| `TERRELL` | `SAILOR` | 3 | `TRAINERTYPE_NORMAL`: 20 POLIWHIRL | `TrainerSailorTerrell` | - -Sight range 1 on Terrell is why the walkthrough hits him only after dropping -down beside him. - -#### MAP_OLIVINE_LIGHTHOUSE_4F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 1 | -| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 2 | -| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 3 | -| 4 | 9 | 5 | `OLIVINE_LIGHTHOUSE_3F` | 3 | -| 5 | 16 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 6 | -| 6 | 17 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 7 | -| 7 | 8 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 8 | -| 8 | 9 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 9 | -| 9 | 16 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 4 | -| 10 | 17 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 5 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE4F_SAILOR` | `SPRITE_SAILOR` | 7 | 14 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSailorKent` | -1 | -| `OLIVINELIGHTHOUSE4F_LASS` | `SPRITE_LASS` | 11 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 1 | `TrainerLassConnie` | -1 | - -Connie stands at (11,2); the drop-down warps 7/8 at (8,3)/(9,3) are the -"hole next to Lass Connie" in the walkthrough. - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `CONNIE1` | `LASS` | 5 | `TRAINERTYPE_NORMAL`: 21 MARILL | `TrainerLassConnie` | -| `KENT` | `SAILOR` | 4 | `TRAINERTYPE_MOVES`: 18 KRABBY (BUBBLE, LEER, VICEGRIP, HARDEN), 20 KRABBY (BUBBLEBEAM, LEER, VICEGRIP, HARDEN) | `TrainerSailorKent` | - -#### MAP_OLIVINE_LIGHTHOUSE_5F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_6F` | 1 | -| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 2 | -| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 3 | -| 4 | 16 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 9 | -| 5 | 17 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 10 | -| 6 | 16 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 2 | -| 7 | 17 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 13 | `BGEVENT_ITEM` | `OlivineLighthouse5FHiddenHyperPotion` -> `hiddenitem HYPER_POTION, EVENT_OLIVINE_LIGHTHOUSE_5F_HIDDEN_HYPER_POTION` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE5F_SAILOR` | `SPRITE_SAILOR` | 8 | 11 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSailorErnest` | -1 | -| `OLIVINELIGHTHOUSE5F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 8 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerBirdKeeperDenis` | -1 | -| `OLIVINELIGHTHOUSE5F_POKE_BALL1` | `SPRITE_POKE_BALL` | 15 | 12 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `OlivineLighthouse5FRareCandy` (`itemball RARE_CANDY`) | `EVENT_OLIVINE_LIGHTHOUSE_5F_RARE_CANDY` | -| `OLIVINELIGHTHOUSE5F_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 15 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `OlivineLighthouse5FGreatBall` (`itemball GREAT_BALL`) | `EVENT_OLIVINE_LIGHTHOUSE_5F_GREAT_BALL` | -| `OLIVINELIGHTHOUSE5F_POKE_BALL3` | `SPRITE_POKE_BALL` | 2 | 13 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `OlivineLighthouse5FTMSwagger` (`itemball TM_SWAGGER`, `pokegold.sym` 44:6aea) | `EVENT_OLIVINE_LIGHTHOUSE_5F_TM_SWAGGER` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SWAGGER` (TM34) | item ball at (2,13) | `OlivineLighthouse5FTMSwagger` | `EVENT_OLIVINE_LIGHTHOUSE_5F_TM_SWAGGER` (`constants/event_flags.asm:1032`) | -| `RARE_CANDY` | item ball at (15,12) | `OlivineLighthouse5FRareCandy` | `EVENT_OLIVINE_LIGHTHOUSE_5F_RARE_CANDY` (:1030) | -| `GREAT_BALL` | item ball at (6,15) | `OlivineLighthouse5FGreatBall` | `EVENT_OLIVINE_LIGHTHOUSE_5F_GREAT_BALL` (:1031) | -| `HYPER_POTION` | hidden at (3,13) | `OlivineLighthouse5FHiddenHyperPotion` | `EVENT_OLIVINE_LIGHTHOUSE_5F_HIDDEN_HYPER_POTION` (:143) | - -The hidden Hyper Potion is one cell east of the TM34 ball and is not mentioned -in the walkthrough. - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `DENIS` | `BIRD_KEEPER` | 6 | `TRAINERTYPE_NORMAL`: 18 SPEAROW, 20 FEAROW, 18 SPEAROW | `TrainerBirdKeeperDenis` | -| `ERNEST` | `SAILOR` | 5 | `TRAINERTYPE_NORMAL`: 18 MACHOP, 18 MACHOP, 18 POLIWHIRL | `TrainerSailorErnest` | - -Note the party order: the asm has Denis as SPEAROW / FEAROW / SPEAROW; the -walkthrough lists Spearow, Spearow, Fearow. - -#### MAP_OLIVINE_LIGHTHOUSE_6F - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_5F` | 1 | -| 2 | 16 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 6 | -| 3 | 17 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 7 | - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE6F_JASMINE` | `SPRITE_JASMINE` | 8 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseJasmine` | `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` | -| `OLIVINELIGHTHOUSE6F_MONSTER` | `SPRITE_MONSTER` | 9 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseAmphy` | -1 | -| `OLIVINELIGHTHOUSE6F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse6FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_OLIVINE_LIGHTHOUSE_6F_SUPER_POTION` | - -`EVENT_OLIVINE_LIGHTHOUSE_JASMINE` is **not** set by -`InitializeEventsScript`, so Jasmine is visible from the start; the script's -own `disappear` sets it once she leaves. - -**Scripts of interest** - -- `OlivineLighthouseJasmine` (`pokegold.sym` 44:6ccd). Three arms: - 1. `checkitem SECRETPOTION` -> `.BroughtSecretpotion` (the cure scene). - 2. Otherwise `checkevent EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` -> - `.ExplainedSickness` (short reminder text). - 3. First visit: `writetext JasmineCianwoodPharmacyText`, `promptbutton`, - **`setevent EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS`**, then the reminder. - This flag is the whole point of the lighthouse climb: it is what - `CianwoodPharmacist` checks before it will hand over the Secretpotion. - 4. `.BroughtSecretpotion`: `yesorno` (NO -> `.Refused`, nothing lost), then - `takeitem SECRETPOTION`, the healing cut-scene (`playmusic MUSIC_HEAL`, - `cry AMPHAROS`, `special FadeOutToWhite` / `FadeInFromWhite`), then - **`setevent EVENT_JASMINE_RETURNED_TO_GYM`** and - **`clearevent EVENT_OLIVINE_GYM_JASMINE`** (the latter un-masks her object - in `maps/OlivineGym.asm`; `InitializeEventsScript` sets it at - `engine/events/std_scripts.asm:511`). Finally it branches on - `readvar VAR_FACING` (DOWN / RIGHT / other) to pick one of three exit - movement scripts and `disappear OLIVINELIGHTHOUSE6F_JASMINE`. -- `OlivineLighthouseAmphy`: `checkevent EVENT_JASMINE_RETURNED_TO_GYM` picks - the weak cry (`special PlaySlowCry`) or the healthy one. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` | `constants/event_flags.asm:64` | set by `OlivineLighthouseJasmine`, read by `CianwoodPharmacist` | **hard gate** on the Secretpotion | -| `EVENT_JASMINE_RETURNED_TO_GYM` | `constants/event_flags.asm:41` | set after the cure | gates the Olivine Gym fight (section 09) | -| `EVENT_OLIVINE_GYM_JASMINE` | `constants/event_flags.asm:1141` | set by `InitializeEventsScript`, cleared here | while set, Jasmine's gym object is masked | -| `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` | `constants/event_flags.asm:1140` | set by `disappear` at the end | removes her from 6F afterwards | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `SUPER_POTION` | item ball at (3,4), west of Jasmine | `OlivineLighthouse6FSuperPotion` | `EVENT_OLIVINE_LIGHTHOUSE_6F_SUPER_POTION` (`constants/event_flags.asm:1033`) | - ---- - -### MAP_ROUTE_40 - -- Script: `maps/Route40.asm` -- Blocks: `maps/Route40.blk` -- Header (`data/maps/maps.asm:442`): `map Route40, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_40, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:409`): `map_const ROUTE_40, 10, 18` (20x36 cells) -- Connections (`data/maps/attributes.asm:223`): south `Route41` / `ROUTE_41` offset -15; east `OlivineCity` / `OLIVINE_CITY` offset -9 -- Scene var: none -- Callback: `callback MAPCALLBACK_OBJECTS, Route40MonicaCallback` - -**Warps** - **none**. Route 40 is reached and left purely by map connections. - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 8 | `BGEVENT_READ` | `Route40Sign` | -| 11 | 7 | `BGEVENT_ITEM` | `Route40HiddenHyperPotion` -> `hiddenitem HYPER_POTION, EVENT_ROUTE_40_HIDDEN_HYPER_POTION` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE40_OLIVINE_RIVAL1` | `SPRITE_OLIVINE_RIVAL` | 14 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSwimmermSimon` | -1 | -| `ROUTE40_OLIVINE_RIVAL2` | `SPRITE_OLIVINE_RIVAL` | 18 | 30 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerSwimmermRandall` | -1 | -| `ROUTE40_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 3 | 19 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSwimmerfElaine` | -1 | -| `ROUTE40_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 10 | 25 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfPaula` | -1 | -| `ROUTE40_ROCK1` | `SPRITE_ROCK` | 12 | 8 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | 0 | `Route40Rock` (`jumpstd SmashRockScript`) | -1 | -| `ROUTE40_ROCK2` | `SPRITE_ROCK` | 11 | 7 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | 0 | `Route40Rock` | -1 | -| `ROUTE40_ROCK3` | `SPRITE_ROCK` | 13 | 6 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | 0 | `Route40Rock` | -1 | -| `ROUTE40_LASS` | `SPRITE_LASS` | 13 | 10 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `Route40Lass1Script` | -1 | -| `ROUTE40_MONICA` | `SPRITE_BEAUTY` | 10 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | 0 | `MonicaScript` | `EVENT_ROUTE_40_MONICA_OF_MONDAY` | - -Note `ROCK2` and the hidden Hyper Potion share the cell (11,7): smash the rock, -then dig up the item. - -**Scripts of interest** - -- `Route40MonicaCallback` (`pokegold.sym` 4c:4ecf): - `readvar VAR_WEEKDAY / ifequal MONDAY, .MonicaAppears`; otherwise - `disappear ROUTE40_MONICA`. Purely day-of-week. -- `MonicaScript`: `checkevent EVENT_GOT_SHARP_BEAK_FROM_MONICA` -> chat; - else `readvar VAR_WEEKDAY / ifnotequal MONDAY` -> chat; else - `setevent EVENT_MET_MONICA_OF_MONDAY`, `verbosegiveitem SHARP_BEAK`, - `setevent EVENT_GOT_SHARP_BEAK_FROM_MONICA`. -- `Route40Rock`: `jumpstd SmashRockScript` -> - `farsjump AskRockSmashScript` (`engine/events/std_scripts.asm:199`). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HYPER_POTION` | hidden at (11,7) | `Route40HiddenHyperPotion` bg_event | `EVENT_ROUTE_40_HIDDEN_HYPER_POTION` (`constants/event_flags.asm:181`) | -| `SHARP_BEAK` | talk to Monica at (10,6), Mondays only | `MonicaScript` | `EVENT_GOT_SHARP_BEAK_FROM_MONICA` (:120) | - -Neither is in the walkthrough. - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `SIMON` | `SWIMMERM` | 2 | `TRAINERTYPE_NORMAL`: 20 TENTACOOL, 20 TENTACOOL | `TrainerSwimmermSimon` | -| `RANDALL` | `SWIMMERM` | 3 | `TRAINERTYPE_NORMAL`: 18 SHELLDER, 20 WARTORTLE, 18 SHELLDER | `TrainerSwimmermRandall` | -| `ELAINE` | `SWIMMERF` | 1 | `TRAINERTYPE_NORMAL`: 21 STARYU | `TrainerSwimmerfElaine` | -| `PAULA` | `SWIMMERF` | 2 | `TRAINERTYPE_NORMAL`: 19 STARYU, 19 SHELLDER | `TrainerSwimmerfPaula` | - -**Wild encounters** - -- Water (`data/wild/johto_water.asm:163`), `def_water_wildmons ROUTE_40`, - rate `6 percent`: 20 TENTACOOL, 15 TENTACOOL, 20 TENTACRUEL. Same in Gold and - Silver. -- Fishing group `FISHGROUP_SHORE` (`data/maps/maps.asm:442`). -- Rock smash: `data/wild/treemon_maps.asm:45` - `treemon_map ROUTE_40, TREEMON_SET_ROCK` (in the `RockMonMaps` table). - `TreeMonSet_Rock` (`data/wild/treemons.asm:91`): 90 KRABBY 15, 10 SHUCKLE 15. - **This is where the walkthrough's Shuckle for this stretch actually comes - from**, not from a water slot. -- Headbutt: `data/wild/treemon_maps.asm:21` `TREEMON_SET_NONE`. - ---- - -### MAP_ROUTE_41 - -- Script: `maps/Route41.asm` -- Blocks: `maps/Route41.blk` -- Header (`data/maps/maps.asm:443`): `map Route41, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_41, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions (`constants/map_constants.asm:410`): `map_const ROUTE_41, 25, 27` (50x54 cells) -- Connections (`data/maps/attributes.asm:227`): north `Route40` / `ROUTE_40` offset 15; west `CianwoodCity` / `CIANWOOD_CITY` offset 0 -- Scene var: none, no callbacks - -**Warps** (`def_warp_events`) - all four are the Whirl Islands, optional in -this section: - -| 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 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 35 | `BGEVENT_ITEM` | `Route41HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_41_HIDDEN_MAX_ETHER` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE41_OLIVINE_RIVAL1` | `SPRITE_OLIVINE_RIVAL` | 32 | 6 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermCharlie` | -1 | -| `ROUTE41_OLIVINE_RIVAL2` | `SPRITE_OLIVINE_RIVAL` | 46 | 8 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermGeorge` | -1 | -| `ROUTE41_OLIVINE_RIVAL3` | `SPRITE_OLIVINE_RIVAL` | 20 | 26 | `SPRITEMOVEDATA_SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermBerke` | -1 | -| `ROUTE41_OLIVINE_RIVAL4` | `SPRITE_OLIVINE_RIVAL` | 32 | 30 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermKirk` | -1 | -| `ROUTE41_OLIVINE_RIVAL5` | `SPRITE_OLIVINE_RIVAL` | 19 | 46 | `SPRITEMOVEDATA_SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermMathew` | -1 | -| `ROUTE41_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 17 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfKaylee` | -1 | -| `ROUTE41_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 23 | 19 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfSusie` | -1 | -| `ROUTE41_SWIMMER_GIRL3` | `SPRITE_SWIMMER_GIRL` | 27 | 34 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfDenise` | -1 | -| `ROUTE41_SWIMMER_GIRL4` | `SPRITE_SWIMMER_GIRL` | 44 | 28 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSwimmerfKara` | -1 | -| `ROUTE41_SWIMMER_GIRL5` | `SPRITE_SWIMMER_GIRL` | 9 | 50 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerSwimmerfWendy` | -1 | - -All ten are plain `endifjustbattled` after-battle scripts - no phone numbers, -no rematch tiers on this route. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_ETHER` | hidden at (9,35) | `Route41HiddenMaxEther` | `EVENT_ROUTE_41_HIDDEN_MAX_ETHER` (`constants/event_flags.asm:182`) | - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `CHARLIE` | `SWIMMERM` | 4 | 21 SHELLDER, 19 TENTACOOL, 19 TENTACRUEL | `TrainerSwimmermCharlie` | -| `GEORGE` | `SWIMMERM` | 5 | 16 TENTACOOL, 17 TENTACOOL, 16 TENTACOOL, 19 STARYU, 17 TENTACOOL, 19 REMORAID | `TrainerSwimmermGeorge` | -| `BERKE` | `SWIMMERM` | 6 | 23 QWILFISH | `TrainerSwimmermBerke` | -| `KIRK` | `SWIMMERM` | 7 | 20 GYARADOS, 20 GYARADOS | `TrainerSwimmermKirk` | -| `MATHEW` | `SWIMMERM` | 8 | 23 KRABBY | `TrainerSwimmermMathew` | -| `KAYLEE` | `SWIMMERF` | 3 | 18 GOLDEEN, 20 GOLDEEN, 20 SEAKING | `TrainerSwimmerfKaylee` | -| `SUSIE` | `SWIMMERF` | 4 | `TRAINERTYPE_MOVES`: 20 PSYDUCK (SCRATCH, TAIL_WHIP, DISABLE, CONFUSION), 22 GOLDEEN (PECK, TAIL_WHIP, SUPERSONIC, HORN_ATTACK) | `TrainerSwimmerfSusie` | -| `DENISE` | `SWIMMERF` | 5 | 22 SEEL | `TrainerSwimmerfDenise` | -| `KARA` | `SWIMMERF` | 6 | 20 STARYU, 20 STARMIE | `TrainerSwimmerfKara` | -| `WENDY` | `SWIMMERF` | 7 | `TRAINERTYPE_MOVES`: 21 HORSEA (BUBBLE, SMOKESCREEN, LEER, WATER_GUN), 21 HORSEA (DRAGON_RAGE, SMOKESCREEN, LEER, WATER_GUN) | `TrainerSwimmerfWendy` | - -All except Kaylee are named by the walkthrough. - -**Wild encounters** - -- Water (`data/wild/johto_water.asm:170`), `def_water_wildmons ROUTE_41`, - rate `6 percent`. Gold: 20 TENTACOOL, 20 TENTACRUEL, **20 MANTINE**. - Silver: 20 TENTACOOL, 20 TENTACRUEL, 15 TENTACOOL. This is the one place in - the section where the version split matters for the Pokedex. -- Fishing group `FISHGROUP_OCEAN` (`data/maps/maps.asm:443`). Good Rod - (`.Ocean_Good`, `data/wild/fish.asm:45`): 35% MAGIKARP 20, 35% TENTACOOL 20, - 20%+1 CHINCHOU 20, remainder `time_group 2` = SHELLDER 20 day or night. -- Headbutt: `TREEMON_SET_NONE`. Rock smash: Route 41 is **not** in - `RockMonMaps`, and `Route41Rock` in the map asm is marked `; unreferenced`. - ---- - -### MAP_CIANWOOD_CITY - -- Script: `maps/CianwoodCity.asm` -- Blocks: `maps/CianwoodCity.blk` -- Header (`data/maps/maps.asm:444`): `map CianwoodCity, TILESET_JOHTO, TOWN, LANDMARK_CIANWOOD_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:411`): `map_const CIANWOOD_CITY, 15, 27` (30x54 cells) -- Connections (`data/maps/attributes.asm:136`): east `Route41` / `ROUTE_41` offset 0 -- Scene var: none -- Callback: `callback MAPCALLBACK_NEWMAP, CianwoodCityFlypointCallback` -- Spawn (`data/maps/spawn_points.asm:32`): `spawn CIANWOOD_CITY, 23, 44`. - Fly point `LANDMARK_CIANWOOD_CITY` -> `SPAWN_CIANWOOD` (`data/maps/flypoints.asm:12`). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 41 | `MANIAS_HOUSE` | 1 | -| 2 | 8 | 43 | `CIANWOOD_GYM` | 1 | -| 3 | 23 | 43 | `CIANWOOD_POKECENTER_1F` | 1 | -| 4 | 15 | 47 | `CIANWOOD_PHARMACY` | 1 | -| 5 | 9 | 31 | `CIANWOOD_PHOTO_STUDIO` | 1 | -| 6 | 15 | 37 | `CIANWOOD_LUGIA_SPEECH_HOUSE` | 1 | - -The walkthrough's "To the left of the Pokémon Center is a house where a -PokeManiac..." is Mania's House at (17,41), left of the Pokecenter at (23,43). -"the building below that. It's the Pharmacy" is warp 4 at (15,47). - -**Coord events** - none. Nothing scripted fires on entry. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 20 | 34 | `BGEVENT_READ` | `CianwoodCitySign` | -| 7 | 45 | `BGEVENT_READ` | `CianwoodGymSign` | -| 24 | 43 | `BGEVENT_READ` | `CianwoodPokecenterSign` | -| 19 | 47 | `BGEVENT_READ` | `CianwoodPharmacySign` | -| 8 | 32 | `BGEVENT_READ` | `CianwoodPhotoStudioSign` | -| 8 | 16 | `BGEVENT_ITEM` | `CianwoodCityHiddenRevive` -> `hiddenitem REVIVE, EVENT_CIANWOOD_CITY_HIDDEN_REVIVE` | -| 5 | 29 | `BGEVENT_ITEM` | `CianwoodCityHiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_CIANWOOD_CITY_HIDDEN_MAX_ETHER` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CIANWOODCITY_STANDING_YOUNGSTER` | `SPRITE_YOUNGSTER` | 21 | 37 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `CianwoodCityYoungster` | -1 | -| `CIANWOODCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 31 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `CianwoodCityPokefanM` | -1 | -| `CIANWOODCITY_LASS` | `SPRITE_LASS` | 14 | 42 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `CianwoodCityLass` | -1 | -| `CIANWOODCITY_ROCK1` | `SPRITE_ROCK` | 8 | 16 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_ROCK2` | `SPRITE_ROCK` | 11 | 15 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_ROCK3` | `SPRITE_ROCK` | 6 | 24 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_ROCK4` | `SPRITE_ROCK` | 5 | 29 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_ROCK5` | `SPRITE_ROCK` | 10 | 27 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_ROCK6` | `SPRITE_ROCK` | 7 | 17 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `CianwoodCityRock` | -1 | -| `CIANWOODCITY_POKEFAN_F` | `SPRITE_POKEFAN_F` | 10 | 46 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `CianwoodCityChucksWife` | -1 | - -Note both hidden items sit on rock cells: the Revive shares (8,16) with ROCK1, -the Max Ether shares (5,29) with ROCK4. - -**Scripts of interest** - -- `CianwoodCityFlypointCallback` (`pokegold.sym` 48:58df): - `setflag ENGINE_FLYPOINT_CIANWOOD`. -- `CianwoodCityChucksWife` (`pokegold.sym` 48:58e3): - `checkevent EVENT_GOT_HM02_FLY` -> chat; else print - `ChucksWifeEasierToFlyText`, then `checkevent EVENT_BEAT_CHUCK`: - - not set -> `ChucksWifeBeatChuckText`, end. **She will not hand over Fly - before Chuck is beaten.** - - set -> `verbosegiveitem HM_FLY`, `setevent EVENT_GOT_HM02_FLY`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_CIANWOOD` | `constants/engine_flags.asm:83` | `CianwoodCityFlypointCallback` | set on arrival; also bumps Route 38 Dana's rematch tier | -| `EVENT_GOT_HM02_FLY` | `constants/event_flags.asm:24` | `CianwoodCityChucksWife` | one-time | -| `EVENT_BEAT_CHUCK` | `constants/event_flags.asm:711` | set by `CianwoodGymChuckScript` | precondition for HM02 | -| `EVENT_CIANWOOD_CITY_HIDDEN_REVIVE` | `constants/event_flags.asm:188` | `CianwoodCityHiddenRevive` | one-time | -| `EVENT_CIANWOOD_CITY_HIDDEN_MAX_ETHER` | `constants/event_flags.asm:189` | `CianwoodCityHiddenMaxEther` | one-time | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HM_FLY` (HM02) | talk to `CIANWOODCITY_POKEFAN_F` at (10,46) after `EVENT_BEAT_CHUCK` | `CianwoodCityChucksWife` | `EVENT_GOT_HM02_FLY` | -| `REVIVE` | hidden at (8,16) | `CianwoodCityHiddenRevive` | `EVENT_CIANWOOD_CITY_HIDDEN_REVIVE` | -| `MAX_ETHER` | hidden at (5,29) | `CianwoodCityHiddenMaxEther` | `EVENT_CIANWOOD_CITY_HIDDEN_MAX_ETHER` | - -**Wild encounters** - -- Water (`data/wild/johto_water.asm:232`), `def_water_wildmons CIANWOOD_CITY`, - rate `6 percent`: 20 TENTACOOL, 15 TENTACOOL, 20 TENTACRUEL. -- Rock smash: `data/wild/treemon_maps.asm:44` - `treemon_map CIANWOOD_CITY, TREEMON_SET_ROCK`, i.e. the same 90 KRABBY 15 / - 10 SHUCKLE 15 table as Route 40. Six smashable rocks are on the map. -- Headbutt: `TREEMON_SET_NONE` (`data/wild/treemon_maps.asm:32`). -- Fishing group `FISHGROUP_SHORE`. - ---- - -### MAP_MANIAS_HOUSE - -- Script: `maps/ManiasHouse.asm` -- Header (`data/maps/maps.asm:445`): `map ManiasHouse, TILESET_HOUSE, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:412`): `map_const MANIAS_HOUSE, 4, 4` -- Attributes (`data/maps/attributes.asm:651`): no connections - -**Warps**: 1 (2,7) -> `CIANWOOD_CITY` 1; 2 (3,7) -> `CIANWOOD_CITY` 1. - -**Coord events / BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MANIASHOUSE_ROCKER` | `SPRITE_ROCKER` | 2 | 4 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `ManiaScript` | -1 | - -**Scripts of interest** - -- `ManiaScript` (`pokegold.sym` 5d:4f6d): - 1. `checkevent EVENT_MANIA_TOOK_SHUCKIE_OR_LET_YOU_KEEP_HIM` -> happiness - speech, done forever. - 2. `checkevent EVENT_GOT_SHUCKIE` -> `.alreadyhaveshuckie`, which checks - `checkflag ENGINE_GOT_SHUCKIE_TODAY` (`constants/engine_flags.asm:102`); - if that daily flag is clear he asks for Shuckie back - (`.returnshuckie`). - 3. First time: `writetext ManiaText_AskLookAfterShuckle`, `yesorno`, - **`special GiveShuckle`** (`iffalse .partyfull`), then - `setevent EVENT_GOT_SHUCKIE`. - 4. `.returnshuckie`: `special ReturnShuckie` and a five-way `ifequal` on - `SHUCKIE_WRONG_MON` / `SHUCKIE_REFUSED` / `SHUCKIE_HAPPY` / - `SHUCKIE_FAINTED` (constants in `constants/script_constants.asm`). - `SHUCKIE_HAPPY` is the "it likes you enough, keep it" arm the - walkthrough's Thard_Verad note describes, and it also sets - `EVENT_MANIA_TOOK_SHUCKIE_OR_LET_YOU_KEEP_HIM`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_SHUCKIE` | `constants/event_flags.asm:78` | `ManiaScript` | Shuckie is in your party | -| `EVENT_MANIA_TOOK_SHUCKIE_OR_LET_YOU_KEEP_HIM` | `constants/event_flags.asm:79` | `.returnshuckie` arms | the encounter is finished | -| `ENGINE_GOT_SHUCKIE_TODAY` | `constants/engine_flags.asm:102` | daily flag, read by `.alreadyhaveshuckie` | he will not ask for it back on the day you got it | - -**Items** - Shuckie is a party member, not a bag item. A bot must have a free -party slot: `GiveShuckle` returns false and the script prints -`ManiaText_PartyFull` otherwise. This is what "clear a space so you can acquire -another Pokémon" means. - ---- - -### MAP_CIANWOOD_PHARMACY - -- Script: `maps/CianwoodPharmacy.asm` -- Header (`data/maps/maps.asm:448`): `map CianwoodPharmacy, TILESET_HOUSE, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:415`): `map_const CIANWOOD_PHARMACY, 4, 4` -- Attributes (`data/maps/attributes.asm:654`): no connections -- Scene scripts: one entry, `scene_script CianwoodPharmacyNoopScene ; unusable` - (a bare `end`, and no scene var row exists in `data/maps/scenes.asm`) - -**Warps**: 1 (2,7) -> `CIANWOOD_CITY` 4; 2 (3,7) -> `CIANWOOD_CITY` 4. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `CianwoodPharmacyBookshelf` | -| 1 | 1 | `BGEVENT_READ` | `CianwoodPharmacyBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CIANWOODPHARMACY_PHARMACIST` | `SPRITE_PHARMACIST` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CianwoodPharmacist` | -1 | - -**Scripts of interest** - -- `CianwoodPharmacist` (`pokegold.sym` 5d:5a9a): - `checkevent EVENT_GOT_SECRETPOTION_FROM_PHARMACY / iftrue .Mart`, then - **`checkevent EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS / iffalse .Mart`**. - Only with that flag set does it run `giveitem SECRETPOTION`, - `playsound SFX_KEY_ITEM`, `itemnotify`, - `setevent EVENT_GOT_SECRETPOTION_FROM_PHARMACY`. Otherwise it just opens the - shop: `pokemart MARTTYPE_PHARMACY, MART_CIANWOOD` -> `MartCianwood` - (`data/items/marts.asm:84`): POTION, SUPER_POTION, HYPER_POTION, FULL_HEAL, - REVIVE. - - **Gate for a bot**: you must talk to Jasmine on Lighthouse 6F *before* the - pharmacist will give you anything. Skipping the lighthouse and sailing - straight to Cianwood silently gets you a shop menu and no Secretpotion. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `SECRETPOTION` | talk to the pharmacist with `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` set | `CianwoodPharmacist` | `EVENT_GOT_SECRETPOTION_FROM_PHARMACY` (`constants/event_flags.asm:44`) | - ---- - -### MAP_CIANWOOD_PHOTO_STUDIO - -- Script: `maps/CianwoodPhotoStudio.asm` -- Header (`data/maps/maps.asm:449`): `map CianwoodPhotoStudio, TILESET_HOUSE, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:416`): `map_const CIANWOOD_PHOTO_STUDIO, 4, 4` -- **Warps**: 1 (2,7) -> `CIANWOOD_CITY` 5; 2 (3,7) -> `CIANWOOD_CITY` 5. -- Object: `CIANWOODPHOTOSTUDIO_FISHING_GURU`, `SPRITE_FISHING_GURU`, (2,3), - `SPRITEMOVEDATA_STANDING_DOWN`, `OBJECTTYPE_SCRIPT`, - `CianwoodPhotoStudioFishingGuruScript`. -- `CianwoodPhotoStudioFishingGuruScript`: `yesorno` then `special PhotoStudio`. - Purely optional (Game Boy Printer flavour); no flags, no items. - ---- - -### MAP_CIANWOOD_POKECENTER_1F - -- Script: `maps/CianwoodPokecenter1F.asm` -- Header (`data/maps/maps.asm:447`): `map CianwoodPokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:414`): `map_const CIANWOOD_POKECENTER_1F, 5, 4` - -**Warps**: 1 (3,7) -> `CIANWOOD_CITY` 3; 2 (4,7) -> `CIANWOOD_CITY` 3; -3 (0,7) -> `POKECENTER_2F` 1. - -**Object events** - -| const | sprite | x | y | movement | type | script label | -|---|---|---|---|---|---|---| -| `CIANWOODPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `jumpstd PokecenterNurseScript` | -| `CIANWOODPOKECENTER1F_LASS` | `SPRITE_LASS` | 1 | 5 | `SPRITEMOVEDATA_WALK_UP_DOWN` | `OBJECTTYPE_SCRIPT` | `CianwoodPokecenter1FLassScript` | -| `CIANWOODPOKECENTER1F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CianwoodGymGuideScript` | - -`CianwoodGymGuideScript` branches on `EVENT_BEAT_CHUCK`. The gym guide lives in -the Pokecenter here, not inside the gym. - ---- - -### MAP_CIANWOOD_LUGIA_SPEECH_HOUSE - -- Script: `maps/CianwoodLugiaSpeechHouse.asm` -- Header (`data/maps/maps.asm:450`): `map CianwoodLugiaSpeechHouse, TILESET_HOUSE, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions (`constants/map_constants.asm:417`): `map_const CIANWOOD_LUGIA_SPEECH_HOUSE, 4, 4` -- **Warps**: 1 (2,7) -> `CIANWOOD_CITY` 6; 2 (3,7) -> `CIANWOOD_CITY` 6. -- Objects: `CIANWOODLUGIASPEECHHOUSE_TEACHER` (`SPRITE_TEACHER`, 2,4), - `..._LASS` (`SPRITE_LASS`, 6,5, `WALK_LEFT_RIGHT`), - `..._TWIN` (`SPRITE_TWIN`, 0,2). BG events: bookshelf at (0,1) and (1,1). - All `jumptextfaceplayer` - Lugia lore, no flags. Optional. - ---- - -### MAP_CIANWOOD_GYM - -- Script: `maps/CianwoodGym.asm` -- Blocks: `maps/CianwoodGym.blk` -- Header (`data/maps/maps.asm:446`): `map CianwoodGym, TILESET_TOWER, INDOOR, LANDMARK_CIANWOOD_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` - (the `TRUE` is the phone-service flag) -- Dimensions (`constants/map_constants.asm:413`): `map_const CIANWOOD_GYM, 5, 9` (10x18 cells) -- Attributes (`data/maps/attributes.asm:652`): no connections -- Scene var: none, no callbacks, no coord events - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `CIANWOOD_CITY` | 2 | -| 2 | 5 | 17 | `CIANWOOD_CITY` | 2 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `CianwoodGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `CianwoodGymStatue` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `CIANWOODGYM_CHUCK` | `SPRITE_CHUCK` | 4 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `CianwoodGymChuckScript` | -1 | -| `CIANWOODGYM_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 2 | 12 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBlackbeltYoshi` | -1 | -| `CIANWOODGYM_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 7 | 12 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBlackbeltLao` | -1 | -| `CIANWOODGYM_BLACK_BELT3` | `SPRITE_BLACK_BELT` | 3 | 9 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 2 | `TrainerBlackbeltNob` | -1 | -| `CIANWOODGYM_BLACK_BELT4` | `SPRITE_BLACK_BELT` | 5 | 5 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerBlackbeltLung` | -1 | -| `CIANWOODGYM_BOULDER1` | `SPRITE_BOULDER` | 5 | 1 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `CianwoodGymBoulder` | -1 | -| `CIANWOODGYM_BOULDER2` | `SPRITE_BOULDER` | 3 | 7 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `CianwoodGymBoulder` | -1 | -| `CIANWOODGYM_BOULDER3` | `SPRITE_BOULDER` | 4 | 7 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `CianwoodGymBoulder` | -1 | -| `CIANWOODGYM_BOULDER4` | `SPRITE_BOULDER` | 5 | 7 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `CianwoodGymBoulder` | -1 | - -The puzzle the walkthrough describes is `BOULDER2`/`BOULDER3`/`BOULDER4`, the -row at y = 7, x = 3/4/5. `BOULDER1` at (5,1) is Chuck's prop: he throws it as -part of his intro. Blackbelt Lung at (5,5) is behind the boulder row - the -walkthrough's "Use Strength to push your way to the last trainer before the gym -leader". - -**Scripts of interest** - -- `CianwoodGymChuckScript` (`pokegold.sym` 5d:5304): - 1. `checkevent EVENT_BEAT_CHUCK / iftrue .FightDone`. - 2. Intro: `writetext ChuckIntroText1`, `turnobject CIANWOODGYM_CHUCK, RIGHT`, - `writetext ChuckIntroText2`, - `applymovement CIANWOODGYM_BOULDER1, CianwoodGymMovement_ChuckChucksBoulder` - (`set_sliding / big_step LEFT / big_step UP / fast_jump_step RIGHT / - remove_sliding`), `playsound SFX_STRENGTH`, `earthquake 80`, - `disappear CIANWOODGYM_BOULDER1`, `writetext ChuckIntroText3`. - 3. `winlosstext ChuckLossText, 0`, `loadtrainer CHUCK, CHUCK1`, - `startbattle`, `reloadmapafterbattle`, `setevent EVENT_BEAT_CHUCK`. - 4. `writetext GetStormBadgeText`, `playsound SFX_GET_BADGE`, - **`setflag ENGINE_STORMBADGE`**, then - `readvar VAR_BADGES / scall CianwoodGymActivateRockets`. - `CianwoodGymActivateRockets`: `ifequal 7, .RadioTowerRockets`, - `ifequal 6, .GoldenrodRockets` (`jumpstd GoldenrodRocketsScript` / - `RadioTowerRocketsScript`). With Storm as your 5th badge neither fires; - it only matters if you took Chuck out of order. - 5. `.FightDone`: `checkevent EVENT_GOT_TM01_DYNAMICPUNCH / iftrue`, else - **`setevent EVENT_BEAT_BLACKBELT_YOSHI` / `..._LAO` / `..._NOB` / - `..._LUNG`**, then `verbosegiveitem TM_DYNAMICPUNCH` and - `setevent EVENT_GOT_TM01_DYNAMICPUNCH`. - - **Bot-critical**: collecting the TM marks all four Blackbelts as beaten. - Fight them *before* Chuck or their EXP and money are gone. -- `CianwoodGymBoulder`: `jumpstd StrengthBoulderScript` -> - `farsjump AskStrengthScript` (`engine/events/std_scripts.asm:196`, - `engine/events/overworld.asm:1001`). That script calls `TryStrengthOW` - (`engine/events/overworld.asm:1038`), which needs a party mon knowing - `STRENGTH` **and** `ENGINE_PLAINBADGE`. -- `CianwoodGymStatue`: `checkflag ENGINE_STORMBADGE` -> `GymStatue2Script` with - `gettrainername STRING_BUFFER_4, CHUCK, CHUCK1`, else `GymStatue1Script`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_CHUCK` | `constants/event_flags.asm:711` | `CianwoodGymChuckScript` | badge earned; unlocks HM02 from Chuck's wife | -| `ENGINE_STORMBADGE` | `constants/engine_flags.asm:43` | `setflag` after the win | Fly's field gate (`engine/events/overworld.asm:545`), obedience up to L70 | -| `EVENT_GOT_TM01_DYNAMICPUNCH` | `constants/event_flags.asm:16` | `.FightDone` | one-time | -| `EVENT_BEAT_BLACKBELT_YOSHI/LAO/NOB/LUNG` | `constants/event_flags.asm` | set en masse by `.FightDone` | see the warning above | -| `ENGINE_PLAINBADGE` | `constants/engine_flags.asm:40` | read by `TryStrengthOW` | needed to shift the three boulders | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_DYNAMICPUNCH` (TM01) | talk to Chuck after the battle | `CianwoodGymChuckScript.FightDone` | `EVENT_GOT_TM01_DYNAMICPUNCH` | - -**Trainers** - -| const | class | id | party | script label | -|---|---|---|---|---| -| `YOSHI` | `BLACKBELT_T` | 2 | `TRAINERTYPE_MOVES`: 27 HITMONLEE (DOUBLE_KICK, MEDITATE, JUMP_KICK, FOCUS_ENERGY) | `TrainerBlackbeltYoshi` | -| `LAO` | `BLACKBELT_T` | 4 | `TRAINERTYPE_MOVES`: 27 HITMONCHAN (COMET_PUNCH, THUNDERPUNCH, ICE_PUNCH, FIRE_PUNCH) | `TrainerBlackbeltLao` | -| `NOB` | `BLACKBELT_T` | 5 | `TRAINERTYPE_MOVES`: 25 MACHOP (LEER, FOCUS_ENERGY, KARATE_CHOP, SEISMIC_TOSS), 25 MACHOKE (LEER, KARATE_CHOP, SEISMIC_TOSS, ROCK_SLIDE) | `TrainerBlackbeltNob` | -| `LUNG` | `BLACKBELT_T` | 7 | `TRAINERTYPE_NORMAL`: 23 MANKEY, 23 MANKEY, 25 PRIMEAPE | `TrainerBlackbeltLung` | -| `CHUCK1` | `CHUCK` | 1 | `ChuckGroup`, `TRAINERTYPE_MOVES`: 27 PRIMEAPE (LEER, RAGE, KARATE_CHOP, FURY_SWIPES), 30 POLIWRATH (HYPNOSIS, MIND_READER, SURF, DYNAMICPUNCH) | `CianwoodGymChuckScript` | - -**Wild encounters** - none (indoor). - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Reaching Route 40's sea and therefore Route 41 / Cianwood | `engine/events/overworld.asm:490` `TrySurfOW` and `:340` `SurfFunction.TrySurf` - `ld de, ENGINE_FOGBADGE / call CheckBadge` | Fog Badge + a party mon that knows `SURF` (`CheckPartyMove`) | Fog Badge is Morty's, earned in section 07. HM03 SURF comes from the Route 42 / Sudowoodo beat | -| Cianwood Gym boulders at (3,7)/(4,7)/(5,7) | `CianwoodGymBoulder` -> `jumpstd StrengthBoulderScript` -> `engine/events/overworld.asm:1001` `AskStrengthScript` / `:1038` `TryStrengthOW` | `ENGINE_PLAINBADGE` **and** a party mon knowing `STRENGTH` | Plain Badge from Whitney (earlier section); HM04 from `OlivineCafeStrengthSailorScript` in this section | -| Secretpotion from the Cianwood pharmacist | `maps/CianwoodPharmacy.asm:CianwoodPharmacist` - `checkevent EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS / iffalse .Mart` | that flag | talk to Jasmine on `MAP_OLIVINE_LIGHTHOUSE_6F` (`OlivineLighthouseJasmine`) | -| HM02 Fly from Chuck's wife | `maps/CianwoodCity.asm:CianwoodCityChucksWife` - `checkevent EVENT_BEAT_CHUCK` | Chuck beaten | `CianwoodGymChuckScript` | -| Using Fly in the field | `engine/events/overworld.asm:545` `FlyFunction.TryFly` - `ld de, ENGINE_STORMBADGE` | Storm Badge | the same gym win | -| Route 41 whirlpool tiles (Whirl Islands access only) | `engine/events/overworld.asm:1077` `WhirlpoolFunction.TryWhirlpool` / `:1171` `TryWhirlpoolOW` - `ld de, ENGINE_GLACIERBADGE` | Glacier Badge + `WHIRLPOOL` | Pryce, much later. **Not** required to reach Cianwood - the walkthrough's route only passes whirlpools, it never crosses one | -| Moomoo Farm berry feeding | `maps/Route39Barn.asm:MoomooScript` - `checkevent EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO` | talk to `PokefanM_DairyFarmer` first | `PokefanM_DairyFarmer` sets it unconditionally | -| TM13 SNORE | `maps/Route39Farmhouse.asm:FarmerFScript_GiveSnore` - `checkevent EVENT_HEALED_MOOMOO` | 7 Berries fed to Moomoo | `MoomooScript.SevenBerries` | -| Shuckie | `maps/ManiasHouse.asm:ManiaScript` - `special GiveShuckle` returns false on a full party | one free party slot | box a mon at the Pokecenter first | -| Olivine rival cut-scene | `coord_event 13,12` / `13,13` gated on `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (`wOlivineCitySceneID` = 0) | none - it is unavoidable if you walk those cells | `setscene SCENE_OLIVINECITY_NOOP` at the end of either scene script | - -Nothing in this section blocks the *walk* into Olivine City or up the -lighthouse. The only true forward gates are Surf (to leave Olivine westward), -the Jasmine-then-pharmacist flag chain, and Strength for the gym boulders. - -## 4. Bot checklist - -Preconditions carried in from earlier sections: `ENGINE_FOGBADGE`, -`ENGINE_PLAINBADGE`, `ENGINE_HIVEBADGE`, a mon that can learn `SURF`, a mon -that can learn `STRENGTH`, and ideally a stack of `BERRY` for Moomoo. - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `ECRUTEAK_CITY` | warps 14/15 | walk in | - | on `ROUTE_38_ECRUTEAK_GATE` | -| 2 | `ROUTE_38_ECRUTEAK_GATE` | warp 1 (0,4) or 2 (0,5) | walk west | - | on `ROUTE_38` at (35,8)/(35,9) | -| 3 | `ROUTE_38` | `ROUTE38_STANDING_YOUNGSTER2` (15,10) | battle | - | `EVENT_BEAT_BIRD_KEEPER_TOBY` | -| 4 | `ROUTE_38` | `ROUTE38_FRUIT_TREE` (12,10) | press A facing it | - | `BERRY` in bag | -| 5 | `ROUTE_38` | `ROUTE38_SAILOR` (25,5) | battle | - | `EVENT_BEAT_SAILOR_HARRY` | -| 6 | `ROUTE_38` | `ROUTE38_LASS` (15,3) | battle, then talk again, answer YES | - | `EVENT_BEAT_LASS_DANA`, `PHONE_LASS_DANA` registered | -| 7 | `ROUTE_38` | `ROUTE38_BEAUTY` (9,6) | battle | - | `EVENT_BEAT_BEAUTY_VALERIE` | -| 8 | `ROUTE_38` | `ROUTE38_STANDING_YOUNGSTER1` (4,1) | battle, talk again for the number | - | `EVENT_BEAT_SCHOOLBOY_CHAD` | -| 9 | `ROUTE_38` | west edge | walk west (connection) | - | on `ROUTE_39` | -| 10 | `ROUTE_39` | `ROUTE39_PSYCHIC_NORMAN` (13,6) | battle | - | `EVENT_BEAT_PSYCHIC_NORMAN` | -| 11 | `ROUTE_39` | `ROUTE39_FRUIT_TREE` (9,3) | press A | - | `MINT_BERRY` | -| 12 | `ROUTE_39` | (5,13) | press A (hidden) | - | `NUGGET`, `EVENT_ROUTE_39_HIDDEN_NUGGET` | -| 13 | `ROUTE_39` | warp 2 (5,3) -> farmhouse, `ROUTE39FARMHOUSE_POKEFAN_M` (3,2) | talk | - | `EVENT_TALKED_TO_FARMER_ABOUT_MOOMOO` | -| 14 | `ROUTE_39_BARN` (via Route 39 warp 1 at (1,3)) | `ROUTE39BARN_MOOMOO` (3,3) | talk, YES, x7 with a `BERRY` each time | step 13 done, 7 `BERRY` in bag | `wMooMooBerries` = 7, `EVENT_HEALED_MOOMOO` | -| 15 | `ROUTE_39_FARMHOUSE` | `ROUTE39FARMHOUSE_POKEFAN_F` (5,4) | talk | `EVENT_HEALED_MOOMOO` | `TM_SNORE`, `EVENT_GOT_TM13_SNORE_FROM_MOOMOO_FARM` | -| 16 | `ROUTE_39` | `ROUTE39_POKEFAN_M` (11,19), `ROUTE39_POKEFAN_F` (13,22), `ROUTE39_SAILOR` (13,29) | battle each | - | three `EVENT_BEAT_*` | -| 17 | `ROUTE_39` | south edge | walk south (connection) | - | on `OLIVINE_CITY`, `ENGINE_FLYPOINT_OLIVINE` set by the NEWMAP callback | -| 18 | `OLIVINE_CITY` | (13,12) or (13,13) | walk onto the cell | `wOlivineCitySceneID` == 0 | rival scene runs; scene -> 1; `SPRITE_OLIVINE_RIVAL` re-pointed to `SPRITE_SWIMMER_GUY` | -| 19 | `OLIVINE_CITY` | warp 6 (13,15) -> `OLIVINEGOODRODHOUSE_FISHING_GURU` (2,3) | talk, answer YES | - | `GOOD_ROD`, `EVENT_GOT_GOOD_ROD` | -| 20 | `OLIVINE_CITY` | warp 1 (13,21) | heal | - | party healed; spawn point already (13,22) | -| 21 | `OLIVINE_CITY` | warp 7 (7,21) -> `OLIVINECAFE_SAILOR` (4,3) | talk | - | `HM_STRENGTH`, `EVENT_GOT_HM04_STRENGTH` | -| 22 | (menu) | any eligible mon | teach `STRENGTH` from HM04 | - | Strength usable once `ENGINE_PLAINBADGE` is held | -| 23 | `OLIVINE_CITY` | warp 9 (29,27) | walk in | - | on `OLIVINE_LIGHTHOUSE_1F` | -| 24 | `OLIVINE_LIGHTHOUSE_1F` | warp 3 (3,11) | stairs | - | `OLIVINE_LIGHTHOUSE_2F` (3,11) | -| 25 | `OLIVINE_LIGHTHOUSE_2F` | `..._GENTLEMAN` (17,8), `..._SAILOR` (9,3) | battle both; talk to Huey again for his number | - | `EVENT_BEAT_GENTLEMAN_ALFRED`, `EVENT_BEAT_SAILOR_HUEY` | -| 26 | `OLIVINE_LIGHTHOUSE_2F` | warp 2 (5,3) | stairs | - | `OLIVINE_LIGHTHOUSE_3F` (5,3) | -| 27 | `OLIVINE_LIGHTHOUSE_3F` | `..._YOUNGSTER` (3,9), `..._GENTLEMAN` (13,5) | battle | - | `EVENT_BEAT_BIRD_KEEPER_THEO`, `EVENT_BEAT_GENTLEMAN_PRESTON` | -| 28 | `OLIVINE_LIGHTHOUSE_3F` | warp 1 (13,3) | stairs | - | `OLIVINE_LIGHTHOUSE_4F` (13,3) | -| 29 | `OLIVINE_LIGHTHOUSE_4F` | `..._LASS` (11,2), `..._SAILOR` (7,14) | battle | - | `EVENT_BEAT_LASS_CONNIE`, `EVENT_BEAT_SAILOR_KENT` | -| 30 | `OLIVINE_LIGHTHOUSE_4F` | warp 2 (3,5) | stairs | - | `OLIVINE_LIGHTHOUSE_5F` (3,5) | -| 31 | `OLIVINE_LIGHTHOUSE_5F` | item ball (2,13) | press A | - | `TM_SWAGGER`, `EVENT_OLIVINE_LIGHTHOUSE_5F_TM_SWAGGER` | -| 32 | `OLIVINE_LIGHTHOUSE_5F` | (3,13) | press A (hidden) | - | `HYPER_POTION` | -| 33 | `OLIVINE_LIGHTHOUSE_5F` | `..._YOUNGSTER` (8,3) | battle | - | `EVENT_BEAT_BIRD_KEEPER_DENIS` | -| 34 | `OLIVINE_LIGHTHOUSE_5F` | item ball (15,12) | press A | - | `RARE_CANDY` | -| 35 | `OLIVINE_LIGHTHOUSE_5F` | warp 4 (16,7) | drop to 4F | - | `OLIVINE_LIGHTHOUSE_4F` (16,7) | -| 36 | `OLIVINE_LIGHTHOUSE_4F` | warp 7 (8,3) or 8 (9,3) | drop to 3F | - | `OLIVINE_LIGHTHOUSE_3F` (8,3)/(9,3) | -| 37 | `OLIVINE_LIGHTHOUSE_3F` | `..._SAILOR` (9,2) | battle | - | `EVENT_BEAT_SAILOR_TERRELL` | -| 38 | `OLIVINE_LIGHTHOUSE_3F` | item ball (8,2) | press A | - | `ETHER` | -| 39 | `OLIVINE_LIGHTHOUSE_3F` | warp 1 (13,3), then 4F warp 2 (3,5) | back up to 5F | - | on 5F | -| 40 | `OLIVINE_LIGHTHOUSE_5F` | `..._SAILOR` (8,11) | battle | - | `EVENT_BEAT_SAILOR_ERNEST` | -| 41 | `OLIVINE_LIGHTHOUSE_5F` | item ball (6,15) | press A | - | `GREAT_BALL` | -| 42 | `OLIVINE_LIGHTHOUSE_5F` | warp 1 (9,15) | stairs | - | `OLIVINE_LIGHTHOUSE_6F` (9,15) | -| 43 | `OLIVINE_LIGHTHOUSE_6F` | `OLIVINELIGHTHOUSE6F_JASMINE` (8,8) | talk | no `SECRETPOTION` in bag | **`EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS`** | -| 44 | `OLIVINE_LIGHTHOUSE_6F` | item ball (3,4) | press A | - | `SUPER_POTION` | -| 45 | `OLIVINE_LIGHTHOUSE_6F` .. `1F` | (16,5) -> (16,7) -> (16,9) -> (16,11) -> (16,13) | walk the right-wall drop chain | - | back on `OLIVINE_LIGHTHOUSE_1F` | -| 46 | `OLIVINE_LIGHTHOUSE_1F` | warp 1 (10,17) | exit | - | `OLIVINE_CITY` (29,27) | -| 47 | `OLIVINE_CITY` | warp 1 (13,21) | heal | - | - | -| 48 | (menu) | a water mon | teach `SURF` (HM03) | HM03 in bag | Surf usable (`ENGINE_FOGBADGE` already held) | -| 49 | `OLIVINE_CITY` | warp 8 (19,17) | buy `GREAT_BALL` etc. | money | - | -| 50 | `OLIVINE_CITY` | west edge | walk west (connection) | - | on `ROUTE_40` | -| 51 | `ROUTE_40` | face the sea, press A / use SURF | start surfing | `ENGINE_FOGBADGE` + `SURF` | `PLAYER_SURF` | -| 52 | `ROUTE_40` | (14,15), (3,19), (10,25), (18,30) | battle Simon, Elaine, Paula, Randall | - | four `EVENT_BEAT_SWIMMER*` | -| 53 | `ROUTE_40` | (11,7) rock then (11,7) hidden | Rock Smash, then press A | `ROCK_SMASH` | `HYPER_POTION`; the rock roll can also yield 10% SHUCKLE 15 | -| 54 | `ROUTE_40` | south edge | swim south (connection) | - | on `ROUTE_41` | -| 55 | `ROUTE_41` | (46,8) George, (44,28) Kara, (32,6) Charlie, (9,50) Wendy, (23,19) Susie, (20,26) Berke, (27,34) Denise, (32,30) Kirk, (19,46) Mathew, (17,4) Kaylee | battle as encountered | - | ten `EVENT_BEAT_SWIMMER*` | -| 56 | `ROUTE_41` | (9,35) | press A (hidden) | - | `MAX_ETHER` | -| 57 | `ROUTE_41` | west edge | swim west (connection) | - | on `CIANWOOD_CITY`; `ENGINE_FLYPOINT_CIANWOOD` set | -| 58 | `CIANWOOD_CITY` | warp 3 (23,43) | heal, deposit a mon to free a party slot | - | party size < 6 | -| 59 | `CIANWOOD_CITY` | warp 1 (17,41) -> `MANIASHOUSE_ROCKER` (2,4) | talk, answer YES | free party slot | `EVENT_GOT_SHUCKIE`, L15 SHUCKLE "SHUCKIE" holding `BERRY`, OT MANIA | -| 60 | `CIANWOOD_CITY` | warp 4 (15,47) -> `CIANWOODPHARMACY_PHARMACIST` (2,3) | talk | **`EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS`** | `SECRETPOTION`, `EVENT_GOT_SECRETPOTION_FROM_PHARMACY` | -| 61 | `CIANWOOD_CITY` | (8,16) and (5,29) | Rock Smash the rock, press A | `ROCK_SMASH` | `REVIVE`, `MAX_ETHER` | -| 62 | `CIANWOOD_CITY` | warp 2 (8,43) | enter gym | - | on `CIANWOOD_GYM` (4,17) | -| 63 | `CIANWOOD_GYM` | (2,12) Yoshi, (7,12) Lao | battle both | - | `EVENT_BEAT_BLACKBELT_YOSHI`, `..._LAO` | -| 64 | `CIANWOOD_GYM` | (3,9) Nob | battle | - | `EVENT_BEAT_BLACKBELT_NOB` | -| 65 | `CIANWOOD_GYM` | boulders at (3,7),(4,7),(5,7) | use Strength, push a lane open | `ENGINE_PLAINBADGE` + `STRENGTH` | path north | -| 66 | `CIANWOOD_GYM` | (5,5) Lung | battle | - | `EVENT_BEAT_BLACKBELT_LUNG` | -| 67 | `CIANWOOD_CITY` | warp 3 | heal, save | - | - | -| 68 | `CIANWOOD_GYM` | `CIANWOODGYM_CHUCK` (4,1) | talk -> battle | all four blackbelts already beaten | `EVENT_BEAT_CHUCK`, `ENGINE_STORMBADGE`, 3000 gold | -| 69 | `CIANWOOD_GYM` | `CIANWOODGYM_CHUCK` | talk again | `EVENT_BEAT_CHUCK` | `TM_DYNAMICPUNCH`, `EVENT_GOT_TM01_DYNAMICPUNCH` | -| 70 | `CIANWOOD_CITY` | `CIANWOODCITY_POKEFAN_F` (10,46) | talk | `EVENT_BEAT_CHUCK` | `HM_FLY`, `EVENT_GOT_HM02_FLY` | -| 71 | (menu) | a flying-capable mon | teach `FLY` | `ENGINE_STORMBADGE` | fly available; section ends, next section flies back to Olivine | - -## 5. Port coverage - -The Gen 2 port is engine-level and data-driven: every map header, `.blk`, -warp/coord/bg/object table and script bytecode in this section is read out of -the ROM by `src/import/RomExtractorGen2.lua` (`readMapEvents` at lines 785-862, -map records written at 973-976) rather than hand-transcribed, so "implemented" -below means the *opcodes and specials these particular scripts use* are -implemented, not that anyone has walked the maps. - -| Beat | Port file | Status | -|---|---|---| -| Map headers, warps, coord events, bg events, object events for all 21 maps | `src/import/RomExtractorGen2.lua`, `src/world/gen2/Map.lua`, `src/world/gen2/World.lua` | implemented (generic, ROM-driven) | -| `MAPCALLBACK_NEWMAP` fly-point callbacks (`OlivineCityFlypointCallback`, `CianwoodCityFlypointCallback`) | `src/world/gen2/World.lua`, driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| `MAPCALLBACK_OBJECTS` weekday callback (`Route40MonicaCallback`) | `src/world/gen2/World.lua` + `readvar VAR_WEEKDAY` in `src/script/gen2/Opcodes.lua` | implemented | -| Scene scripts / `setscene` / coord-event trip-wires (the Olivine rival) | `src/world/gen2/World.lua` (`mapScenes`, `setScene`, `setMapScene`), `src/script/gen2/Vm.lua` | implemented | -| Rival cut-scene opcodes: `showemote`, `applymovement`, `appear`/`disappear`, `variablesprite`, `special LoadUsedSpritesGFX`, `special FadeOutMusic`/`RestartMapMusic` | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Movement.lua`, `src/script/gen2/Specials.lua` | implemented | -| Trainer objects, sight ranges, `loadtrainer`/`startbattle`/`winlosstext` | `src/world/gen2/Trainers.lua`, `src/script/gen2/Vm.lua`, driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| Phone registration and rematch tiers (Dana, Chad, Derek, Huey) | `src/core/gen2/Phone.lua`; `checkcellnum` / `askforphonenumber` in `src/script/gen2/Opcodes.lua` | implemented | -| Item balls (`OBJECTTYPE_ITEMBALL`) and hidden items (`BGEVENT_ITEM`) | `src/world/gen2/World.lua` (bg-event jumptable), `src/world/gen2/HiddenItems.lua` | implemented | -| Fruit trees (`fruittree FRUITTREE_ROUTE_38/39`) | `fruittree` opcode in `src/script/gen2/Opcodes.lua` | implemented | -| Moomoo Farm berry counter (`readmem`/`addval`/`writemem wMooMooBerries`) | `src/script/gen2/Vm.lua:711` and `:1657` (`scriptMem`), persisted by `src/core/gen2/Save.lua:187` | implemented - the port names this exact WRAM slot | -| `verbosegiveitem` / `giveitem` / `takeitem` / `checkitem` / `itemnotify` (Good Rod, HM04, TM13, Secretpotion, HM02, TM01) | `src/script/gen2/Opcodes.lua` | implemented | -| Moomoo Milk purchase (`checkmoney` / `takemoney` / `giveitem`) | `src/script/gen2/Opcodes.lua` | implemented | -| Marts: `MARTTYPE_STANDARD` (`MART_OLIVINE`) and `MARTTYPE_PHARMACY` (`MART_CIANWOOD`) | `src/ui/gen2/MartMenu.lua` (`PHARMACY` table at :212) | implemented | -| Shuckie: `special GiveShuckle` / `special ReturnShuckie`, OT MANIA id 518, happiness arms, `ENGINE_GOT_SHUCKIE_TODAY` | `src/script/gen2/Specials.lua:1191` onward | implemented | -| Cianwood photo studio `special PhotoStudio` | `src/script/gen2/Specials.lua:2122`, `src/ui/gen2/PhotoStudio.lua` | implemented | -| Jasmine cure scene: `special PlaySlowCry`, `special FadeOutToWhite`/`FadeInFromWhite`, `cry AMPHAROS`, `readvar VAR_FACING` branch | `src/script/gen2/Specials.lua` (998, 1000, 1079), `src/script/gen2/Opcodes.lua` | implemented | -| Surf field move + `ENGINE_FOGBADGE` gate | `src/world/gen2/FieldMoves.lua` (`surfFromMenu`, `TrySurfOW` port), driver `tests/drivers/gold_water_moves.lua` (its `WHIRL` fixture is literally `ROUTE_41` (22,11)) | implemented | -| Strength field move + `ENGINE_PLAINBADGE` gate + `STRENGTH_BOULDER` pushing | `src/world/gen2/FieldMoves.lua` (`strengthFromMenu`, the 3-way `TryStrengthOW` result at :689), driver `tests/drivers/gold_icepath_boulder.lua` | implemented | -| Fly field move + `ENGINE_STORMBADGE` gate, fly points | `src/world/gen2/FieldMoves.lua:330` (`flypoints.asm` transcribed), `:504` `flyFromMenu` | implemented | -| Whirlpool + `ENGINE_GLACIERBADGE` gate (Route 41 flavour, Whirl Islands) | `src/world/gen2/FieldMoves.lua:542`, `:625` | implemented | -| Badge award: `setflag ENGINE_STORMBADGE`, `readvar VAR_BADGES`, `scall` into `GoldenrodRocketsScript`/`RadioTowerRocketsScript` | `src/script/gen2/Opcodes.lua` (`setflag`, `checkflag`), `src/script/gen2/Vm.lua` | implemented | -| Chuck's boulder throw: `earthquake 80`, `set_sliding`/`big_step`/`fast_jump_step` | `earthquake` in `src/script/gen2/Opcodes.lua`, movement verbs in `src/script/gen2/Movement.lua` | implemented | -| Water wild encounters for Route 40 / 41 / Olivine / Cianwood, Gold-vs-Silver Mantine split | `src/battle/gen2/Encounter.lua`, tables from `RomExtractorGen2.lua` | implemented | -| Good Rod fishing (`FISHGROUP_SHORE` / `FISHGROUP_OCEAN`, `TimeFishGroups`) | `src/battle/gen2/Encounter.lua` (`Encounter.fish`), extractor `readRod` | implemented | -| Headbutt trees on Route 38/39 (`TREEMON_SET_FOREST`) | `src/battle/gen2/Encounter.lua:114` onward, `RomExtractorGen2.lua:3780` | implemented | -| **Rock Smash wild encounters (`RockMonMaps` -> `TREEMON_SET_ROCK`, i.e. Shuckle/Krabby on Route 40 and Cianwood City)** | `src/script/gen2/CallAsm.lua:526` explicitly stubs `RockMonEncounter` with "ROCK SMASH has no field-move path yet; RockMonMaps is unported"; `RomExtractorGen2.lua` extracts `TreeMonMaps` only | **missing** | -| Roaming / phone-call flavour beats the walkthrough mentions (Arnie, Liz, Todd, Mom's doll) | `src/core/gen2/Phone.lua` (incoming/special call machinery), `src/core/gen2/MomShopping.lua` | implemented (generic); no per-call verification done for this section | -| Any driver that actually walks Route 38 -> Cianwood end to end | - | **missing** - no `tests/drivers/gold_*` covers this stretch. `gold_water_moves.lua` touches `ROUTE_41` (22,11) only as a whirlpool fixture | - -## 6. Unresolved / verify by hand - -1. **"Beauty Olivia" on Route 38.** The walkthrough says "Now move southward - now to face Beauty Olivia." There is no `OLIVIA` in - `constants/trainer_constants.asm` (the `BEAUTY` class runs VICTORIA, - SAMANTHA, JULIE, JACLYN, BRENDA, CASSIE, CAROLINE, CARLENE, JESSICA, - RACHAEL, ANGELICA, KENDRA, VERONICA, JULIA, THERESA, VALERIE), no `OLIVIA` - string in `data/trainers/parties.asm`, and `maps/Route38.asm` has exactly - five trainer objects. This trainer does not exist in pokegold. -2. **"#100 Voltorb" listed under "Pokémon found in Olivine City".** Olivine - City has no grass encounter table at all, and its water table - (`data/wild/johto_water.asm:239`) is TENTACOOL / TENTACOOL / TENTACRUEL. - Voltorb is not reachable on this map by any table I could find. Unverified. -3. **"#213 Shuckle" listed under Route 40 *and* Route 41.** Route 40 is correct - but by Rock Smash, not by surfing: `data/wild/treemon_maps.asm:45` puts it - in `RockMonMaps` with `TREEMON_SET_ROCK` (10% SHUCKLE 15). Route 41 is - **not** in `RockMonMaps`, has `TREEMON_SET_NONE` for headbutt, and its - `Route41Rock` script is marked `; unreferenced` in the asm. Shuckle appears - unobtainable on Route 41. -4. **Swimmer Charlie's second Pokemon.** Walkthrough: "Level 21 Tentacool". - `data/trainers/parties.asm` `SwimmerMGroup` "CHARLIE" (`SWIMMERM` 4) reads - `db 21, SHELLDER / db 19, TENTACOOL / db 19, TENTACRUEL`. Level 19, not 21. -5. **Bird Keeper Denis's party order.** Walkthrough lists Spearow, Spearow, - Fearow; the asm order is `18 SPEAROW / 20 FEAROW / 18 SPEAROW`. -6. **Swimmer Kaylee** (`SWIMMERF` 3, 18 GOLDEEN / 20 GOLDEEN / 20 SEAKING) at - `ROUTE_41` (17,4) is a real trainer the walkthrough never mentions. -7. **Whether the x = 16/17 lighthouse pairs are one-way holes or two-way - passages.** The warp tables are symmetric (every pair points back at the - coordinates it came from), so warp data alone cannot tell you. Whichever it - is lives in the block collision for `TILESET_LIGHTHOUSE` and the `.blk` - files, which I did not decode. The walkthrough treats them as one-way drops - and the geometry is consistent with that, but treat "can I climb back up - through (16,7)?" as unverified. -8. **"You can avoid fighting the next trainer" (Bird Keeper Theo, 3F).** Theo - has sight range 3 at (3,9); whether a walkable lane exists outside that - cone depends on the 3F `.blk`, not the object table. -9. **The exact spot the walkthrough means by "Now, swim south onto Route 41" - and the "left side / right side" split.** Route 41's two branches are - geometry in `maps/Route41.blk`; the object coordinates above are the only - hard data. A bot should path by trainer coordinates rather than by the - prose. -10. **Phone-call timing claims** ("Arnie calls again", "Liz then calls you - about her Nidoran", "Todd will now call", "Mom calls saying she bought you - an adorable doll"). These come from `CheckPhoneCall` in - `engine/overworld/time.asm` plus `data/phone/*.asm`, not from any script in - these maps, and the walkthrough's own quoted note says the caller and topic - are random. Nothing in this section's asm schedules them. -11. **"3000G" from Chuck.** Prize money is computed from the trainer class base - in `data/trainers/attributes.asm` times the last mon's level, not written - anywhere in `maps/CianwoodGym.asm`. I did not verify the arithmetic. -12. **"TM23" under "Items found in Olivine City".** TM23 is Jasmine's gym - reward in `maps/OlivineGym.asm`, which belongs to section 09; nothing on - `MAP_OLIVINE_CITY` itself yields it. diff --git a/docs/gold-walkthrough/asm-walk/section-09-olivine-city-gym.md b/docs/gold-walkthrough/asm-walk/section-09-olivine-city-gym.md deleted file mode 100644 index aeda633e..00000000 --- a/docs/gold-walkthrough/asm-walk/section-09-olivine-city-gym.md +++ /dev/null @@ -1,681 +0,0 @@ -# Section 09 - Olivine City Gym - -Source: `../section-09-olivine-city-gym.txt` -Maps covered: `MAP_GOLDENROD_CITY` (fly stop, optional), `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` (transit), `MAP_GOLDENROD_UNDERGROUND`, `MAP_OLIVINE_CITY`, `MAP_OLIVINE_LIGHTHOUSE_1F` .. `MAP_OLIVINE_LIGHTHOUSE_6F`, `MAP_OLIVINE_GYM` -Badges / key milestones in this section: **MINERALBADGE** (`ENGINE_MINERALBADGE`), `EVENT_BEAT_JASMINE`, `TM_IRON_TAIL` (TM23), `EVENT_JASMINE_RETURNED_TO_GYM` (set on Lighthouse 6F, the gate for the gym even existing), the 6th-badge Rocket trigger (`GoldenrodRocketsScript`). - -This section assumes the player already has the SECRETPOTION from Cianwood (section 8) and -`ENGINE_STORMBADGE` (Chuck), which is what makes `FLY` usable at all. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `GOLDENROD_CITY` (optional) | `maps/GoldenrodCity.asm` | Fly (`SPAWN_GOLDENROD`, `ENGINE_FLYPOINT_GOLDENROD`) | warp 15 at (11, 29) -> `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` warp 5 | Only if raising an Eevee: buy the 500 haircut for happiness | -| 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` | warp 5 at (4, 29) / warp 6 at (5, 29) | warp 4 at (5, 25) -> `GOLDENROD_UNDERGROUND` warp 2 | Pure transit corridor to the salon | -| 3 | `GOLDENROD_UNDERGROUND` | `maps/GoldenrodUnderground.asm` | warp 2 at (3, 34) | back out the way in, then Fly | Talk to the *older* Haircut Brother at (7, 14), pay 500, happiness bump | -| 4 | `OLIVINE_CITY` | `maps/OlivineCity.asm` | Fly (`SPAWN_OLIVINE`, `ENGINE_FLYPOINT_OLIVINE`) | warp 9 at (29, 27) -> `OLIVINE_LIGHTHOUSE_1F` warp 1 | Head for the lighthouse | -| 5 | `OLIVINE_LIGHTHOUSE_1F` -> `2F` -> `3F` -> `4F` -> `3F` -> `4F` -> `5F` -> `6F` | `maps/OlivineLighthouse[1-6]F.asm` | ladders + one floor pit ("fall to the left of that one lass") | same ladders/pits back down | Hand Jasmine the SECRETPOTION | -| 6 | `OLIVINE_LIGHTHOUSE_6F` | `maps/OlivineLighthouse6F.asm` | warp 1 at (9, 15) from 5F warp 1 | warp 1 at (9, 15) back to 5F | `OlivineLighthouseJasmine` -> `EVENT_JASMINE_RETURNED_TO_GYM`, `clearevent EVENT_OLIVINE_GYM_JASMINE` | -| 7 | `OLIVINE_CITY` | `maps/OlivineCity.asm` | `OLIVINE_LIGHTHOUSE_1F` warps 1/2 at (10, 17)/(11, 17) | warp 2 at (10, 11) -> `OLIVINE_GYM` warp 1 | Walk to the gym door | -| 8 | `OLIVINE_GYM` | `maps/OlivineGym.asm` | warps 1/2 at (4, 15)/(5, 15) | same warps back to `OLIVINE_CITY` warp 2 | Beat Jasmine, take MINERALBADGE + TM23 | - -Spill into the next section: the walkthrough offers "fly to Ecruteak and go through the Lake of -Rage" as an alternative to fighting Jasmine now. `MAP_LAKE_OF_RAGE` and the Route 42/43 chain -belong to the next section; nothing in this section's asm depends on them. - ---- - -## 2. Maps - -### MAP_GOLDENROD_UNDERGROUND - -- Script: `maps/GoldenrodUnderground.asm` (`46:4900 GoldenrodUnderground_MapEvents`, `46:4129 OlderHaircutBrotherScript`) -- Blocks: `maps/GoldenrodUnderground.blk` -- Header: `data/maps/maps.asm:123` -> `map GoldenrodUnderground, TILESET_GATE, DUNGEON, LANDMARK_GOLDENROD_CITY, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:110` -> `map_const GOLDENROD_UNDERGROUND, 15, 18` (map group 1, the `newgroup OLIVINE` group, map id 45 - it is *not* in the Goldenrod group) -- Attributes: `data/maps/attributes.asm:442` -> `map_attributes GoldenrodUnderground, GOLDENROD_UNDERGROUND, $00` (no connections) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 7 | -| 2 | 3 | 34 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 4 | -| 3 | 18 | 6 | `GOLDENROD_UNDERGROUND` | 4 | -| 4 | 21 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 5 | 22 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 6 | 22 | 27 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 1 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 18 | 6 | `BGEVENT_READ` | `BasementDoorScript` | -| 19 | 6 | `BGEVENT_READ` | `GoldenrodUndergroundNoEntrySign` | -| 6 | 13 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenParlyzHeal` (`hiddenitem PARLYZ_HEAL, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_PARLYZ_HEAL`) | -| 4 | 18 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenSuperPotion` (`hiddenitem SUPER_POTION, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_SUPER_POTION`) | -| 17 | 8 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenAntidote` (`hiddenitem ANTIDOTE, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_ANTIDOTE`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODUNDERGROUND_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 31 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSupernerdEric` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 6 | 9 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSupernerdTeru` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD3` | `SPRITE_SUPER_NERD` | 3 | 27 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerPokemaniacIssac` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD4` | `SPRITE_SUPER_NERD` | 2 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPokemaniacDonald` | -1 | -| `GOLDENRODUNDERGROUND_POKE_BALL` | `SPRITE_POKE_BALL` | 7 | 25 | `STILL` | `OBJECTTYPE_ITEMBALL` | `GoldenrodUndergroundCoinCase` (`itemball COIN_CASE`) | `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE` | -| `GOLDENRODUNDERGROUND_GRAMPS` | `SPRITE_GRAMPS` | 7 | 11 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `BargainMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRAMPS` | -| `GOLDENRODUNDERGROUND_OLDER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 14 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `OlderHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 15 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `YoungerHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_GRANNY` | `SPRITE_GRANNY` | 7 | 21 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `BitterMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRANNY` | - -Object-event visibility convention (verified in `engine/overworld/scripting.asm:879-898`): `appear` -*clears* the flag and `disappear` *sets* it, so **an object is on the map only while its event flag -is clear.** - -**Scripts of interest** - -- `GoldenrodUndergroundCheckDayOfWeekCallback` (`callback MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY` - and appear/disappear the four merchants. Who is standing there: - - Sunday: younger brother + granny - - Monday: gramps only, and only during `MORN` (`checktime MORN`) - - Tuesday / Thursday: **older brother** - - Wednesday / Friday: younger brother - - Saturday: older brother + granny - - The walkthrough's "500 haircut" therefore only exists on **Tuesday, Thursday or Saturday**. -- `OlderHaircutBrotherScript` (`46:4129`): `readvar VAR_WEEKDAY`, bails to - `GoldenrodUndergroundScript_ShopClosed` unless TUE/THU/SAT. Then - `checkflag ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (one cut per day, cleared by the daily - reset), `yesorno`, `checkmoney YOUR_MONEY, 500`, `special OlderHaircutBrother`, then - `takemoney YOUR_MONEY, 500`. `DEF GOLDENRODUNDERGROUND_OLDER_HAIRCUT_PRICE EQU 500` is at the top - of the file. The special returns 0/1 (cancel or Egg) or 2/3/4, which the script mirrors into - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1/2/3` just to pick which of three "looks happier" lines to - print. -- `special OlderHaircutBrother` -> `engine/events/haircut.asm:15` -> `HaircutOrGrooming`, weighted - roll over `HappinessData_OlderHaircutBrother` (`data/events/happiness_probabilities.asm:1`): - 30% -> `HAPPINESS_OLDERCUT1`, 50%+1 -> `HAPPINESS_OLDERCUT2`, remainder -> `HAPPINESS_OLDERCUT3`. - The actual deltas are `data/events/happiness_changes.asm:13-15`: - `+1/+1/+1`, `+3/+3/+1`, `+5/+5/+2` (columns are happiness <100 / <200 / otherwise). - The younger brother (300, SUN/WED/FRI) rolls 60%+1 / 30% / rest for `+1/+1/+1`, `+3/+3/+1`, - `+10/+10/+4` - **its jackpot row is strictly better than the older brother's**, which the - walkthrough does not mention. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (id 89) | `constants/engine_flags.asm` | checked+set by both `*HaircutBrotherScript` | one haircut per day, either brother | -| `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` (0x754) | `constants/event_flags.asm:1270` | day-of-week callback | clear = older brother is standing at (7, 14) | -| `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` (0x755) | `constants/event_flags.asm` | day-of-week callback | clear = younger brother at (7, 15) | -| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1/2/3` (0x000-0x002) | `constants/event_flags.asm:5-7` | haircut scripts | scratch, do not persist | - -**Items** - nothing the walkthrough takes here; the Coin Case itemball and the three hidden items -belong to earlier sections. - -**Trainers** - the four in the table above are optional and already beaten by this point in a -linear run. - -**Wild encounters** - none (indoor `DUNGEON`, no entry in `data/wild/johto_grass.asm`). - ---- - -### MAP_OLIVINE_CITY - -- Script: `maps/OlivineCity.asm` (`49:4463 OlivineCity_MapEvents`) -- Blocks: `maps/OlivineCity.blk` -- Header: `data/maps/maps.asm:63` -> `map OlivineCity, TILESET_JOHTO, TOWN, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:52` -> `map_const OLIVINE_CITY, 20, 18` (group 1, id 14) -- Connections: `data/maps/attributes.asm:143-145` -> north `Route39` (`ROUTE_39`, offset 5), west `Route40` (`ROUTE_40`, offset 9). No south/east. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 21 | `OLIVINE_POKECENTER_1F` | 1 | -| 2 | 10 | 11 | `OLIVINE_GYM` | 1 | -| 3 | 25 | 11 | `OLIVINE_TIMS_HOUSE` | 1 | -| 4 | 0 | 0 | `OLIVINE_HOUSE_BETA` | 1 (marked `; inaccessible` in the asm) | -| 5 | 29 | 11 | `OLIVINE_PUNISHMENT_SPEECH_HOUSE` | 1 | -| 6 | 13 | 15 | `OLIVINE_GOOD_ROD_HOUSE` | 1 | -| 7 | 7 | 21 | `OLIVINE_CAFE` | 1 | -| 8 | 19 | 17 | `OLIVINE_MART` | 2 | -| 9 | 29 | 27 | `OLIVINE_LIGHTHOUSE_1F` | 1 | -| 10 | 19 | 27 | `OLIVINE_PORT_PASSAGE` | 1 | -| 11 | 20 | 27 | `OLIVINE_PORT_PASSAGE` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (= 0) | 13 | 12 | `OlivineCityRivalSceneTop` | rival cutscene, ends with `setscene SCENE_OLIVINECITY_NOOP` | -| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (= 0) | 13 | 13 | `OlivineCityRivalSceneBottom` | same, mirrored movement | - -Scene ids come from the `scene_script` macro's own `const_def` (`macros/scripts/maps.asm:12-36`): -`SCENE_OLIVINECITY_RIVAL_ENCOUNTER` = 0, `SCENE_OLIVINECITY_NOOP` = 1. By this section the scene is -already 1 (the rival scene fires on the *first* Olivine visit, section 8), so both trip-wires are -inert and a bot may walk x=13 freely. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 11 | `BGEVENT_READ` | `OlivineCitySign` | -| 20 | 24 | `BGEVENT_READ` | `OlivineCityPortSign` | -| 7 | 11 | `BGEVENT_READ` | `OlivineGymSign` | -| 30 | 28 | `BGEVENT_READ` | `OlivineLighthouseSign` | -| 14 | 21 | `BGEVENT_READ` | `OlivineCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 20 | 17 | `BGEVENT_READ` | `OlivineCityMartSign` (`jumpstd MartSignScript`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINECITY_SAILOR1` | `SPRITE_SAILOR` | 26 | 27 | `WALK_UP_DOWN` (0,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor1Script` | -1 | -| `OLIVINECITY_STANDING_YOUNGSTER` | `SPRITE_YOUNGSTER` | 20 | 13 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `OlivineCityStandingYoungsterScript` | -1 | -| `OLIVINECITY_SAILOR2` | `SPRITE_SAILOR` | 17 | 21 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor2Script` | -1 | -| `OLIVINECITY_OLIVINE_RIVAL` | `SPRITE_OLIVINE_RIVAL` | 10 | 11 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_OLIVINE_CITY` | - -Note the rival object sits **on the gym door tile** (10, 11). `InitializeEventsScript` -(`engine/events/std_scripts.asm:520`) sets `EVENT_RIVAL_OLIVINE_CITY` at new game, so he is hidden -except during the cutscene, and the door is walkable. - -**Scripts of interest** - -- `OlivineCityFlypointCallback` (`callback MAPCALLBACK_NEWMAP`): `setflag ENGINE_FLYPOINT_OLIVINE` - (engine flag id 70). This is what makes "fly straight back to Olivine City" legal; it was already - set in section 8. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_OLIVINE` (id 70) | `constants/engine_flags.asm:85` | set by `OlivineCityFlypointCallback` | Olivine selectable on the Fly map | -| `ENGINE_FLYPOINT_GOLDENROD` (id 69) | `constants/engine_flags.asm:84` | set by `maps/GoldenrodCity.asm:24` | Goldenrod selectable on the Fly map | -| `EVENT_RIVAL_OLIVINE_CITY` | `constants/event_flags.asm` | set by `InitializeEventsScript`, `appear`/`disappear` in the scene scripts | clear = rival standing on the gym door | - -**Items** - none taken in this section. - -**Trainers** - none on the overworld map. - -**Wild encounters** - `data/wild/johto_water.asm:239`: - -``` -def_water_wildmons OLIVINE_CITY -db 6 percent ; encounter rate -db 20, TENTACOOL -db 15, TENTACOOL -db 20, TENTACRUEL -``` - -No grass table (`data/wild/johto_grass.asm` has no `OLIVINE` entry). Fish group is -`FISHGROUP_SHORE` per the header row. - ---- - -### MAP_OLIVINE_LIGHTHOUSE_1F .. 6F - -- Scripts: `maps/OlivineLighthouse1F.asm` ... `maps/OlivineLighthouse6F.asm` -- Blocks: `maps/OlivineLighthouse[1-6]F.blk` -- Header: `data/maps/maps.asm:112-117`. 1F-5F are - `TILESET_LIGHTHOUSE, DUNGEON, LANDMARK_LIGHTHOUSE, MUSIC_LIGHTHOUSE, FALSE, PALETTE_DAY, FISHGROUP_SHORE`; - **6F swaps the music to `MUSIC_VIOLET_CITY`** (line 117), which is a cheap way for a bot to - confirm it reached the top. -- Dimensions: `constants/map_constants.asm:99-104` -> every floor is `10, 9` blocks = 20 x 18 tiles - (group 1, ids 34-39). -- Attributes: `data/maps/attributes.asm:436` (6F) and neighbours - `$00`, no connections. - -**Warps** - transcribed verbatim, all six floors. - -`OLIVINE_LIGHTHOUSE_1F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 10 | 17 | `OLIVINE_CITY` | 9 | -| 2 | 11 | 17 | `OLIVINE_CITY` | 9 | -| 3 | 3 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 1 | -| 4 | 16 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 3 | -| 5 | 17 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 4 | - -`OLIVINE_LIGHTHOUSE_2F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 11 | `OLIVINE_LIGHTHOUSE_1F` | 3 | -| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 2 | -| 3 | 16 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 4 | -| 4 | 17 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 5 | -| 5 | 16 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 4 | -| 6 | 17 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 5 | - -`OLIVINE_LIGHTHOUSE_3F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 1 | -| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_2F` | 2 | -| 3 | 9 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 4 | -| 4 | 16 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 5 | -| 5 | 17 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 6 | -| 6 | 16 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 5 | -| 7 | 17 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 6 | -| 8 | 8 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 7 | -| 9 | 9 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 8 | - -`OLIVINE_LIGHTHOUSE_4F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 1 | -| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 2 | -| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 3 | -| 4 | 9 | 5 | `OLIVINE_LIGHTHOUSE_3F` | 3 | -| 5 | 16 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 6 | -| 6 | 17 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 7 | -| 7 | 8 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 8 | -| 8 | 9 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 9 | -| 9 | 16 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 4 | -| 10 | 17 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 5 | - -`OLIVINE_LIGHTHOUSE_5F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_6F` | 1 | -| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 2 | -| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 3 | -| 4 | 16 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 9 | -| 5 | 17 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 10 | -| 6 | 16 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 2 | -| 7 | 17 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 3 | - -`OLIVINE_LIGHTHOUSE_6F`: - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_5F` | 1 | -| 2 | 16 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 6 | -| 3 | 17 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 7 | - -**Which warps are ladders and which are pits.** Derived by decoding each floor's `.blk` against -`data/tilesets/lighthouse_collision.asm`. Block `$3a` and `$31` carry `LADDER` in their bottom-right -tile; block `$28` is `tilecoll FLOOR, FLOOR, PIT, PIT`, i.e. its two bottom tiles drop you a floor. - -| floor | tile | block | kind | goes to | -|---|---|---|---|---| -| 1F | (10, 17), (11, 17) | `$2e` bottom tiles `WARP_CARPET_DOWN` | exit door | `OLIVINE_CITY` | -| 1F | (3, 11) | `$3a` | ladder UP | 2F (3, 11) | -| 2F | (3, 11) | `$31` | ladder DOWN | 1F (3, 11) | -| 2F | (5, 3) | `$3a` | ladder UP | 3F (5, 3) | -| 2F | (16, 13), (17, 13) | `$28` | **PIT** | 1F (16, 13) / (17, 13) | -| 3F | (5, 3) | `$31` | ladder DOWN | 2F (5, 3) | -| 3F | (13, 3) | `$3a` | ladder UP | 4F (13, 3) | -| 3F | (9, 5) | `$3a` | ladder UP | 4F (9, 5) | -| 3F | (16, 11), (17, 11) | `$28` | **PIT** | 2F (16, 11) / (17, 11) | -| 4F | (13, 3) | `$31` | ladder DOWN | 3F (13, 3) | -| 4F | (9, 5) | `$31` | ladder DOWN | 3F (9, 5) | -| 4F | (3, 5) | `$3a` | ladder UP | 5F (3, 5) | -| 4F | (9, 7) | `$3a` | ladder UP | 5F (9, 7) | -| 4F | (8, 3), (9, 3) | `$28` | **PIT** | 3F (8, 3) / (9, 3) | -| 4F | (16, 9), (17, 9) | `$28` | **PIT** | 3F (16, 9) / (17, 9) | -| 5F | (3, 5) | `$31` | ladder DOWN | 4F (3, 5) | -| 5F | (9, 7) | `$31` | ladder DOWN | 4F (9, 7) | -| 5F | (9, 15) | `$3a` | ladder UP | 6F (9, 15) | -| 5F | (16, 7), (17, 7) | `$28` | **PIT** | 4F (16, 7) / (17, 7) | -| 6F | (9, 15) | `$31` | ladder DOWN | 5F (9, 15) | -| 6F | (16, 5), (17, 5) | `$28` | **PIT** | 5F (16, 5) / (17, 5) | - -The pit destination coordinates always match the pit's own coordinates on the floor below - the -warp row on the lower floor exists purely as a landing slot and its own destination is never -triggered (the landing tile is plain `FLOOR`). - -**Coord events** - none on any floor. - -**BG events** - only 5F: `bg_event 3, 13, BGEVENT_ITEM, OlivineLighthouse5FHiddenHyperPotion`. - -**Object events** (trainers a bot will be forced into on the climb) - -| floor | const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| 1F | - | `SPRITE_SAILOR` | 8 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FSailorScript` | -1 | -| 1F | - | `SPRITE_POKEFAN_F` | 16 | 9 | `WALK_UP_DOWN` (0,2) | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FPokefanFScript` | -1 | -| 2F | - | `SPRITE_SAILOR` | 9 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorHuey` | -1 | -| 2F | - | `SPRITE_GENTLEMAN` | 17 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerGentlemanAlfred` | -1 | -| 3F | - | `SPRITE_SAILOR` | 9 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerSailorTerrell` | -1 | -| 3F | - | `SPRITE_GENTLEMAN` | 13 | 5 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerGentlemanPreston` | -1 | -| 3F | - | `SPRITE_YOUNGSTER` | 3 | 9 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperTheo` | -1 | -| 3F | - | `SPRITE_POKE_BALL` | 8 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse3FEther` | `EVENT_OLIVINE_LIGHTHOUSE_3F_ETHER` | -| 4F | - | `SPRITE_SAILOR` | 7 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorKent` | -1 | -| 4F | - | `SPRITE_LASS` | 11 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerLassConnie` | -1 | -| 5F | - | `SPRITE_SAILOR` | 8 | 11 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorErnest` | -1 | -| 5F | - | `SPRITE_YOUNGSTER` | 8 | 3 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerBirdKeeperDenis` | -1 | -| 5F | - | `SPRITE_POKE_BALL` | 15 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FRareCandy` | `EVENT_OLIVINE_LIGHTHOUSE_5F_RARE_CANDY` | -| 5F | - | `SPRITE_POKE_BALL` | 6 | 15 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FGreatBall` | `EVENT_OLIVINE_LIGHTHOUSE_5F_GREAT_BALL` | -| 5F | - | `SPRITE_POKE_BALL` | 2 | 13 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FTMSwagger` | `EVENT_OLIVINE_LIGHTHOUSE_5F_TM_SWAGGER` | - -`TrainerLassConnie` at 4F (11, 2) is "that one lass". The pit at 4F (8, 3) / (9, 3) is the tile -pair immediately left-and-below her, which matches the walkthrough's "falling to the left of that -one lass". - -**6F object events** (`def_object_events`, `maps/OlivineLighthouse6F.asm:270-273`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINELIGHTHOUSE6F_JASMINE` | `SPRITE_JASMINE` | 8 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseJasmine` | `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` (0x6d2) | -| `OLIVINELIGHTHOUSE6F_MONSTER` | `SPRITE_MONSTER` | 9 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseAmphy` | -1 | -| `OLIVINELIGHTHOUSE6F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 4 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse6FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_OLIVINE_LIGHTHOUSE_6F_SUPER_POTION` (0x668) | - -**Scripts of interest** - -- `OlivineLighthouseJasmine` (`44:6ccd`, `maps/OlivineLighthouse6F.asm:11`): - 1. `faceplayer` / `opentext` - 2. `checkitem SECRETPOTION` -> `.BroughtSecretpotion` (`44:6ce7`). Without the potion it falls - into the `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` (0x37) branch and just repeats the request. - 3. `.BroughtSecretpotion`: `writetext JasmineCureAmphyText`, `yesorno`. **`iffalse .Refused`** - - answering No leaves everything unset and the gym stays empty, so a bot must answer Yes. - 4. `takeitem SECRETPOTION`, a long cutscene (`turnobject`, `playmusic MUSIC_HEAL`, - `special RestartMapMusic`, `cry AMPHAROS`, `special FadeOutToWhite` / `FadeInFromWhite`). - 5. **`setevent EVENT_JASMINE_RETURNED_TO_GYM`** (0x20) and - **`clearevent EVENT_OLIVINE_GYM_JASMINE`** (0x6d3) - this pair is the entire gate on the gym. - 6. `readvar VAR_FACING` picks one of three exit movements - (`OlivineLighthouseJasmineLeaves{Up,Down,Right}Movement`) then `disappear OLIVINELIGHTHOUSE6F_JASMINE`. -- `OlivineLighthouseAmphy`: cosmetic; branches on `EVENT_JASMINE_RETURNED_TO_GYM` for the healthy - cry. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_JASMINE_RETURNED_TO_GYM` (0x20) | `constants/event_flags.asm:41` | set by `OlivineLighthouseJasmine`; read by `OlivineGymGuideScript` and `maps/EcruteakCity.asm:47` | the section's real progress bit | -| `EVENT_OLIVINE_GYM_JASMINE` (0x6d3) | `constants/event_flags.asm:1141` | set at new game by `InitializeEventsScript` (`engine/events/std_scripts.asm:511`), cleared by `OlivineLighthouseJasmine` | clear = Jasmine is standing in her gym | -| `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` (0x6d2) | `constants/event_flags.asm:1140` | `disappear` at the end of the cure | set = she has left the lighthouse | -| `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` (0x37) | `constants/event_flags.asm:64` | `OlivineLighthouseJasmine` | first-visit text latch only | - -**Items** - `SECRETPOTION` is consumed (`takeitem SECRETPOTION`). Nothing is given. - -**Wild encounters** - none on any lighthouse floor (indoor `DUNGEON`, no table). - ---- - -### MAP_OLIVINE_GYM - -- Script: `maps/OlivineGym.asm` (`51:410e OlivineGym_MapScripts`, `51:4507 OlivineGym_MapEvents`) -- Blocks: `maps/OlivineGym.blk` (`2b:7c7b OlivineGym_Blocks`) -- Header: `data/maps/maps.asm:51` -> `map OlivineGym, TILESET_CHAMPIONS_ROOM, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:40` -> `map_const OLIVINE_GYM, 5, 8` = 10 x 16 tiles (group 1, id 2) -- Attributes: `data/maps/attributes.asm:473` -> `map_attributes OlivineGym, OLIVINE_GYM, $00`, no connections -- `def_scene_scripts` and `def_callbacks` are both **empty** - no scene variable, no map callbacks. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 15 | `OLIVINE_CITY` | 2 | -| 2 | 5 | 15 | `OLIVINE_CITY` | 2 | - -**Coord events** (`def_coord_events`) - **none**. There is no trip-wire in this gym; every -interaction is a `faceplayer` talk. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 13 | `BGEVENT_READ` | `OlivineGymStatue` | -| 6 | 13 | `BGEVENT_READ` | `OlivineGymStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINEGYM_JASMINE` | `SPRITE_JASMINE` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `OlivineGymJasmineScript` | `EVENT_OLIVINE_GYM_JASMINE` | -| `OLIVINEGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `OlivineGymGuideScript` | -1 | - -The walkthrough's "there are no apprentices of any kind" is literally true in the asm: the object -table has exactly two rows and neither is `OBJECTTYPE_TRAINER`. - -**Walkable shape** (decoded from `maps/OlivineGym.blk` against -`data/tilesets/champions_room_collision.asm`, `.` = FLOOR, `#` = WALL, `W` = `WARP_CARPET_DOWN`, -`U` = `UP_WALL`; x across 0..9, y down 0..15): - -``` - y0 # # # # # # # # # # - y1 # # # # # # # # # # - y2 # # # U U U U # # # - y3 # # # . . . . # # # - y4 # # # . . . . # # # - y5 # # # # . . # # # # - y6 # # # # . . # # # # - y7 # # # . . . . # # # - y8 # # . . . . . . # # - y9 # # # # . . # # # # -y10 # # # . . . . # # # -y11 # # # . . . . # # # -y12 # # . # . . # . # # -y13 # # . # . . # . # # -y14 # . . . . . . . . # -y15 # . . . W W . . . # -``` - -Columns x=4 and x=5 are floor from y=14 straight up to y=3, so a bot can enter at (4, 15) or -(5, 15) and hold UP to reach Jasmine at (5, 3) with no turns. The two statues are the wall tiles at -(3, 13) and (6, 13); the gym guide stands at (7, 13), reachable from (7, 14). - -**Scripts of interest** - -- `OlivineGymJasmineScript` (`51:4110`, `maps/OlivineGym.asm:10`). Opcode-by-opcode: - - ``` - faceplayer / opentext - checkevent EVENT_BEAT_JASMINE -> iftrue .FightDone - writetext Jasmine_SteelTypeIntro / waitbutton / closetext - winlosstext Jasmine_BetterTrainer, 0 - loadtrainer JASMINE, JASMINE1 - startbattle - reloadmapafterbattle - setevent EVENT_BEAT_JASMINE - opentext / writetext Text_ReceivedMineralBadge - playsound SFX_GET_BADGE / waitsfx - setflag ENGINE_MINERALBADGE - readvar VAR_BADGES - scall OlivineGymActivateRockets - .FightDone: - checkevent EVENT_GOT_TM23_IRON_TAIL -> iftrue .GotIronTail - writetext Jasmine_BadgeSpeech / promptbutton - verbosegiveitem TM_IRON_TAIL - iffalse .NoRoomForIronTail - setevent EVENT_GOT_TM23_IRON_TAIL - writetext Jasmine_IronTailSpeech / waitbutton / closetext / end - ``` - - Two bot-relevant consequences: (a) `winlosstext ..., 0` means there is **no loss-warp script**, - losing just blacks you out normally; (b) if the bag has no room in the TM pocket, - `verbosegiveitem` returns false and `EVENT_GOT_TM23_IRON_TAIL` is **not** set - re-talking to - Jasmine retries the give from `.FightDone`. Keep TM room free. - -- `OlivineGymActivateRockets` (`51:4159`): the `scall`ed tail, dispatching on the `readvar - VAR_BADGES` value that was just pushed: - - ``` - ifequal 7, .RadioTowerRockets -> jumpstd RadioTowerRocketsScript - ifequal 6, .GoldenrodRockets -> jumpstd GoldenrodRocketsScript - end - ``` - - `VAR_BADGES` is `CountSetBits` over the two `wBadges` bytes - (`engine/overworld/variables.asm:80-86`), so it counts Johto **and** Kanto badges. Since - `setflag ENGINE_MINERALBADGE` runs first, MINERALBADGE is included in the count. On the - walkthrough's route (Zephyr, Hive, Plain, Fog, Storm, Mineral) the count is **6** and - `GoldenrodRocketsScript` fires: `clearevent EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` - (`engine/events/std_scripts.asm:251-253`), which makes the two Rocket grunts appear in Goldenrod - City at (28, 20) and (8, 15) (`maps/GoldenrodCity.asm:376-377`). Every gym carries the same - `scall` (`maps/VioletGym.asm:31`, `maps/MahoganyGym.asm:34`, ...), so whichever gym hands you - badge 6 is the one that arms the Rockets - beating Jasmine *before* Chuck simply moves the - trigger to Chuck. - -- `OlivineGymGuideScript` (`51:4168`): three-way on `EVENT_BEAT_JASMINE`, then - `EVENT_JASMINE_RETURNED_TO_GYM`. Text only, no flags written. -- `OlivineGymStatue` (`51:418a`): `checkflag ENGINE_MINERALBADGE`, `jumpstd GymStatue1Script` - before, `gettrainername STRING_BUFFER_4, JASMINE, JASMINE1` + `jumpstd GymStatue2Script` after. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_OLIVINE_GYM_JASMINE` (0x6d3) | `constants/event_flags.asm:1141` | set by `InitializeEventsScript`; cleared by `OlivineLighthouseJasmine` | **precondition**: while set, the gym is empty and unbeatable | -| `EVENT_BEAT_JASMINE` (0x4c1) | `constants/event_flags.asm:710` | set by `OlivineGymJasmineScript` | postcondition of the battle | -| `ENGINE_MINERALBADGE` (engine flag id 30) | `constants/engine_flags.asm:42`, `data/events/engine_flags.asm:50` (`engine_flag wJohtoBadges, MINERALBADGE`) | `setflag` here, `checkflag` in `OlivineGymStatue` | the badge bit; `MINERALBADGE` bit index in `wJohtoBadges` is `constants/ram_constants.asm:256` | -| `EVENT_GOT_TM23_IRON_TAIL` (0x0d) | `constants/event_flags.asm:19` | `OlivineGymJasmineScript` | one-time TM guard | -| `EVENT_JASMINE_RETURNED_TO_GYM` (0x20) | `constants/event_flags.asm:41` | read by `OlivineGymGuideScript` | flavour only inside the gym | -| `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` (0x6cd) | `constants/event_flags.asm` | cleared by `GoldenrodRocketsScript` | side effect of reaching 6 badges | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_IRON_TAIL` (TM23, item id `$d6`, `constants/item_constants.asm:243`) | `verbosegiveitem` after the battle | `OlivineGymJasmineScript` | `EVENT_GOT_TM23_IRON_TAIL` | -| 3500 prize money | battle reward | see below | - | - -Prize money check: `TrainerClassAttributes` for Jasmine (`data/trainers/attributes.asm`, the sixth -gym-leader block) is `db HYPER_POTION, NO_ITEM ; items` and `db 25 ; base reward`. -`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300-317`) sets -`wBattleReward = 25 * wCurPartyLevel` = `25 * 35` = **875**, and the payout loop in -`engine/battle/core.asm:2340-2360` adds `wBattleReward` **four times** (`ld c, 4`). 875 x 4 = -**3500**, which matches the walkthrough exactly. (Note Jasmine's AI holds a `HYPER_POTION`.) - -Badge effect check: `BadgeStatBoosts` (`engine/battle/core.asm:6533-6588`) - the comment block -states "MineralBadge: Defense", and the code deliberately swaps the PlainBadge and MineralBadge -bits before the every-other-badge walk, boosting the stat at `wBattleMonDefense` by 1/8 -(`BoostStat`). Separately `data/types/badge_type_boosts.asm:7` maps MINERALBADGE to `STEEL` for the -type-matching trainer-card/boost table. So the walkthrough's "raises your Pokemon's defense -slightly" is correct. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `JASMINE` / `JASMINE1` | `JASMINE` (trainer class 6, `constants/trainer_constants.asm:42-43`) | 1 | `JasmineGroup` (`0e:5a42`, `data/trainers/parties.asm:53`) | `OlivineGymJasmineScript` | none (gym leaders have no phone row) | - -`JasmineGroup`, verbatim (`TRAINERTYPE_MOVES`): - -``` -db "JASMINE@", TRAINERTYPE_MOVES -db 30, MAGNEMITE, THUNDERBOLT, SUPERSONIC, SONICBOOM, THUNDER_WAVE -db 30, MAGNEMITE, THUNDERBOLT, SUPERSONIC, SONICBOOM, THUNDER_WAVE -db 35, STEELIX, SCREECH, SUNNY_DAY, ROCK_THROW, IRON_TAIL -db -1 ; end -``` - -EXP check against the walkthrough's numbers: trainer EXP is `base * level / 7 * 3 / 2`. -Magnemite base exp 89 (`data/pokemon/base_stats/magnemite.asm:8`) -> `89*30/7 = 381`, `*3/2 = 571`. -Steelix base exp 196 (`data/pokemon/base_stats/steelix.asm:8`) -> `196*35/7 = 980`, `*3/2 = 1470`. -Both match. - -**Wild encounters** - none (`INDOOR`). - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Jasmine is not in her gym | `maps/OlivineGym.asm:212` object row, event flag `EVENT_OLIVINE_GYM_JASMINE`; visibility semantics in `engine/overworld/scripting.asm:879-898` | flag must be **clear** | `clearevent EVENT_OLIVINE_GYM_JASMINE` at `maps/OlivineLighthouse6F.asm:69` | -| Jasmine will not leave the lighthouse | `maps/OlivineLighthouse6F.asm:14` `checkitem SECRETPOTION` | `SECRETPOTION` in the bag (from the Cianwood Pharmacy, section 8) | hand it over and answer **Yes** to `JasmineCureAmphyText` (`yesorno` at line 29; `iffalse .Refused` aborts the whole scene) | -| Cannot Fly to Olivine / Goldenrod | `engine/events/overworld.asm:544-547` (`FlyFunction.TryFly` -> `ld de, ENGINE_STORMBADGE` / `call CheckBadge`), mirrored in the port at `src/world/gen2/FieldMoves.lua:107` (`FLY = "STORM"`), plus `ENGINE_FLYPOINT_OLIVINE` (id 70) / `ENGINE_FLYPOINT_GOLDENROD` (id 69) set by each town's `MAPCALLBACK_NEWMAP` (`maps/OlivineCity.asm:22`, `maps/GoldenrodCity.asm:24`) | `ENGINE_STORMBADGE` + having visited the town on foot | both already true entering this section | -| Haircut unavailable | `OlderHaircutBrotherScript` `readvar VAR_WEEKDAY` + `GoldenrodUndergroundCheckDayOfWeekCallback` | in-game weekday must be TUE/THU/SAT for the 500 brother | wait for the day, or use the younger brother SUN/WED/FRI for 300 | -| Second haircut same day | `checkflag ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (engine flag 89) | flag clear | daily reset | -| 4F of the lighthouse is a dead end from the (13, 3) ladder | geometry: 4F pit at (8, 3)/(9, 3), block `$28` in `maps/OlivineLighthouse4F.blk`, warps 7/8 | must step into the pit and re-climb via 3F warp 3 at (9, 5) | no flag, purely navigational | -| No TM pocket room | `verbosegiveitem TM_IRON_TAIL` -> `iffalse .NoRoomForIronTail` (`maps/OlivineGym.asm:35-36`) | free TM slot | drop a TM and re-talk to Jasmine | - -Nothing in this section requires `CUT`, `SURF`, `STRENGTH`, `WHIRLPOOL`, `WATERFALL` or `FLASH`. - ---- - -## 4. Bot checklist - -Optional Eevee-happiness detour (steps 1-5) can be skipped entirely. - -1. **Fly** to `SPAWN_GOLDENROD`. Precondition: `ENGINE_STORMBADGE` set and `ENGINE_FLYPOINT_GOLDENROD` (id 69) set. Postcondition: on `GOLDENROD_CITY`. -2. `GOLDENROD_CITY`: walk to (11, 29), step onto warp 15. Postcondition: on `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` at warp 5 (4, 29). -3. `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`: walk to (5, 25), step onto warp 4. Postcondition: on `GOLDENROD_UNDERGROUND` at warp 2 (3, 34). -4. `GOLDENROD_UNDERGROUND`: precondition `VAR_WEEKDAY in {TUESDAY, THURSDAY, SATURDAY}` and `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` clear and money >= 500. Walk to (6, 14) and face RIGHT (the brother stands at (7, 14) facing left); talk. Answer **Yes**, pick the Eevee. Postcondition: `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` set, money -500, Eevee happiness +1/+3/+5. -5. Retrace warps out (warp 2 at (3, 34) -> switch-room warp 5/6 at (4, 29)/(5, 29) -> `GOLDENROD_CITY`). -6. **Fly** to `SPAWN_OLIVINE`. Precondition: `ENGINE_FLYPOINT_OLIVINE` (id 70). Postcondition: on `OLIVINE_CITY`. -7. `OLIVINE_CITY`: walk to (29, 27), step onto warp 9. Postcondition: on `OLIVINE_LIGHTHOUSE_1F` at warp 1 (10, 17). -8. `1F`: walk to (3, 11), step on ladder-up. -> `2F` (3, 11). -9. `2F`: walk to (5, 3), ladder-up. -> `3F` (5, 3). (Sailor Huey (9, 3) and Gentleman Alfred (17, 8) are already beaten; their `EVENT_BEAT_*` flags guard re-battle.) -10. `3F`: walk to (13, 3), ladder-up. -> `4F` (13, 3). -11. `4F`: walk to the pit at (8, 3) or (9, 3) - "left of Lass Connie at (11, 2)". Step in. -> `3F` (8, 3)/(9, 3). -12. `3F`: walk to (9, 5), ladder-up. -> `4F` (9, 5). -13. `4F`: walk to (9, 7), ladder-up. -> `5F` (9, 7). (Alternative: 4F (3, 5) -> 5F (3, 5).) -14. `5F`: walk to (9, 15), ladder-up. -> `6F` (9, 15). Confirm arrival by `MUSIC_VIOLET_CITY` playing (6F is the only lighthouse floor with it). -15. `6F`: precondition `SECRETPOTION` in bag and `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` clear. Walk to (8, 9) and face UP (Jasmine at (8, 8) faces down) and talk. Answer **Yes** to `JasmineCureAmphyText`. Sit through ~120 frames of cutscene (`pause 60`, `pause 15`, several `pause 10`). Postcondition: `SECRETPOTION` removed, `EVENT_JASMINE_RETURNED_TO_GYM` set, `EVENT_OLIVINE_GYM_JASMINE` **cleared**, `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` set. -16. Descend: `6F` pit at (16, 5)/(17, 5) -> `5F`; `5F` pit at (16, 7)/(17, 7) -> `4F`; `4F` pit at (16, 9)/(17, 9) -> `3F`; `3F` pit at (16, 11)/(17, 11) -> `2F`; `2F` pit at (16, 13)/(17, 13) -> `1F`. Then `1F` (10, 17)/(11, 17) facing DOWN -> `OLIVINE_CITY` warp 9 at (29, 27). -17. `OLIVINE_CITY`: (optional) heal at the Pokecenter, warp 1 at (13, 21). Save before the gym. -18. `OLIVINE_CITY`: walk to (10, 11), step onto warp 2. Postcondition: on `OLIVINE_GYM` at warp 1 (4, 15). -19. `OLIVINE_GYM`: (optional) talk to the guide by walking to (7, 14) and facing UP. -20. `OLIVINE_GYM`: hold UP along x=5 from (5, 14) to (5, 4), face UP, talk to Jasmine at (5, 3). Precondition: `EVENT_OLIVINE_GYM_JASMINE` clear, `EVENT_BEAT_JASMINE` clear, a free TM slot. Battle `loadtrainer JASMINE, JASMINE1`. -21. Win. Postconditions in order: `EVENT_BEAT_JASMINE` set -> `ENGINE_MINERALBADGE` set -> money +3500 -> `VAR_BADGES` re-read -> if it equals 6, `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` cleared (Rockets appear in Goldenrod) -> `verbosegiveitem TM_IRON_TAIL` -> `EVENT_GOT_TM23_IRON_TAIL` set. -22. Leave via (4, 15)/(5, 15) facing DOWN -> `OLIVINE_CITY` warp 2 (10, 11). - -Battle notes for the driver: lead a Fire or Fighting attacker; both Magnemite carry -`THUNDER_WAVE` (paralysis risk) and `SONICBOOM` (fixed 20 damage, ignores type - it will connect on -a Ground type); Steelix has `IRON_TAIL` and `SUNNY_DAY` (which boosts your own Fire moves too). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers, blocks, warps, coord/bg/object events for all of these maps | `src/import/RomExtractorGen2.lua` (`readItemBall` ~line 2875, object/trainer struct decode ~line 2961) | implemented (data-driven from ROM, no hand-ported Olivine map) | -| Script opcodes used by `OlivineGymJasmineScript` (`faceplayer`, `checkevent`, `winlosstext`, `loadtrainer`, `startbattle`, `reloadmapafterbattle`, `setflag`, `readvar`, `scall`, `verbosegiveitem`, `jumpstd`, `gettrainername`) | `src/script/gen2/Vm.lua` (`faceplayer` L164, `gettrainername` L407, `verbosegiveitem` L490, `jumpstd` L742, `loadtrainer` L806, `winlosstext` L918); opcode table `src/script/gen2/Opcodes.lua` | implemented | -| `VAR_BADGES` (`CountSetBits` over Johto+Kanto) | `src/world/gen2/World.lua:1240-1245` | implemented | -| Fly gate + flypoint engine flags | `src/world/gen2/FieldMoves.lua:103-130`, `FLYPOINTS` L340-368 (`OLIVINE` flag 70, `GOLDENROD` flag 69) | implemented | -| Pit / warp-carpet collision (the lighthouse climb) | `src/world/gen2/Permissions.lua:162-180` (`COLL_PIT` 0x60 / 0x68 and the `$7x` warp nybble; carpet direction table) | implemented | -| Haircut brothers (weighted roll + happiness deltas) | `src/script/gen2/Specials.lua:1577-1615` (`HAIRCUT_TABLES`), `src/core/gen2/Happiness.lua:79-84` | implemented | -| Trainer prize money incl. the x4 payout loop | `src/battle/gen2/Prize.lua` (header comment cites `ld c, 4`) | implemented | -| Gym-leader battle music for `JASMINE` | `src/battle/gen2/BattleMusic.lua:25`, `src/battle/gen2/Battle.lua:86` | implemented | -| Eevee -> Espeon/Umbreon (`EVOLVE_HAPPINESS`, `HAPPINESS_TO_EVOLVE` 220, `TR_MORNDAY`/`TR_NITE`) | `src/core/gen2/Evolution.lua:40-52,143` | implemented | -| Trainer card badge display | `src/ui/gen2/TrainerCard.lua`; leader palette list at `src/import/RomExtractorGen2.lua:4982` | implemented | -| **`BadgeStatBoosts`** - the MINERALBADGE +1/8 Defense (and the PlainBadge/MineralBadge bit swap) | nothing found. `grep -i badge src/battle/gen2/` returns no hits; the only badge-boost code in the repo is Gen 1's (`src/battle/Damage.lua:24`, `src/battle/BattleState.lua:439`) | **missing** | -| **`BadgeTypeBoosts`** (`data/types/badge_type_boosts.asm`, MINERALBADGE -> STEEL) | no hits for `BadgeTypeBoosts` anywhere in `src/` | **missing** | -| A driver that walks the lighthouse or fights a gym leader | `tests/drivers/gold_*.lua` - `gold_trainer_smoke.lua` and `gold_battle_smoke.lua` exist but neither names Olivine or Jasmine | **missing** (no section-9 driver) | - ---- - -## 6. Unresolved / verify by hand - -- **"Climb up the Olivine Lighthouse in the EXACT same way you came up."** The ladder/pit graph in - section 2 is fully decoded from the `.blk` files and `data/tilesets/lighthouse_collision.asm`, but - I did **not** flood-fill each floor to prove the specific walking path between a ladder and the - next pit. Steps 8-14 of the checklist are the geometrically obvious route (and the pit at 4F - (8, 3)/(9, 3) really is left of Lass Connie at (11, 2)), but the intermediate tile-by-tile route - on 3F, 4F and 5F should be confirmed in-game before a driver hard-codes it. -- **"Talk to the man who is running the second/middle shop."** The asm has four merchant objects in - one column at x=7: gramps y=11, older brother y=14, younger brother y=15, granny y=21, and - `GoldenrodUndergroundCheckDayOfWeekCallback` hides most of them on any given day. "Middle" is - therefore day-dependent; the unambiguous identifier is the **500 price** - (`GOLDENRODUNDERGROUND_OLDER_HAIRCUT_PRICE`), i.e. the older brother at (7, 14), available - TUE/THU/SAT only. The walkthrough does not mention the weekday restriction at all. -- **"Eevee's happiness meter should get a nice boost."** The older brother's roll is +1 (30%), - +3 (50%) or +5 (20%) at happiness < 200. The younger brother's top roll is +10. The walkthrough's - implied "500 is the better option" is not supported by - `data/events/happiness_probabilities.asm` / `data/events/happiness_changes.asm`. -- **"Say Yes to the fact that Ampharos will be cured by the medicine."** Confirmed as a hard - requirement (`yesorno` / `iffalse .Refused` at `maps/OlivineLighthouse6F.asm:29-30`), but note the - refusal path is *recoverable*: it sets nothing, so the player can simply talk again. -- **EXP values (571 / 1470)** are reproduced by the standard trainer formula from the base-exp bytes - I read, but I did not open the EXP-award routine itself to confirm the `*3/2` trainer multiplier - in this disassembly; treat those two numbers as arithmetic agreement rather than a code citation. -- **`data/trainers/attributes.asm` Jasmine row**: the file has no per-class labels, only ordered - comment headers. I identified Jasmine's row by the `; Jasmine` comment (sixth block, `db 25 ; - base reward`, `db HYPER_POTION, NO_ITEM`). If that comment ever drifts from the class order in - `constants/trainer_constants.asm`, the 3500 derivation drifts with it. -- The walkthrough's "you can go to the Lake of Rage first" branch is out of scope here; nothing in - the Olivine Gym asm checks for it. diff --git a/docs/gold-walkthrough/asm-walk/section-10-the-lake-of-rage-and-shiny-gyarados.md b/docs/gold-walkthrough/asm-walk/section-10-the-lake-of-rage-and-shiny-gyarados.md deleted file mode 100644 index 4a28fe33..00000000 --- a/docs/gold-walkthrough/asm-walk/section-10-the-lake-of-rage-and-shiny-gyarados.md +++ /dev/null @@ -1,1101 +0,0 @@ -# Section 10 - The Lake of Rage and Shiny Gyarados - -Source: `../section-10-the-lake-of-rage-and-shiny-gyarados.txt` -Maps covered: `MAP_ECRUTEAK_CITY` (fly-in only), `MAP_ROUTE_42_ECRUTEAK_GATE`, -`MAP_ROUTE_42`, `MAP_MAHOGANY_TOWN`, `MAP_MAHOGANY_MART_1F`, -`MAP_MAHOGANY_RED_GYARADOS_SPEECH_HOUSE`, `MAP_MAHOGANY_POKECENTER_1F`, -`MAP_ROUTE_43_MAHOGANY_GATE`, `MAP_ROUTE_43`, `MAP_ROUTE_43_GATE`, -`MAP_LAKE_OF_RAGE`, `MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE`, -`MAP_LAKE_OF_RAGE_MAGIKARP_HOUSE` - -Badges / key milestones in this section: no badge. The milestones are -`ENGINE_FLYPOINT_MAHOGANY`, `ENGINE_FLYPOINT_LAKE_OF_RAGE`, catching or -defeating the Red Gyarados (`EVENT_LAKE_OF_RAGE_RED_GYARADOS`), the `RED_SCALE`, -and `EVENT_DECIDED_TO_HELP_LANCE`, which arms -`SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` and opens the Team Rocket base in -the next section. - -A note on coordinates: every table below is transcribed verbatim from the map's -`_MapEvents` block. `warp_event`, `coord_event`, `bg_event` and `object_event` -all use the same map coordinate space (the `object_event` macro in -`macros/scripts/maps.asm` adds the +4 border offset itself), so the numbers are -directly comparable. A map declared `map_const NAME, W, H` spans x in -`0 .. 2W-1` and y in `0 .. 2H-1`. - -A note on the `event flag` column of `object_event`: `engine/overworld/map_objects_2.asm` -`CheckObjectFlag` masks (hides) the object when the flag is **set**, and `-1` -means always visible. So "set = gone". - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | Fly (`SPAWN_ECRUTEAK`, `data/maps/spawn_points.asm` `spawn ECRUTEAK_CITY, 23, 28`) | warp 1/2 at (35,26)/(35,27) -> `ROUTE_42_ECRUTEAK_GATE` 1/2 | "Fly to Ecruteak City, then head east onto Route 42" | -| 2 | `MAP_ROUTE_42_ECRUTEAK_GATE` | `maps/Route42EcruteakGate.asm` | warps 1/2 at (0,4)/(0,5) | warps 3/4 at (9,4)/(9,5) -> `ROUTE_42` 1/2 | gate hut between Ecruteak and Route 42 | -| 3 | `MAP_ROUTE_42` | `maps/Route42.asm` | warps 1/2 at (0,8)/(0,9) | east map connection -> `MAHOGANY_TOWN` | Ultra Ball, Surf the two lakes, Cut to the apricorn trees, Super Potion, three trainers | -| 4 | `MAP_MAHOGANY_TOWN` | `maps/MahoganyTown.asm` | west map connection from `ROUTE_42` | warp 5 at (9,1) -> `ROUTE_43_MAHOGANY_GATE` 3 | heal, Mahogany Mart, Red Gyarados speech house, blocked east exit | -| 5 | `MAP_ROUTE_43_MAHOGANY_GATE` | `maps/Route43MahoganyGate.asm` | warps 3/4 at (4,7)/(5,7) | warps 1/2 at (4,0)/(5,0) -> `ROUTE_43` 1/2 | gate hut north out of Mahogany | -| 6 | `MAP_ROUTE_43` | `maps/Route43.asm` | warps 1/2 at (9,51)/(10,51) | north map connection -> `LAKE_OF_RAGE` | five trainers, Max Ether, Bitter Berry tree, the Rocket toll gate you are told to walk around | -| 7 | `MAP_ROUTE_43_GATE` | `maps/Route43Gate.asm` | Route 43 warp 3 at (17,35) (southbound) or warps 4/5 at (17,31)/(18,31) (northbound) | mirrored warps | the 1000-yen Rocket shakedown the walkthrough tells you to skip | -| 8 | `MAP_LAKE_OF_RAGE` | `maps/LakeOfRage.asm` | south map connection from `ROUTE_43` | Fly out (`ENGINE_FLYPOINT_LAKE_OF_RAGE`) once Lance is talked to | Red Gyarados, Red Scale, Lance, Wesley of Wednesday, Max Ether, TM43 | -| 9 | `MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE` | `maps/LakeOfRageHiddenPowerHouse.asm` | Lake of Rage warp 1 at (7,3) | warps 1/2 at (2,7)/(3,7) | TM10 Hidden Power | -| 10 | `MAP_LAKE_OF_RAGE_MAGIKARP_HOUSE` | `maps/LakeOfRageMagikarpHouse.asm` | Lake of Rage warp 2 at (27,31) | warps 1/2 at (2,7)/(3,7) | Fishing Guru flavour; the Magikarp length contest only opens after `EVENT_CLEARED_ROCKET_HIDEOUT` | - -Spill into the next section: after Lance is helped, `LakeOfRageLanceScript` does -`setmapscene MAHOGANY_MART_1F, SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS`, and -the walkthrough ends with "fly back to Mahogany Town". `MAP_MAHOGANY_MART_1F`'s -staircase scene, `MAP_TEAM_ROCKET_BASE_B1F..B3F` and `MAP_MAHOGANY_GYM` belong to -the next section; only the pieces of Mahogany Mart reachable *before* that are -documented here. - -Also out of scope but named by the walkthrough: `maps/MrPokemonsHouse.asm` -`MrPokemonsHouse_MrPokemonScript.RedScale` is the `RED_SCALE` -> `EXP_SHARE` -trade (`checkitem RED_SCALE` / `verbosegiveitem EXP_SHARE` / `takeitem RED_SCALE`). -Mt. Mortar (`MOUNT_MORTAR_1F_OUTSIDE`, reached from Route 42 warps 3/4/5) is -explicitly deferred by the walkthrough. - ---- - -## 2. Maps - -### MAP_ECRUTEAK_CITY - -Only the eastern exit matters here. - -- Script: `maps/EcruteakCity.asm` -- Blocks: `maps/EcruteakCity.blk` -- Header: `data/maps/maps.asm:173` -> `map EcruteakCity, TILESET_JOHTO, TOWN, LANDMARK_ECRUTEAK_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:159` -> `map_const ECRUTEAK_CITY, 20, 18` -- Connections (`data/maps/attributes.asm:147`): south `ROUTE_37` (+5), west `ROUTE_38` (+5), east `ROUTE_42` (+9) -- Fly spawn: `data/maps/spawn_points.asm:35` -> `spawn ECRUTEAK_CITY, 23, 28` - -**Warps** (the two this section uses, `maps/EcruteakCity.asm:229`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 35 | 26 | `ROUTE_42_ECRUTEAK_GATE` | 1 | -| 2 | 35 | 27 | `ROUTE_42_ECRUTEAK_GATE` | 2 | - ---- - -### MAP_ROUTE_42_ECRUTEAK_GATE - -- Script: `maps/Route42EcruteakGate.asm` -- Blocks: shared, `data/maps/blocks.asm` (no dedicated `.blk`) -- Header: `data/maps/maps.asm:71` -> `map Route42EcruteakGate, TILESET_GATE, GATE, LANDMARK_ROUTE_42, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:59` -> `map_const ROUTE_42_ECRUTEAK_GATE, 5, 4` -- Connections: none (indoor gate) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ECRUTEAK_CITY` | 1 | -| 2 | 0 | 5 | `ECRUTEAK_CITY` | 2 | -| 3 | 9 | 4 | `ROUTE_42` | 1 | -| 4 | 9 | 5 | `ROUTE_42` | 2 | - -**Coord events** - none. -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| (unnamed, index 2) | `SPRITE_OFFICER` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route42EcruteakGateOfficerScript` | -1 | - -No gate check here: the officer is a `jumptextfaceplayer` only. This gate is -always open. - ---- - -### MAP_ROUTE_42 - -- Script: `maps/Route42.asm` (symbol `Route42_MapEvents` = `4c:5e86`, `pokegold.sym:23813`) -- Blocks: `maps/Route42.blk` -- Header: `data/maps/maps.asm:72` -> `map Route42, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_42, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:60` -> `map_const ROUTE_42, 30, 9` (x 0..59, y 0..17) -- Connections (`data/maps/attributes.asm:231`): west `ECRUTEAK_CITY` (-9), east `MAHOGANY_TOWN` (0) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 8 | `ROUTE_42_ECRUTEAK_GATE` | 3 | -| 2 | 0 | 9 | `ROUTE_42_ECRUTEAK_GATE` | 4 | -| 3 | 10 | 5 | `MOUNT_MORTAR_1F_OUTSIDE` | 1 | -| 4 | 28 | 9 | `MOUNT_MORTAR_1F_OUTSIDE` | 2 | -| 5 | 46 | 7 | `MOUNT_MORTAR_1F_OUTSIDE` | 3 | - -**Coord events** - `def_coord_events` is empty. Nothing on Route 42 trips a scene. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 10 | `BGEVENT_READ` | `Route42Sign1` | -| 7 | 5 | `BGEVENT_READ` | `MtMortarSign1` | -| 45 | 9 | `BGEVENT_READ` | `MtMortarSign2` | -| 54 | 8 | `BGEVENT_READ` | `Route42Sign2` | -| 16 | 11 | `BGEVENT_ITEM` | `Route42HiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_ROUTE_42_HIDDEN_MAX_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE42_FISHER` | `SPRITE_FISHER` | 40 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerFisherChris` | -1 | -| `ROUTE42_POKEFAN_M` | `SPRITE_POKEFAN_M` | 51 | 9 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerHikerBenjamin` | -1 | -| `ROUTE42_SUPER_NERD` | `SPRITE_SUPER_NERD` | 47 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPokemaniacShane` | -1 | -| `ROUTE42_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 27 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree1` -> `fruittree FRUITTREE_ROUTE_42_1` | -1 | -| `ROUTE42_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 28 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree2` -> `fruittree FRUITTREE_ROUTE_42_2` | -1 | -| `ROUTE42_FRUIT_TREE3` | `SPRITE_FRUIT_TREE` | 29 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree3` -> `fruittree FRUITTREE_ROUTE_42_3` | -1 | -| `ROUTE42_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route42UltraBall` -> `itemball ULTRA_BALL` | `EVENT_ROUTE_42_ULTRA_BALL` | -| `ROUTE42_POKE_BALL2` | `SPRITE_POKE_BALL` | 33 | 8 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route42SuperPotion` -> `itemball SUPER_POTION` | `EVENT_ROUTE_42_SUPER_POTION` | - -**Scripts of interest** - -- `TrainerFisherChris` - `trainer FISHER, CHRIS1, EVENT_BEAT_FISHER_CHRIS, ...`. - After-battle branch is the standard phone-number ladder: `checkevent - EVENT_CHRIS_READY_FOR_REMATCH` -> rematch; else `checkcellnum - PHONE_FISHER_CHRIS`; else `setevent EVENT_CHRIS_ASKED_FOR_PHONE_NUMBER` and - `askforphonenumber PHONE_FISHER_CHRIS`. The rematch arm picks `CHRIS2` if - `EVENT_CLEARED_ROCKET_HIDEOUT` and `CHRIS3` if `EVENT_BEAT_ELITE_FOUR`; neither - applies in this section. -- `TrainerPokemaniacShane`, `TrainerHikerBenjamin` - plain - `endifjustbattled` / `writetext` / `end`, no flags beyond their - `EVENT_BEAT_*`. -- `Route42UltraBall` / `Route42SuperPotion` - `itemball`; the one-time flag is - the object's own event flag. -- `Route42HiddenMaxPotion` - `hiddenitem MAX_POTION, EVENT_ROUTE_42_HIDDEN_MAX_POTION` - at (16,11). The walkthrough never mentions it. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_42_ULTRA_BALL` | `constants/event_flags.asm:1108` | object row / `itemball` | set = ball at (6,4) already taken | -| `EVENT_ROUTE_42_SUPER_POTION` | `constants/event_flags.asm:1109` | object row / `itemball` | set = ball at (33,8) already taken | -| `EVENT_ROUTE_42_HIDDEN_MAX_POTION` | `constants/event_flags.asm:183` | `Route42HiddenMaxPotion` | set = hidden Max Potion consumed | -| `EVENT_BEAT_FISHER_CHRIS` | `constants/event_flags.asm:591` | `TrainerFisherChris` | set = Chris beaten | -| `EVENT_BEAT_POKEMANIAC_SHANE` | `constants/event_flags.asm:753` | `TrainerPokemaniacShane` | set = Shane beaten | -| `EVENT_BEAT_HIKER_BENJAMIN` | `constants/event_flags.asm:823` | `TrainerHikerBenjamin` | set = Benjamin beaten | -| `EVENT_CHRIS_ASKED_FOR_PHONE_NUMBER` / `EVENT_CHRIS_READY_FOR_REMATCH` | `constants/event_flags.asm` | `TrainerFisherChris.Script` | phone bookkeeping only | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `ULTRA_BALL` | item ball at (6,4) | `Route42UltraBall` | `EVENT_ROUTE_42_ULTRA_BALL` | -| `SUPER_POTION` | item ball at (33,8) | `Route42SuperPotion` | `EVENT_ROUTE_42_SUPER_POTION` | -| `MAX_POTION` | hidden at (16,11) | `bg_event 16, 11, BGEVENT_ITEM` | `EVENT_ROUTE_42_HIDDEN_MAX_POTION` | -| `PNK_APRICORN` | fruit tree (27,16) | `FRUITTREE_ROUTE_42_1`, `data/items/fruit_trees.asm:24` | daily reset, not an event flag | -| `GRN_APRICORN` | fruit tree (28,16) | `FRUITTREE_ROUTE_42_2`, `data/items/fruit_trees.asm:25` | daily reset | -| `YLW_APRICORN` | fruit tree (29,16) | `FRUITTREE_ROUTE_42_3`, `data/items/fruit_trees.asm:26` | daily reset | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `CHRIS1` | `FISHER` (0x25) | 7 | `FisherGroup` entry `; FISHER (7)` at `parties.asm:1537`: L18 `QWILFISH` | `TrainerFisherChris` | yes, `PHONE_FISHER_CHRIS`; `CHRIS2` (L23 Qwilfish) after `EVENT_CLEARED_ROCKET_HIDEOUT`, `CHRIS3` after `EVENT_BEAT_ELITE_FOUR` | -| `SHANE` | `POKEMANIAC` (0x1e) | 4 | `parties.asm:1097`: L16 `NIDORINA`, L16 `NIDORINO` | `TrainerPokemaniacShane` | no | -| `BENJAMIN` | `HIKER` (0x2c) | 6 | `parties.asm:2149`: L14 `DIGLETT`, L14 `GEODUDE`, L16 `DUGTRIO` | `TrainerHikerBenjamin` | no | - -**Wild encounters** - -`data/wild/johto_grass.asm:2125` `def_grass_wildmons ROUTE_42`, rates 10%/10%/10% -morn/day/nite. The table is `IF DEF(_GOLD)` / `ELIF DEF(_SILVER)` split, so the -walkthrough's "Mankey (Gold only)" is literally true: - -| slot | morn (Gold) | day (Gold) | nite (Gold) | -|---|---|---|---| -| 1 | L15 `MANKEY` | L15 `MANKEY` | L15 `MANKEY` | -| 2 | L13 `MAREEP` | L13 `MAREEP` | L13 `MAREEP` | -| 3 | L14 `SPEAROW` | L14 `SPEAROW` | L14 `ZUBAT` | -| 4 | L16 `SPEAROW` | L16 `SPEAROW` | L16 `ZUBAT` | -| 5 | L15 `FLAAFFY` | L15 `FLAAFFY` | L15 `FLAAFFY` | -| 6 | L17 `FLAAFFY` | L17 `FLAAFFY` | L17 `FLAAFFY` | -| 7 | L17 `FLAAFFY` | L17 `FLAAFFY` | L17 `FLAAFFY` | - -Silver swaps slot 1 `MANKEY` for L15 `MAREEP` and reorders; the rest is -identical. - -Water (`data/wild/johto_water.asm:183`), 4% rate: L20 `GOLDEEN`, L15 `GOLDEEN`, -L20 `SEAKING`. - -Fishing: header says `FISHGROUP_LAKE` -> `data/wild/fish.asm` `.Lake_Old` -(Magikarp/Magikarp/Goldeen L10), `.Lake_Good` (Magikarp/Goldeen/Goldeen L20 + -`time_group 4`), `.Lake_Super` (Goldeen L40 + `time_group 5` + Magikarp L40 + -Seaking L40). - -Headbutt: `data/wild/treemon_maps.asm:23` -> `treemon_map ROUTE_42, TREEMON_SET_CANYON`. - -Roamers: `data/wild/roammon_maps.asm:30` -> `roam_map ROUTE_42, ROUTE_43, ROUTE_44, -ROUTE_37, ROUTE_38`. A roaming beast can be on Route 42 or Route 43 while the -bot walks this section. - ---- - -### MAP_MAHOGANY_TOWN - -- Script: `maps/MahoganyTown.asm` (symbol `MahoganyTown_MapEvents` = `49:4e95`, `pokegold.sym:22738`) -- Blocks: `maps/MahoganyTown.blk` -- Header: `data/maps/maps.asm:74` -> `map MahoganyTown, TILESET_JOHTO, TOWN, LANDMARK_MAHOGANY_TOWN, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:62` -> `map_const MAHOGANY_TOWN, 10, 9` (x 0..19, y 0..17) -- Connections (`data/maps/attributes.asm:152`): north `ROUTE_43` (0), west `ROUTE_42` (0), east `ROUTE_44` (0) -- Fly spawn: `data/maps/spawn_points.asm:36` -> `spawn MAHOGANY_TOWN, 15, 14` -- Callback: `callback MAPCALLBACK_NEWMAP, MahoganyTownFlypointCallback` -> `setflag ENGINE_FLYPOINT_MAHOGANY` - -**Scene scripts** (`def_scene_scripts`, ids assigned in order) - -| id | constant | script | -|---|---|---| -| 0 | `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` | `MahoganyTownNoop1Scene` (`end`) | -| 1 | `SCENE_MAHOGANYTOWN_NOOP` | `MahoganyTownNoop2Scene` (`end`) | - -Scene 0 is the new-game default and is what arms the two coord events below. -Only `RadioTowerRocketsScript` (`engine/events/std_scripts.asm:262`) ever moves -it to 1 - which is much later than this section. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 7 | `MAHOGANY_MART_1F` | 1 | -| 2 | 17 | 7 | `MAHOGANY_RED_GYARADOS_SPEECH_HOUSE` | 1 | -| 3 | 6 | 13 | `MAHOGANY_GYM` | 1 | -| 4 | 15 | 13 | `MAHOGANY_POKECENTER_1F` | 1 | -| 5 | 9 | 1 | `ROUTE_43_MAHOGANY_GATE` | 3 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` | 19 | 8 | `MahoganyTownTryARageCandyBarScript` | RageCandyBar sales pitch, then you are shoved one step west | -| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` | 19 | 9 | `MahoganyTownTryARageCandyBarScript` | same | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 5 | `BGEVENT_READ` | `MahoganyTownSign` | -| 9 | 7 | `BGEVENT_READ` | `MahoganyTownRagecandybarSign` | -| 3 | 13 | `BGEVENT_READ` | `MahoganyGymSign` | -| 16 | 13 | `BGEVENT_READ` | `MahoganyTownPokecenterSign` (`jumpstd PokecenterSignScript`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MAHOGANYTOWN_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownPokefanMScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` | -| `MAHOGANYTOWN_GRAMPS` | `SPRITE_GRAMPS` | 6 | 9 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius x=1) | `OBJECTTYPE_SCRIPT` | `MahoganyTownGrampsScript` | -1 | -| `MAHOGANYTOWN_FISHER` | `SPRITE_FISHER` | 6 | 14 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownFisherScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` | -| `MAHOGANYTOWN_LASS` | `SPRITE_LASS` | 12 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownLassScript` | `EVENT_MAHOGANY_MART_OWNERS` | - -State during this section: `EVENT_MAHOGANY_MART_OWNERS` is **set** by -`InitializeEventsScript` (`engine/events/std_scripts.asm:501`), so the Lass at -(12,8) is not on the map yet - she only appears after -`maps/RadioTower5F.asm:107` / `maps/BlackthornGym1F.asm:53` clear it. -`EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` and -`EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` are **clear** at new game (nothing in -`InitializeEventsScript` sets them), so both the merchant at (19,8) and the -fisher at (6,14) are physically standing in the way. - -**Scripts of interest** - -- `MahoganyTownTryARageCandyBarScript` (symbol `49:4ac8`, `pokegold.sym:22705`) - - `showemote EMOTE_SHOCK` on the merchant, `applymovement MAHOGANYTOWN_POKEFAN_M, - MahoganyTownRageCandyBarMerchantBlocksYouMovement` (`step RIGHT`, `step DOWN`, - `turn_head LEFT`), `follow PLAYER, MAHOGANYTOWN_POKEFAN_M`, `applymovement - PLAYER, MahoganyTownPlayerStepLeftMovement` (`step LEFT`), `stopfollow`, - `turnobject PLAYER, RIGHT`, `scall RageCandyBarMerchantScript`, then - `MahoganyTownRageCandyBarMerchantReturnsMovement` (`step UP`, `turn_head - DOWN`). It never calls `setscene`, so it fires **every** time the bot steps on - (19,8)/(19,9). Buying does not unlock the east exit; only - `RadioTowerRocketsScript`'s `setevent EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` - + `setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP` does. -- `RageCandyBarMerchantScript` - `checkevent EVENT_CLEARED_ROCKET_HIDEOUT` -> - "sold out"; otherwise `writetext RageCandyBarMerchantTryOneText`, `special - PlaceMoneyTopRight`, `yesorno`, `checkmoney YOUR_MONEY, - MAHOGANYTOWN_RAGECANDYBAR_PRICE`, `giveitem RAGECANDYBAR`, `takemoney - YOUR_MONEY, 300`. `DEF MAHOGANYTOWN_RAGECANDYBAR_PRICE EQU 300` at - `maps/MahoganyTown.asm:1`. -- `MahoganyTownGrampsScript` / `MahoganyTownFisherScript` - text only. The Gramps - branches on `EVENT_CLEARED_ROCKET_HIDEOUT`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_MAHOGANY` | `constants/engine_flags.asm:87` | `MahoganyTownFlypointCallback` | set on first map load; Fly destination unlocked | -| `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` | `constants/event_flags.asm:1272` | object row; set by `engine/events/std_scripts.asm:261` `RadioTowerRocketsScript` | clear = merchant stands at (19,8); the east exit to Route 44 is closed for this whole section | -| `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` | `constants/event_flags.asm:1273` | object row; set by `maps/TeamRocketBaseB2F.asm:303` | clear = fisher at (6,14) blocks the Gym door at (6,13). Mahogany Gym is not enterable in this section | -| `EVENT_MAHOGANY_MART_OWNERS` | `constants/event_flags.asm:1240` | object rows; set at init, cleared in `RadioTower5F` / `BlackthornGym1F` | set = Lass and Mart Granny absent | -| `EVENT_CLEARED_ROCKET_HIDEOUT` | `constants/event_flags.asm:43` | read all over this section | clear for the whole of section 10 | -| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` (=0) / `SCENE_MAHOGANYTOWN_NOOP` (=1) | exported by the `scene_script` macro in `macros/scripts/maps.asm` | `def_scene_scripts`, `setmapscene` | 0 for this section | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `RAGECANDYBAR` | bought for 300 from the merchant | `RageCandyBarMerchantScript.SellRageCandyBars` | none, repeatable until `EVENT_CLEARED_ROCKET_HIDEOUT` | - -**Trainers** - none. - -**Wild encounters** - none on the town map. `FISHGROUP_SHORE` per the header if -the bot fishes off the town's water. - ---- - -### MAP_MAHOGANY_MART_1F (the "Grandma's shop" the walkthrough calls a house) - -Only the pre-Lance state is in scope. - -- Script: `maps/MahoganyMart1F.asm` -- Header: `data/maps/maps.asm:118` -> `map MahoganyMart1F, TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_MAHOGANY_MART, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:105` -> `map_const MAHOGANY_MART_1F, 4, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `MAHOGANY_TOWN` | 1 | -| 2 | 4 | 7 | `MAHOGANY_TOWN` | 1 | -| 3 | 7 | 3 | `TEAM_ROCKET_BASE_B1F` | 1 | - -Warp 3 is the hidden staircase: `MahoganyMart1FStaircaseCallback` -(`MAPCALLBACK_TILES`) only does `changeblock 6, 2, $1e` once -`EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` is set, which is next section. - -**Coord events** - none. **BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MAHOGANYMART1F_PHARMACIST` | `SPRITE_PHARMACIST` | 4 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FPharmacistScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `MAHOGANYMART1F_BLACK_BELT` | `SPRITE_BLACK_BELT` | 1 | 6 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FBlackBeltScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `MAHOGANYMART1F_LANCE` | `SPRITE_LANCE` | 4 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` | -| `MAHOGANYMART1F_DRAGONITE` | `SPRITE_DRAGON` | 3 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` | -| `MAHOGANYMART1F_GRANNY` | `SPRITE_GRANNY` | 1 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FGrannyScript` | `EVENT_MAHOGANY_MART_OWNERS` | - -`EVENT_TEAM_ROCKET_BASE_POPULATION` is clear at new game, so the Pharmacist runs -`pokemart MARTTYPE_STANDARD, MART_MAHOGANY_1`. `MartMahogany1` -(`data/items/marts.asm:195`): `TINYMUSHROOM`, `SLOWPOKETAIL`, `POKE_BALL`, -`POTION`. `SLOWPOKETAIL`'s price is 9800 (`data/items/attributes.asm:215`, -`item_attribute 9800, HELD_NONE, ...`) - this is the walkthrough's "9800G -Slowpoketails". `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` is **set** by -`InitializeEventsScript`, so Lance and the Dragonite are not here yet; -`LakeOfRageLanceScript` clears it at the end of this section. - ---- - -### MAP_MAHOGANY_RED_GYARADOS_SPEECH_HOUSE - -- Script: `maps/MahoganyRedGyaradosSpeechHouse.asm` -- Header: `data/maps/maps.asm:68` -> `map MahoganyRedGyaradosSpeechHouse, TILESET_HOUSE, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:56` -> `map_const MAHOGANY_RED_GYARADOS_SPEECH_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `MAHOGANY_TOWN` | 2 | -| 2 | 3 | 7 | `MAHOGANY_TOWN` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MAHOGANYREDGYARADOSSPEECHHOUSE_BLACK_BELT` | `SPRITE_BLACK_BELT` | 2 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MahoganyRedGyaradosSpeechHouseBlackBeltScript` | -1 | -| `MAHOGANYREDGYARADOSSPEECHHOUSE_TEACHER` | `SPRITE_TEACHER` | 6 | 5 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius y=1) | `OBJECTTYPE_SCRIPT` | `MahoganyRedGyaradosSpeechHouseTeacherScript` | -1 | - -Text only. The teacher branches on `checkflag ENGINE_ROCKETS_IN_RADIO_TOWER`. - ---- - -### MAP_MAHOGANY_POKECENTER_1F - -- Script: `maps/MahoganyPokecenter1F.asm` -- Header: `data/maps/maps.asm:70` -> `map MahoganyPokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:58` -> `map_const MAHOGANY_POKECENTER_1F, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `MAHOGANY_TOWN` | 4 | -| 2 | 4 | 7 | `MAHOGANY_TOWN` | 4 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| (index 2) | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyPokecenter1FNurseScript` | -1 | -| (index 3) | `SPRITE_POKEFAN_M` | 7 | 2 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `MahoganyPokecenter1FPokefanMScript` | -1 | -| (index 4) | `SPRITE_YOUNGSTER` | 1 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MahoganyPokecenter1FYoungsterScript` | -1 | -| (index 5) | `SPRITE_COOLTRAINER_F` | 2 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `MahoganyPokecenter1FCooltrainerFScript` | -1 | - -Heal target for a bot: talk to the nurse at (3,1) from (3,2). - ---- - -### MAP_ROUTE_43_MAHOGANY_GATE - -- Script: `maps/Route43MahoganyGate.asm` -- Header: `data/maps/maps.asm:239` -> `map Route43MahoganyGate, TILESET_GATE, GATE, LANDMARK_ROUTE_43, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:219` -> `map_const ROUTE_43_MAHOGANY_GATE, 5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_43` | 1 | -| 2 | 5 | 0 | `ROUTE_43` | 2 | -| 3 | 4 | 7 | `MAHOGANY_TOWN` | 5 | -| 4 | 5 | 7 | `MAHOGANY_TOWN` | 5 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE43MAHOGANYGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route43MahoganyGateOfficer` | -1 | - -`Route43MahoganyGateOfficer` branches on `EVENT_CLEARED_ROCKET_HIDEOUT` for text -only. This gate never blocks. - ---- - -### MAP_ROUTE_43 - -- Script: `maps/Route43.asm` (symbol `Route43_MapEvents` = `4d:4680`, `pokegold.sym:23879`) -- Blocks: `maps/Route43.blk` -- Header: `data/maps/maps.asm:241` -> `map Route43, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_43, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:221` -> `map_const ROUTE_43, 10, 27` (x 0..19, y 0..53) -- Connections (`data/maps/attributes.asm:235`): north `LAKE_OF_RAGE` (-5), south `MAHOGANY_TOWN` (0) -- Callback: `callback MAPCALLBACK_NEWMAP, Route43CheckIfRocketsScene` - -`Route43CheckIfRocketsScene`: `checkevent EVENT_CLEARED_ROCKET_HIDEOUT`; if -clear, `setmapscene ROUTE_43_GATE, SCENE_ROUTE43GATE_ROCKET_SHAKEDOWN`, else -`setmapscene ROUTE_43_GATE, SCENE_ROUTE43GATE_NOOP`. In this section the toll -scene is always armed. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 51 | `ROUTE_43_MAHOGANY_GATE` | 1 | -| 2 | 10 | 51 | `ROUTE_43_MAHOGANY_GATE` | 2 | -| 3 | 17 | 35 | `ROUTE_43_GATE` | 3 | -| 4 | 17 | 31 | `ROUTE_43_GATE` | 1 | -| 5 | 18 | 31 | `ROUTE_43_GATE` | 2 | - -**Coord events** - `def_coord_events` is empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 13 | 3 | `BGEVENT_READ` | `Route43Sign1` | -| 11 | 49 | `BGEVENT_READ` | `Route43Sign2` | -| 16 | 38 | `BGEVENT_READ` | `Route43TrainerTips` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE43_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 14 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPokemaniacBen` | -1 | -| `ROUTE43_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 13 | 20 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPokemaniacBrent` | -1 | -| `ROUTE43_SUPER_NERD3` | `SPRITE_SUPER_NERD` | 13 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerPokemaniacRon` | -1 | -| `ROUTE43_FISHER` | `SPRITE_FISHER` | 4 | 16 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerFisherMarvin` | -1 | -| `ROUTE43_LASS` | `SPRITE_LASS` | 9 | 29 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerPicnickerTiffany` | -1 | -| `ROUTE43_YOUNGSTER` | `SPRITE_YOUNGSTER` | 15 | 43 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerCamperSpencer` | -1 | -| `ROUTE43_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 1 | 26 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route43FruitTree` -> `fruittree FRUITTREE_ROUTE_43` | -1 | -| `ROUTE43_POKE_BALL` | `SPRITE_POKE_BALL` | 12 | 32 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route43MaxEther` -> `itemball MAX_ETHER` | `EVENT_ROUTE_43_MAX_ETHER` | - -Walking north from the Mahogany gate the y coordinates decrease, which gives the -walkthrough's order exactly: Spencer (y=43), Tiffany (y=29), Brent (y=20), Ron -(y=7), Ben (y=6). Marvin at (4,16) is the far-west detour. - -**Scripts of interest** - -- `TrainerPicnickerTiffany` - `trainer PICNICKER, TIFFANY3, EVENT_BEAT_PICNICKER_TIFFANY, ...`. - Phone ladder on `PHONE_PICNICKER_TIFFANY`; rematch loads `TIFFANY1` after - `EVENT_CLEARED_RADIO_TOWER` and `TIFFANY2` after `EVENT_BEAT_ELITE_FOUR`. - Note the inversion: `TIFFANY3` (L20) is the *first* fight. -- `TrainerPokemaniacBrent` - `BRENT1` first; `BRENT2` after - `EVENT_CLEARED_ROCKET_HIDEOUT`, `BRENT3` after `EVENT_BEAT_ELITE_FOUR`. Phone - on `PHONE_POKEMANIAC_BRENT`. -- `TrainerCamperSpencer`, `TrainerPokemaniacBen`, `TrainerPokemaniacRon`, - `TrainerFisherMarvin` - plain, no phone, no rematch. -- `Route43CheckIfRocketsScene` - the callback above; this is the only thing that - arms the toll. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_43_MAX_ETHER` | `constants/event_flags.asm:1110` | object row | set = ball at (12,32) taken | -| `EVENT_BEAT_CAMPER_SPENCER` | `constants/event_flags.asm:546` | `TrainerCamperSpencer` | | -| `EVENT_BEAT_PICNICKER_TIFFANY` | `constants/event_flags.asm:655` | `TrainerPicnickerTiffany` | | -| `EVENT_BEAT_POKEMANIAC_BRENT` | `constants/event_flags.asm:755` | `TrainerPokemaniacBrent` | | -| `EVENT_BEAT_POKEMANIAC_RON` | `constants/event_flags.asm:756` | `TrainerPokemaniacRon` | | -| `EVENT_BEAT_POKEMANIAC_BEN` | `constants/event_flags.asm:754` | `TrainerPokemaniacBen` | | -| `EVENT_BEAT_FISHER_MARVIN` | `constants/event_flags.asm:590` | `TrainerFisherMarvin` | | -| `EVENT_CLEARED_ROCKET_HIDEOUT` | `constants/event_flags.asm:43` | `Route43CheckIfRocketsScene` | clear here, so the gate toll is armed | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `MAX_ETHER` | item ball at (12,32) | `Route43MaxEther` | `EVENT_ROUTE_43_MAX_ETHER` | -| `BITTER_BERRY` | fruit tree at (1,26) | `FRUITTREE_ROUTE_43`, `data/items/fruit_trees.asm:11` | daily reset | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SPENCER` | `CAMPER` (0x36) | 19 | `; CAMPER (19)` at `parties.asm:2817`: L17 `SANDSHREW`, L17 `SANDSLASH`, L19 `ZUBAT` | `TrainerCamperSpencer` | no | -| `TIFFANY3` | `PICNICKER` (0x35) | 20 | `; PICNICKER (20)` at `parties.asm:2693`, `TRAINERTYPE_MOVES`: L20 `CLEFAIRY` - `ENCORE`, `SING`, `DOUBLESLAP`, `MINIMIZE` | `TrainerPicnickerTiffany` | yes, `PHONE_PICNICKER_TIFFANY` | -| `BRENT1` | `POKEMANIAC` (0x1e) | 6 | `; POKEMANIAC (6)` at `parties.asm:1108`: L19 `LICKITUNG` | `TrainerPokemaniacBrent` | yes, `PHONE_POKEMANIAC_BRENT` | -| `RON` | `POKEMANIAC` | 7 | `parties.asm:1113`: L19 `NIDOKING` | `TrainerPokemaniacRon` | no | -| `BEN` | `POKEMANIAC` | 5 | `parties.asm:1103`: L19 `SLOWBRO` | `TrainerPokemaniacBen` | no | -| `MARVIN` | `FISHER` (0x25) | 6 | `; FISHER (6)` at `parties.asm:1529`: L10 `MAGIKARP`, L10 `GYARADOS`, L15 `MAGIKARP`, L15 `GYARADOS` | `TrainerFisherMarvin` | no | - -**Wild encounters** - -`data/wild/johto_grass.asm:2180` `def_grass_wildmons ROUTE_43`, rates 10%/10%/10%. -No version split. - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L15 `FLAAFFY` | L15 `FLAAFFY` | L15 `FLAAFFY` | -| 2 | L15 `GIRAFARIG` | L15 `GIRAFARIG` | L15 `GIRAFARIG` | -| 3 | L17 `PIDGEOTTO` | L17 `PIDGEOTTO` | L17 `NOCTOWL` | -| 4 | L15 `MAREEP` | L15 `MAREEP` | L16 `VENONAT` | -| 5 | L16 `VENONAT` | L17 `FLAAFFY` | L15 `MAREEP` | -| 6 | L17 `PIDGEOTTO` | L17 `FLAAFFY` | L16 `VENONAT` | -| 7 | L17 `PIDGEOTTO` | L17 `FLAAFFY` | L16 `VENONAT` | - -Water (`data/wild/johto_water.asm:190`), 2% rate: L20/L15/L10 `MAGIKARP`. -Fishing: `FISHGROUP_POND` -> `.Pond_Old` Magikarp/Magikarp/Poliwag L10, -`.Pond_Good` Magikarp/Poliwag/Poliwag L20 + `time_group 6`. -Headbutt: `data/wild/treemon_maps.asm:24` -> `TREEMON_SET_CANYON`. -Roamers: `data/wild/roammon_maps.asm:31` -> `roam_map ROUTE_43, ROUTE_42, ROUTE_44`. - ---- - -### MAP_ROUTE_43_GATE (the Rocket toll booth) - -- Script: `maps/Route43Gate.asm` (`Route43GateRocketTakeoverScript` = `53:5bb0`, `pokegold.sym:25377`) -- Blocks: shared, `data/maps/blocks.asm:206` `Route43Gate_Blocks` -- Header: `data/maps/maps.asm:240` -> `map Route43Gate, TILESET_GATE, GATE, LANDMARK_ROUTE_43, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:220` -> `map_const ROUTE_43_GATE, 5, 4` -- Callback: `callback MAPCALLBACK_NEWMAP, Route43GateCheckIfRocketsCallback` - -**Scene scripts** - -| id | constant | script | -|---|---|---| -| 0 | `SCENE_ROUTE43GATE_ROCKET_SHAKEDOWN` | `Route43GateRocketShakedownScene` -> `sdefer Route43GateRocketTakeoverScript` | -| 1 | `SCENE_ROUTE43GATE_NOOP` | `Route43GateNoopScene` | - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_43` | 4 | -| 2 | 5 | 0 | `ROUTE_43` | 5 | -| 3 | 4 | 7 | `ROUTE_43` | 3 | -| 4 | 5 | 7 | `ROUTE_43` | 3 | - -(Warp 4 also lands on Route 43 warp 3, not a separate one - transcribed as written.) - -**Coord events** - none. **BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE43GATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `OfficerScript_GuardWithSludgeBomb` | `EVENT_LAKE_OF_RAGE_CIVILIANS` | -| `ROUTE43GATE_ROCKET1` | `SPRITE_ROCKET` | 2 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `RocketScript_MakingABundle` | `EVENT_ROUTE_43_GATE_ROCKETS` | -| `ROUTE43GATE_ROCKET2` | `SPRITE_ROCKET` | 7 | 4 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `RocketScript_MakingABundle` | `EVENT_ROUTE_43_GATE_ROCKETS` | - -`EVENT_LAKE_OF_RAGE_CIVILIANS` is **set** at new game -(`engine/events/std_scripts.asm:500`), so the officer with TM36 is **not here -during this section**; he only appears once `maps/TeamRocketBaseB2F.asm:305` -clears it. `EVENT_ROUTE_43_GATE_ROCKETS` is clear, so the two grunts are here; -`TeamRocketBaseB2F.asm:302` sets it later to remove them. - -**Scripts of interest** - -- `Route43GateRocketTakeoverScript` - `playmusic MUSIC_ROCKET_ENCOUNTER`, - `readvar VAR_FACING`, `ifequal DOWN, RocketScript_Southbound`, `ifequal UP, - RocketScript_Northbound`, otherwise `setscene SCENE_ROUTE43GATE_NOOP` and end. -- `RocketScript_Northbound` / `RocketScript_Southbound` - two grunts step in, - `writetext RocketText_TollFee`, then `checkmoney YOUR_MONEY, - ROUTE43GATE_TOLL - 1`; `HAVE_MORE` goes to `RocketScript_Toll*`, otherwise - `RocketScript_YoureBroke*`. **Both arms run `takemoney YOUR_MONEY, - ROUTE43GATE_TOLL`** - a bot with less than 1000 loses everything it has and - still passes. `DEF ROUTE43GATE_TOLL EQU 1000` at `maps/Route43Gate.asm:1`. - Both arms then `setscene SCENE_ROUTE43GATE_NOOP`, so the toll is charged once - per `Route43CheckIfRocketsScene` re-arm (i.e. once per Route 43 map load). -- `OfficerScript_GuardWithSludgeBomb` - `verbosegiveitem TM_SLUDGE_BOMB` guarded - by `EVENT_GOT_TM36_SLUDGE_BOMB`. Out of reach this section. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_SLUDGE_BOMB` (TM36) | talk to the officer | `OfficerScript_GuardWithSludgeBomb` | `EVENT_GOT_TM36_SLUDGE_BOMB` - **not obtainable in this section**, the officer is masked by `EVENT_LAKE_OF_RAGE_CIVILIANS` | - ---- - -### MAP_LAKE_OF_RAGE - -- Script: `maps/LakeOfRage.asm` (`LakeOfRage_MapEvents` = `49:5825`, `pokegold.sym:22807`; `LakeOfRageLanceScript` = `49:4f2e`; `RedGyarados` = `49:4f6f`; `WesleyScript` = `49:501a`) -- Blocks: `maps/LakeOfRage.blk` -- Header: `data/maps/maps.asm:242` -> `map LakeOfRage, TILESET_JOHTO, TOWN, LANDMARK_LAKE_OF_RAGE, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_AUTO, FISHGROUP_GYARADOS` -- Dimensions: `constants/map_constants.asm:222` -> `map_const LAKE_OF_RAGE, 20, 18` (x 0..39, y 0..35) -- Connections (`data/maps/attributes.asm:157`): south `ROUTE_43` (-5) -- Fly spawn: `data/maps/spawn_points.asm:37` -> `spawn LAKE_OF_RAGE, 21, 29` -- Callbacks: `MAPCALLBACK_NEWMAP` -> `LakeOfRageFlypointCallback` (`setflag - ENGINE_FLYPOINT_LAKE_OF_RAGE`); `MAPCALLBACK_OBJECTS` -> `LakeOfRageWesleyCallback` - -**Scene scripts** - `LakeOfRageNoop1Scene` / `LakeOfRageNoop2Scene`, both marked -`; unusable` in the source. Lake of Rage effectively has no scene variable; -`RedGyarados`'s `setscene 0` carries the comment saying so. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 3 | `LAKE_OF_RAGE_HIDDEN_POWER_HOUSE` | 1 | -| 2 | 27 | 31 | `LAKE_OF_RAGE_MAGIKARP_HOUSE` | 1 | - -**Coord events** - `def_coord_events` is empty. Nothing here is a trip-wire; the -Gyarados and Lance are both talk-to objects. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 21 | 27 | `BGEVENT_READ` | `LakeOfRageSign` (this is the sign Lance is standing at) | -| 25 | 31 | `BGEVENT_READ` | `MagikarpHouseSignScript` | -| 11 | 28 | `BGEVENT_ITEM` | `LakeOfRageHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_LAKE_OF_RAGE_HIDDEN_FULL_RESTORE` | -| 4 | 4 | `BGEVENT_ITEM` | `LakeOfRageHiddenRareCandy` -> `hiddenitem RARE_CANDY, EVENT_LAKE_OF_RAGE_HIDDEN_RARE_CANDY` | -| 35 | 5 | `BGEVENT_ITEM` | `LakeOfRageHiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_LAKE_OF_RAGE_HIDDEN_MAX_POTION` | - -The hidden Rare Candy at (4,4) is on the exact tile Wesley stands on -(`object_event 4, 4, SPRITE_SUPER_NERD ... WesleyScript`). On a Wednesday the NPC -is in the way of the Itemfinder tile. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LAKEOFRAGE_LANCE` | `SPRITE_LANCE` | 21 | 28 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `LakeOfRageLanceScript` | `EVENT_LAKE_OF_RAGE_LANCE` | -| `LAKEOFRAGE_GRAMPS` | `SPRITE_GRAMPS` | 20 | 26 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `LakeOfRageGrampsScript` | -1 | -| `LAKEOFRAGE_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 36 | 13 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `LakeOfRageSuperNerdScript` | -1 | -| `LAKEOFRAGE_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 25 | 29 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius x=1) | `OBJECTTYPE_SCRIPT` | `LakeOfRageCooltrainerFScript` | -1 | -| `LAKEOFRAGE_FISHER1` | `SPRITE_FISHER` | 30 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerFisherAndre` | `EVENT_LAKE_OF_RAGE_CIVILIANS` | -| `LAKEOFRAGE_FISHER2` | `SPRITE_FISHER` | 24 | 26 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerFisherRaymond` | `EVENT_LAKE_OF_RAGE_CIVILIANS` | -| `LAKEOFRAGE_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 4 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerCooltrainermAaron` | `EVENT_LAKE_OF_RAGE_CIVILIANS` | -| `LAKEOFRAGE_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 36 | 7 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerCooltrainerfLois` | `EVENT_LAKE_OF_RAGE_CIVILIANS` | -| `LAKEOFRAGE_GYARADOS` | `SPRITE_GYARADOS` | 18 | 22 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `RedGyarados` | `EVENT_LAKE_OF_RAGE_RED_GYARADOS` | -| `LAKEOFRAGE_WESLEY` | `SPRITE_SUPER_NERD` | 4 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `WesleyScript` | `EVENT_LAKE_OF_RAGE_WESLEY_OF_WEDNESDAY` | -| `LAKEOFRAGE_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 10 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `LakeOfRageMaxEther` -> `itemball MAX_ETHER` | `EVENT_LAKE_OF_RAGE_MAX_ETHER` | -| `LAKEOFRAGE_POKE_BALL2` | `SPRITE_POKE_BALL` | 35 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `LakeOfRageTMDetect` -> `itemball TM_DETECT` | `EVENT_LAKE_OF_RAGE_TM_DETECT` | - -Visibility during this section: `EVENT_LAKE_OF_RAGE_LANCE` is **set** at init -(`engine/events/std_scripts.asm:516`) so Lance is absent until the Gyarados -script's `appear LAKEOFRAGE_LANCE`. `EVENT_LAKE_OF_RAGE_CIVILIANS` is **set** at -init (`:500`), so **Andre, Raymond, Aaron and Lois are not on the map yet** - -they only turn up after the Rocket hideout. `EVENT_LAKE_OF_RAGE_RED_GYARADOS` -and `EVENT_LAKE_OF_RAGE_WESLEY_OF_WEDNESDAY` are clear at init, so the Gyarados -is present and Wesley is governed purely by the weekday callback. - -**Scripts of interest** - -- `LakeOfRageWesleyCallback` (`MAPCALLBACK_OBJECTS`) - `readvar VAR_WEEKDAY`, - `ifequal WEDNESDAY, .WesleyAppears` -> `appear LAKEOFRAGE_WESLEY`; otherwise - `disappear LAKEOFRAGE_WESLEY`. -- `RedGyarados` - the whole encounter: - `opentext` / `writetext LakeOfRageGyaradosCryText` / `pause 15` / - `cry GYARADOS` / `closetext`, then - `loadwildmon GYARADOS, 30`, - `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCESHINY`, - `startbattle`, - `ifequal LOSE, .NotBeaten` (skip the disappear), - `disappear LAKEOFRAGE_GYARADOS`, - `reloadmapafterbattle`, - `opentext` / **`giveitem RED_SCALE`** / `waitsfx` / - `writetext LakeOfRageGotRedScaleText` / `playsound SFX_ITEM` / `waitsfx` / - `itemnotify` / `closetext`, - `setscene 0`, `appear LAKEOFRAGE_LANCE`, `end`. - Two things a bot must know: the `giveitem RED_SCALE` has **no `iffalse` - guard**, so a full bag silently eats the Red Scale; and the Red Scale and Lance - arrive even on a loss, because the `.NotBeaten` label only skips the - `disappear`. -- `LakeOfRageLanceScript` - `checkevent EVENT_REFUSED_TO_HELP_LANCE_AT_LAKE_OF_RAGE` - -> `.AskAgainForHelp`. First pass: `writetext LakeOfRageLanceForcedToEvolveText`, - `promptbutton`, `faceplayer`, `writetext LakeOfRageLanceIntroText`, `yesorno`. - `iffalse .RefusedToHelp` -> `setevent EVENT_REFUSED_TO_HELP_LANCE_AT_LAKE_OF_RAGE` - and stop. `.AgreedToHelp` -> - `writetext LakeOfRageLanceRadioSignalText`, `playsound SFX_WARP_TO`, - `applymovement LAKEOFRAGE_LANCE, LakeOfRageLanceTeleportIntoSkyMovement` - (`teleport_from` = `$4c`, `macros/scripts/movement.asm:148`), - `disappear LAKEOFRAGE_LANCE`, - `clearevent EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE`, - `setevent EVENT_DECIDED_TO_HELP_LANCE`, - `setmapscene MAHOGANY_MART_1F, SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS`. - **This is the section's terminal state change.** Saying no is recoverable - the - `.AskAgainForHelp` arm re-asks. -- `WesleyScript` - `checkevent EVENT_GOT_BLACKBELT_FROM_WESLEY` -> - `WesleyWednesdayScript`; `readvar VAR_WEEKDAY` / `ifnotequal WEDNESDAY` -> - `WesleyNotWednesdayScript`; else `setevent EVENT_MET_WESLEY_OF_WEDNESDAY`, - `verbosegiveitem BLACKBELT_I`, `iffalse WesleyDoneScript`, `setevent - EVENT_GOT_BLACKBELT_FROM_WESLEY`. Guarded, so a full bag is safe here. -- `MagikarpHouseSignScript` - `writetext FishingGurusHouseSignText`, then - `checkevent EVENT_CLEARED_ROCKET_HIDEOUT` -> `special MagikarpHouseSign` - (the record board). Only text in this section. -- `LakeOfRageGrampsScript`, `LakeOfRageSuperNerdScript`, - `LakeOfRageCooltrainerFScript` - the "something strange is going on" NPCs the - walkthrough mentions; all branch on `EVENT_CLEARED_ROCKET_HIDEOUT` at most. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_LAKE_OF_RAGE` | `constants/engine_flags.asm:88` | `LakeOfRageFlypointCallback` | set on first entry | -| `EVENT_LAKE_OF_RAGE_RED_GYARADOS` | `constants/event_flags.asm:1267` | `RedGyarados` (`disappear`) | clear = the Gyarados object is on the water at (18,22) | -| `EVENT_LAKE_OF_RAGE_LANCE` | `constants/event_flags.asm:1142` | set by `InitializeEventsScript`; cleared by `RedGyarados`'s `appear`; set again by `LakeOfRageLanceScript`'s `disappear` | Lance is only talkable in the window between beating the Gyarados and agreeing to help | -| `EVENT_LAKE_OF_RAGE_CIVILIANS` | `constants/event_flags.asm:1239` | set at init; cleared in `maps/TeamRocketBaseB2F.asm:305` | set for this whole section: no Lake of Rage trainers | -| `EVENT_LAKE_OF_RAGE_WESLEY_OF_WEDNESDAY` | `constants/event_flags.asm:1278` | `LakeOfRageWesleyCallback` | driven by `VAR_WEEKDAY` on every map load | -| `EVENT_MET_WESLEY_OF_WEDNESDAY` | `constants/event_flags.asm:115` | `WesleyScript` | first-meeting text seen | -| `EVENT_GOT_BLACKBELT_FROM_WESLEY` | `constants/event_flags.asm:116` | `WesleyScript` | gift taken | -| `EVENT_REFUSED_TO_HELP_LANCE_AT_LAKE_OF_RAGE` | `constants/event_flags.asm:47` | `LakeOfRageLanceScript` | you said no; the re-ask arm | -| `EVENT_DECIDED_TO_HELP_LANCE` | `constants/event_flags.asm:105` | set by `LakeOfRageLanceScript`; read by `maps/MahoganyMart1F.asm:35,50` | **section complete** | -| `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` | `constants/event_flags.asm:1143` | set at init, cleared by `LakeOfRageLanceScript` | clear = Lance and Dragonite now stand in Mahogany Mart | -| `EVENT_LAKE_OF_RAGE_MAX_ETHER` | `constants/event_flags.asm:998` | object row | ball at (7,10) | -| `EVENT_LAKE_OF_RAGE_TM_DETECT` | `constants/event_flags.asm:999` | object row | ball at (35,2) | -| `EVENT_LAKE_OF_RAGE_HIDDEN_FULL_RESTORE` | `constants/event_flags.asm:191` | `LakeOfRageHiddenFullRestore` | hidden at (11,28) | -| `EVENT_LAKE_OF_RAGE_HIDDEN_RARE_CANDY` | `constants/event_flags.asm:192` | `LakeOfRageHiddenRareCandy` | hidden at (4,4) | -| `EVENT_LAKE_OF_RAGE_HIDDEN_MAX_POTION` | `constants/event_flags.asm:193` | `LakeOfRageHiddenMaxPotion` | hidden at (35,5) | -| `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` | `maps/MahoganyMart1F.asm` `def_scene_scripts` (id 1) | `setmapscene` in `LakeOfRageLanceScript` | arms the next section | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `RED_SCALE` | `giveitem` after the Gyarados battle | `RedGyarados` | none of its own; gated by `EVENT_LAKE_OF_RAGE_RED_GYARADOS` | -| `BLACKBELT_I` | Wesley, Wednesdays only | `WesleyScript` (`verbosegiveitem BLACKBELT_I`) | `EVENT_GOT_BLACKBELT_FROM_WESLEY` | -| `MAX_ETHER` | item ball at (7,10) | `LakeOfRageMaxEther` | `EVENT_LAKE_OF_RAGE_MAX_ETHER` | -| `TM_DETECT` (TM43) | item ball at (35,2) | `LakeOfRageTMDetect` | `EVENT_LAKE_OF_RAGE_TM_DETECT` | -| `FULL_RESTORE` | hidden at (11,28) | `bg_event 11, 28, BGEVENT_ITEM` | `EVENT_LAKE_OF_RAGE_HIDDEN_FULL_RESTORE` | -| `RARE_CANDY` | hidden at (4,4) | `bg_event 4, 4, BGEVENT_ITEM` | `EVENT_LAKE_OF_RAGE_HIDDEN_RARE_CANDY` | -| `MAX_POTION` | hidden at (35,5) | `bg_event 35, 5, BGEVENT_ITEM` | `EVENT_LAKE_OF_RAGE_HIDDEN_MAX_POTION` | - -TM numbering, from the `add_tm` ladder in `constants/item_constants.asm:218-264`: -`TM_HIDDEN_POWER` is TM10 and `TM_DETECT` is TM43 (`__tmhm_value__` skips -`ITEM_C3` and `ITEM_DC`, which is why the item ids and TM numbers do not line up -one-for-one). - -**Trainers** - none reachable in this section (all four are masked by -`EVENT_LAKE_OF_RAGE_CIVILIANS`). For completeness, once unmasked: -`FISHER ANDRE` (id 8, `parties.asm:1542`, L27 `GYARADOS`), -`FISHER RAYMOND` (id 9, `parties.asm:1547`, four L22 `MAGIKARP`), -`COOLTRAINERM AARON` (id 2, `parties.asm:749`, L24 `IVYSAUR`/`CHARMELEON`/`WARTORTLE`), -`COOLTRAINERF LOIS` (id 2, `parties.asm:875`, `TRAINERTYPE_MOVES`, L25 `SKIPLOOM` -and L25 `NINETALES`). - -**Wild encounters** - -There is **no** `def_grass_wildmons LAKE_OF_RAGE` entry in -`data/wild/johto_grass.asm` - the grass here is empty. - -Water (`data/wild/johto_water.asm:253`): 4% in Gold, 6% in Silver; -L15 `MAGIKARP`, L10 `MAGIKARP`, L15 `GYARADOS`. - -Fishing: header says `FISHGROUP_GYARADOS` -> `data/wild/fish.asm:132` -`.Gyarados_Old` / `.Gyarados_Good` / `.Gyarados_Super` are Magikarp in every -slot (the `time_group 14` / `time_group 15` slots aside). This is the "lake full -of Gyarados but nothing else" that Lance narrates. - -Headbutt: `data/wild/treemon_maps.asm:37` -> `treemon_map LAKE_OF_RAGE, TREEMON_SET_FOREST`. - -The Red Gyarados itself is **not** a wild-table roll: `loadwildmon GYARADOS, 30` -plus `BATTLETYPE_FORCESHINY`. - ---- - -### MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE - -- Script: `maps/LakeOfRageHiddenPowerHouse.asm` (`HiddenPowerGuy` = `53:54fd`, `pokegold.sym:25334`) -- Blocks: shared, `data/maps/blocks.asm:165` -- Header: `data/maps/maps.asm:237` -> `map LakeOfRageHiddenPowerHouse, TILESET_HOUSE, INDOOR, LANDMARK_LAKE_OF_RAGE, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:217` -> `map_const LAKE_OF_RAGE_HIDDEN_POWER_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `LAKE_OF_RAGE` | 1 | -| 2 | 3 | 7 | `LAKE_OF_RAGE` | 1 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `HiddenPowerHouseBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `HiddenPowerHouseBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LAKEOFRAGEHIDDENPOWERHOUSE_FISHER` | `SPRITE_FISHER` | 2 | 3 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `HiddenPowerGuy` | -1 | - -`HiddenPowerGuy`: `checkevent EVENT_GOT_TM10_HIDDEN_POWER` -> already-got text; -else `verbosegiveitem TM_HIDDEN_POWER`, `iffalse .Done`, `setevent -EVENT_GOT_TM10_HIDDEN_POWER`. Guarded, so a full bag is safe. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_HIDDEN_POWER` (TM10) | talk to the fisher at (2,3) | `HiddenPowerGuy` | `EVENT_GOT_TM10_HIDDEN_POWER` (`constants/event_flags.asm:97`) | - ---- - -### MAP_LAKE_OF_RAGE_MAGIKARP_HOUSE - -- Script: `maps/LakeOfRageMagikarpHouse.asm` -- Blocks: shared, `data/maps/blocks.asm:166` -- Header: `data/maps/maps.asm:238` -> `map LakeOfRageMagikarpHouse, TILESET_HOUSE, INDOOR, LANDMARK_LAKE_OF_RAGE, MUSIC_LAKE_OF_RAGE, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:218` -> `map_const LAKE_OF_RAGE_MAGIKARP_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `LAKE_OF_RAGE` | 2 | -| 2 | 3 | 7 | `LAKE_OF_RAGE` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `MagikarpHouseBookshelf` | -| 1 | 1 | `BGEVENT_READ` | `MagikarpHouseBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LAKEOFRAGEMAGIKARPHOUSE_FISHING_GURU` | `SPRITE_FISHING_GURU` | 2 | 3 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `MagikarpLengthRaterScript` | -1 | - -`MagikarpLengthRaterScript` in this section takes the -`EVENT_LAKE_OF_RAGE_EXPLAINED_WEIRD_MAGIKARP` path only: first talk sets that -flag and tells the lake's history, second talk gives the "men in black" line. -The measuring contest (`special CheckMagikarpLength`, `verbosegiveitem ETHER`) -sits behind `checkevent EVENT_CLEARED_ROCKET_HIDEOUT`, which is the next section -onwards. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Route 42's two lakes | `engine/events/overworld.asm:322` `SurfFunction` -> `.TrySurf`: `ld de, ENGINE_FOGBADGE` / `call CheckBadge` (`:50`), then `GetTilePermission` must be `WATER_TILE` | FOG BADGE + a party mon that knows SURF (`CheckPartyMove`, `:64`) | Morty, section 8 | -| The Route 42 cut trees in front of the apricorn trees | `engine/events/overworld.asm:117` `CutFunction` -> `.CheckAble`: `ld de, ENGINE_HIVEBADGE` / `CheckBadge`, then `CheckMapForSomethingToCut` | HIVE BADGE + a party mon that knows CUT | Bugsy, section 5. Tile positions are in `maps/Route42.blk`, not in any event table | -| Reaching the Red Gyarados at (18,22) | it is a water tile in the middle of `maps/LakeOfRage.blk`; no script gate | SURF | same as above | -| Mahogany east exit (to Route 44) | `maps/MahoganyTown.asm` `coord_event 19, 8` / `coord_event 19, 9` on `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR`, plus the `MAHOGANYTOWN_POKEFAN_M` object at (19,8) with `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` clear | none - it is unconditional | `engine/events/std_scripts.asm:261` `RadioTowerRocketsScript` (`setevent` + `setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP`). Far beyond this section. Buying a RageCandyBar does **not** help | -| Mahogany Gym door at (6,13) | `maps/MahoganyTown.asm` `object_event 6, 14, SPRITE_FISHER ... EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` (clear = NPC present, standing on the approach tile) | none | `maps/TeamRocketBaseB2F.asm:303` sets the flag after the hideout is cleared | -| Route 43 gate toll | `maps/Route43Gate.asm` scene 0 `Route43GateRocketShakedownScene` -> `sdefer Route43GateRocketTakeoverScript`, re-armed on every Route 43 load by `Route43CheckIfRocketsScene` | 1000 yen, or everything you have (`takemoney` runs in both branches) | not a hard gate - you always pass. `maps/TeamRocketBaseB2F.asm:302` removes the grunts permanently | -| Lance at (21,28) | `object_event ... EVENT_LAKE_OF_RAGE_LANCE`, set by `InitializeEventsScript` (`engine/events/std_scripts.asm:516`) | beat or catch the Red Gyarados | `RedGyarados`'s `appear LAKEOFRAGE_LANCE` | -| Wesley / `BLACKBELT_I` | `LakeOfRageWesleyCallback` (`MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY` / `ifequal WEDNESDAY` | the in-game day must be Wednesday | real clock; no in-game unlock | -| Lake of Rage trainers and the TM36 officer | `EVENT_LAKE_OF_RAGE_CIVILIANS`, set by `InitializeEventsScript:500` | - | `maps/TeamRocketBaseB2F.asm:305` `clearevent`. Not this section | -| Magikarp length contest / `ETHER` | `MagikarpLengthRaterScript`'s `checkevent EVENT_CLEARED_ROCKET_HIDEOUT` | Rocket hideout cleared | next section | -| Team Rocket base entrance | `MahoganyMart1FStaircaseCallback`'s `checkevent EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` -> `changeblock 6, 2, $1e` | Lance uncovers it | armed here by `setmapscene MAHOGANY_MART_1F, SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` | - ---- - -## 4. Bot checklist - -Coordinates are map coordinates. "Talk" means face the tile and press A. - -1. `MAP_ECRUTEAK_CITY` - Fly to `SPAWN_ECRUTEAK` (lands at 23,28). - Pre: `ENGINE_FLYPOINT_ECRUTEAK`. Party must contain SURF and CUT users. -2. `MAP_ECRUTEAK_CITY` - walk to warp 1 (35,26) or 2 (35,27). Post: on - `MAP_ROUTE_42_ECRUTEAK_GATE`. -3. `MAP_ROUTE_42_ECRUTEAK_GATE` - walk east to warp 3 (9,4) / 4 (9,5). Post: on - `MAP_ROUTE_42` at (0,8)/(0,9). -4. `MAP_ROUTE_42` - walk to (6,4), talk to `ROUTE42_POKE_BALL1`. - Pre: `EVENT_ROUTE_42_ULTRA_BALL` clear. Post: it is set, `ULTRA_BALL` in bag. -5. `MAP_ROUTE_42` - do **not** enter warps 3/4/5 (Mt. Mortar); the walkthrough - defers them to the Waterfall trip. -6. `MAP_ROUTE_42` - use SURF to cross the first lake (badge: `ENGINE_FOGBADGE`). -7. `MAP_ROUTE_42` - use CUT on the tree(s) south of the lake (badge: - `ENGINE_HIVEBADGE`), then talk to the fruit trees at (27,16), (28,16), - (29,16). Post: `PNK_APRICORN`, `GRN_APRICORN`, `YLW_APRICORN`; each tree is - bare until the next daily reset. -8. `MAP_ROUTE_42` - optional: Itemfinder / step on (16,11) for the hidden - `MAX_POTION`. Post: `EVENT_ROUTE_42_HIDDEN_MAX_POTION`. -9. `MAP_ROUTE_42` - walk to (33,8), talk to `ROUTE42_POKE_BALL2`. - Post: `EVENT_ROUTE_42_SUPER_POTION`, `SUPER_POTION` in bag. -10. `MAP_ROUTE_42` - SURF the second lake, then battle `ROUTE42_FISHER` at - (40,10) (`FISHER CHRIS1`, L18 Qwilfish). Post: `EVENT_BEAT_FISHER_CHRIS`; - optionally accept `PHONE_FISHER_CHRIS`. -11. `MAP_ROUTE_42` - battle `ROUTE42_SUPER_NERD` at (47,8) (`POKEMANIAC SHANE`). - Post: `EVENT_BEAT_POKEMANIAC_SHANE`. -12. `MAP_ROUTE_42` - battle `ROUTE42_POKEFAN_M` at (51,9) (`HIKER BENJAMIN`). - Post: `EVENT_BEAT_HIKER_BENJAMIN`. -13. `MAP_ROUTE_42` - walk east off the map edge (x > 59) into - `MAP_MAHOGANY_TOWN`. Post: `ENGINE_FLYPOINT_MAHOGANY` set by - `MahoganyTownFlypointCallback`. -14. `MAP_MAHOGANY_TOWN` - warp 4 (15,13) -> Pokecenter; talk to the nurse at - (3,1) from (3,2); leave by warp 1/2. -15. `MAP_MAHOGANY_TOWN` - optional flavour: warp 1 (11,7) -> Mahogany Mart, talk - to `MAHOGANYMART1F_PHARMACIST` at (4,3) for `MART_MAHOGANY_1` (the 9800-yen - `SLOWPOKETAIL`). Warp 2 (17,7) -> Red Gyarados speech house. -16. `MAP_MAHOGANY_TOWN` - **avoid** (19,8) and (19,9) unless you want the - RageCandyBar scene; it costs 300 and never opens the east exit. Do not try - the Gym at (6,13): `MAHOGANYTOWN_FISHER` at (6,14) is in the way. -17. `MAP_MAHOGANY_TOWN` - walk to warp 5 (9,1). Post: on - `MAP_ROUTE_43_MAHOGANY_GATE` at (4,7)/(5,7). -18. `MAP_ROUTE_43_MAHOGANY_GATE` - walk north to warp 1 (4,0) / 2 (5,0). Post: - on `MAP_ROUTE_43` at (9,51)/(10,51). Loading Route 43 runs - `Route43CheckIfRocketsScene`, which arms `SCENE_ROUTE43GATE_ROCKET_SHAKEDOWN`. -19. `MAP_ROUTE_43` - battle `ROUTE43_YOUNGSTER` at (15,43) (`CAMPER SPENCER`). - Post: `EVENT_BEAT_CAMPER_SPENCER`. -20. `MAP_ROUTE_43` - do **not** enter warp 3 (17,35) or warps 4/5 - (17,31)/(18,31): those are the toll booth. Take the west/grass path instead. -21. `MAP_ROUTE_43` - optional: talk to the fruit tree at (1,26) for - `BITTER_BERRY`; battle `ROUTE43_FISHER` at (4,16) (`FISHER MARVIN`). Post: - `EVENT_BEAT_FISHER_MARVIN`. -22. `MAP_ROUTE_43` - battle `ROUTE43_LASS` at (9,29) (`PICNICKER TIFFANY3`, L20 - Clefairy). Post: `EVENT_BEAT_PICNICKER_TIFFANY`; optional phone. -23. `MAP_ROUTE_43` - talk to `ROUTE43_POKE_BALL` at (12,32). Post: - `EVENT_ROUTE_43_MAX_ETHER`, `MAX_ETHER` in bag. -24. `MAP_ROUTE_43` - battle `ROUTE43_SUPER_NERD2` at (13,20) (`POKEMANIAC - BRENT1`). Post: `EVENT_BEAT_POKEMANIAC_BRENT`; optional phone. -25. `MAP_ROUTE_43` - battle `ROUTE43_SUPER_NERD3` at (13,7) (`POKEMANIAC RON`) - and `ROUTE43_SUPER_NERD1` at (14,6) (`POKEMANIAC BEN`). Post: - `EVENT_BEAT_POKEMANIAC_RON`, `EVENT_BEAT_POKEMANIAC_BEN`. -26. `MAP_ROUTE_43` - walk north off the map edge (y < 0) into - `MAP_LAKE_OF_RAGE`. Post: `ENGINE_FLYPOINT_LAKE_OF_RAGE`. -27. `MAP_LAKE_OF_RAGE` - CUT your way northwest; talk to - `LAKEOFRAGE_POKE_BALL1` at (7,10). Post: `EVENT_LAKE_OF_RAGE_MAX_ETHER`. -28. `MAP_LAKE_OF_RAGE` - if `VAR_WEEKDAY == WEDNESDAY`, talk to - `LAKEOFRAGE_WESLEY` at (4,4). Post: `EVENT_MET_WESLEY_OF_WEDNESDAY`, - `EVENT_GOT_BLACKBELT_FROM_WESLEY`, `BLACKBELT_I` in bag. Then step on (4,4) - for the hidden `RARE_CANDY`. -29. `MAP_LAKE_OF_RAGE` - warp 1 at (7,3) -> `MAP_LAKE_OF_RAGE_HIDDEN_POWER_HOUSE`; - talk to the fisher at (2,3). Pre: `EVENT_GOT_TM10_HIDDEN_POWER` clear. - Post: it is set, `TM_HIDDEN_POWER` in bag. Leave by (2,7)/(3,7). -30. `MAP_LAKE_OF_RAGE` - CUT northeast; talk to `LAKEOFRAGE_POKE_BALL2` at - (35,2). Post: `EVENT_LAKE_OF_RAGE_TM_DETECT`, `TM_DETECT` (TM43) in bag. - Optional hidden `MAX_POTION` at (35,5). -31. `MAP_LAKE_OF_RAGE` - optional hidden `FULL_RESTORE` at (11,28). -32. **Save.** Make sure the bag has a free slot: `RedGyarados`'s `giveitem - RED_SCALE` is unguarded. -33. `MAP_LAKE_OF_RAGE` - SURF onto the lake and face `LAKEOFRAGE_GYARADOS` at - (18,22); press A. Pre: `EVENT_LAKE_OF_RAGE_RED_GYARADOS` clear. The script - runs `loadwildmon GYARADOS, 30` + `loadvar VAR_BATTLETYPE, - BATTLETYPE_FORCESHINY`. Throw Great Balls; do not KO it if you want it. - Post: `EVENT_LAKE_OF_RAGE_RED_GYARADOS` set (on a win or a catch), - `RED_SCALE` in bag, `EVENT_LAKE_OF_RAGE_LANCE` cleared (Lance appears). -34. `MAP_LAKE_OF_RAGE` - walk to (21,29) and face north to talk to - `LAKEOFRAGE_LANCE` at (21,28). Answer **YES** to `yesorno`. - Post: `EVENT_DECIDED_TO_HELP_LANCE` set, - `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` cleared, `MAHOGANY_MART_1F` scene - set to `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS`, Lance disappears. - Answering NO sets `EVENT_REFUSED_TO_HELP_LANCE_AT_LAKE_OF_RAGE` and is - recoverable by talking again. -35. Fly to `SPAWN_MAHOGANY` (lands at 15,14). Section complete; the next section - starts at `MAP_MAHOGANY_MART_1F` warp 1 (11,7). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map geometry, warps, coord/bg/object events for every map in this section | `src/import/RomExtractorGen2.lua:804-975` (`coordEvents`/`bgEvents`/`objects`), `src/world/gen2/Map.lua`, `src/world/gen2/World.lua:5013` (coord events), `:5148` (bg events) | implemented (data-driven from the ROM; nothing is hand-transcribed, so these tables come across as-is) | -| Map header fields (tileset/environment/landmark/music/palette/fish group) | `src/import/RomExtractorGen2.lua:741,965` | implemented | -| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_OBJECTS` (the two flypoint callbacks, `Route43CheckIfRocketsScene`, `LakeOfRageWesleyCallback`) | `src/script/gen2/Vm.lua` `runCallback`, `tests/gen2_map_callbacks_test.lua`, `tests/drivers/gold_map_callbacks.lua` | implemented | -| `ENGINE_FLYPOINT_MAHOGANY` / `ENGINE_FLYPOINT_LAKE_OF_RAGE` and Fly | `src/world/gen2/FieldMoves.lua:352-353` (`SPAWN_MAHOGANY` flag 72, `SPAWN_LAKE_OF_RAGE` flag 73) | implemented | -| `readvar VAR_WEEKDAY` (Wesley of Wednesday) | `src/world/gen2/World.lua` `World:weekday`, `src/script/gen2/Vm.lua` `readvar` | implemented | -| Fruit trees (`FRUITTREE_ROUTE_42_1/2/3`, `FRUITTREE_ROUTE_43`) with the daily reset | `src/core/gen2/Apricorns.lua:362` (the `BITTER_BERRY` row is `FRUITTREE_ROUTE_43`), `World:fruitTreeItem` / `fruitTreePick` | implemented | -| `itemball` / `hiddenitem` one-time flags | `src/script/gen2/CallAsm.lua`, `src/world/gen2/HiddenItems.lua` | implemented | -| Trainer objects, sight range, `loadtrainer` / `startbattle` | `src/world/gen2/Trainers.lua`, `src/script/gen2/Vm.lua` | implemented | -| Phone contacts for `FISHER CHRIS1`, `POKEMANIAC BRENT1`, `PICNICKER TIFFANY3` | `src/core/gen2/Phone.lua:201` (Chris, `ROUTE_42`), `:204` (Brent, `ROUTE_43`), `:207` (Tiffany, `ROUTE_43`) | implemented | -| Roaming beasts crossing Route 42/43 | `src/core/gen2/Roamers.lua:70-72` | implemented | -| RageCandyBar merchant scene (`showemote`, `applymovement`, `follow`/`stopfollow`, `checkmoney`/`takemoney`, `special PlaceMoneyTopRight`) | `src/script/gen2/Vm.lua` (all five opcodes present), `src/script/gen2/Specials.lua:1301` | implemented | -| Route 43 gate toll scene (`sdefer`, `readvar VAR_FACING`, `big_step` movement, `special RestartMapMusic`, `setscene`) | `src/script/gen2/Vm.lua`, `src/script/gen2/Specials.lua:1059` | implemented | -| Magikarp length rater (`special CheckMagikarpLength`, `MagikarpHouseSign`, `FindPartyMonThatSpecies`) | `src/script/gen2/Specials.lua:409,443,1124` | implemented (though the contest arm is gated behind `EVENT_CLEARED_ROCKET_HIDEOUT`, i.e. the next section) | -| Red Gyarados encounter plumbing (`loadwildmon`, `cry`, `startbattle`, `giveitem`, `itemnotify`, `appear`/`disappear`) | `src/script/gen2/Vm.lua`, `src/world/gen2/World.lua:4420` | implemented | -| **`BATTLETYPE_FORCESHINY` actually making the Red Gyarados shiny** | `src/world/gen2/World.lua:4591` sets `opts.battleType`, but `World:startBattle` (`:4420`) never reads it, and the wild mon is built by `Mon.new(data, id, wild.level or 5)` at `:4572` with `Mon.randomDVs()` | **missing** - the Lake of Rage Gyarados will be an ordinary blue L30 Gyarados except by 1-in-8192 luck. `World.lua:4576` handles `BATTLETYPE_FORCEITEM` only. The shiny *rendering* path is proven by `tests/drivers/gold_shiny_shots.lua`, which builds the mon with the `$EA`/`$AA` DVs by hand; nothing wires those DVs to the script opcode | -| `teleport_from` (`$4c`) - Lance's warp-into-the-sky animation | `src/script/gen2/Movement.lua` `decodeByte` has no case for `$4c`; it falls through to `{ kind = "nop" }` | partial - the script still runs and Lance still `disappear`s, so the flags are correct; only the animation is absent | -| Mahogany Mart `pokemart MARTTYPE_STANDARD, MART_MAHOGANY_1` | `src/ui/gen2/MartMenu.lua` + the extracted mart tables | implemented (not exercised by any `gold_*` driver for this map) | -| Any driver that plays this stretch | `tests/drivers/` has no `gold_lake_of_rage.lua` / `gold_route43.lua` | **missing** - no scripted run covers section 10 | - ---- - -## 6. Unresolved / verify by hand - -1. **"TM43 Secret Power"** - the walkthrough uses the HGSS move name. In - Gold/Silver `LakeOfRageTMDetect` is `itemball TM_DETECT`, and the `add_tm` - ladder in `constants/item_constants.asm:264` puts `DETECT` at TM43. The item - is right, the move name in the FAQ is wrong. -2. **"Items found in Mahogany Town: TM16"** - there is no TM16 anywhere in - `maps/MahoganyTown.asm`. TM16 is `ICY_WIND` (`constants/item_constants.asm:234`), - which is Pryce's Gym reward - a different section, and unreachable here - because `MAHOGANYTOWN_FISHER` blocks the Gym door. -3. **Camper Spencer's party** - the walkthrough says L17 Sandshrew / **L18** - Sandslash / L19 Zubat. `data/trainers/parties.asm:2817` says L17 `SANDSHREW`, - **L17** `SANDSLASH`, L19 `ZUBAT`. Trust the asm. -4. **"Wesley ... on the left path of Route 43"** - Wesley is not on Route 43 at - all. `object_event 4, 4, SPRITE_SUPER_NERD ... WesleyScript` is on - `MAP_LAKE_OF_RAGE`, in the far northwest corner behind the cut trees. -5. **"Max Ether ... to the right of Picnicker Tiffany"** - the ball is at - (12,32) and Tiffany is at (9,29), so it is right *and* three rows south of - her. Close but not the same row. -6. **Where the Cut trees are on Route 42 and Lake of Rage** - not in any event - table. They are block ids inside `maps/Route42.blk` and `maps/LakeOfRage.blk`, - read by `CheckMapForSomethingToCut` (`engine/events/overworld.asm`, called from - `CutFunction.CheckAble`). A bot needs the block data, not the asm, to know - which tiles to cut. Same for the exact water tiles a Surf entry point needs - on Route 42 and Lake of Rage, and for the walkthrough's claim that Route 43's - west grass path bypasses `ROUTE_43_GATE`. -7. **"Your Mom calls you"** on Route 43 - this is the generic - `SPECIALCALL_*` / Mom-shopping call, not anything in `maps/Route43.asm`. - There is no `specialphonecall` in the Route 43 script. Which call fires and - when lives in `data/events/special_phone_calls.asm` and - `src/core/gen2/MomShopping.lua`; not pinned down for this section. -8. **Fisher Marvin's fourth mon EXP** - the walkthrough leaves the L15 Gyarados - EXP as "?". Nothing in the asm to resolve; it is a derived number. -9. **`LakeOfRage_MapScripts`'s two scene scripts are commented `; unusable`** and - `RedGyarados` does `setscene 0` with the comment "Lake of Rage does not have a - scene variable". Harmless on the cart, but a port that models `setscene` as a - write to a per-map byte should confirm it does not corrupt a neighbouring - map's scene. -10. **`Route43Gate` warp 4 (5,7) points at `ROUTE_43` warp 3**, the same as warp - 3 (4,7). Transcribed as written; if a bot walks out of the gate's - bottom-right tile it lands on the same Route 43 warp as the bottom-left one. -11. **Gyarados loss path** - `RedGyarados`'s `ifequal LOSE, .NotBeaten` only - skips the `disappear`; the `giveitem RED_SCALE` and `appear LAKEOFRAGE_LANCE` - run either way. That reads like a cart quirk rather than a transcription - error, but it has not been observed on hardware for this document. diff --git a/docs/gold-walkthrough/asm-walk/section-11-team-rocket-hideout-and-mahogany-town-gym.md b/docs/gold-walkthrough/asm-walk/section-11-team-rocket-hideout-and-mahogany-town-gym.md deleted file mode 100644 index 57e640f7..00000000 --- a/docs/gold-walkthrough/asm-walk/section-11-team-rocket-hideout-and-mahogany-town-gym.md +++ /dev/null @@ -1,851 +0,0 @@ -# Section 11 - Team Rocket Hideout and Mahogany Town Gym - -Source: `../section-11-team-rocket-hideout-and-mahogany-town-gym.txt` -(the FAQ's own heading is "17 > Team Rocket Hideout and Mahogany Town Gym") - -Maps covered: `MAP_MAHOGANY_TOWN`, `MAP_MAHOGANY_POKECENTER_1F`, `MAP_MAHOGANY_MART_1F`, -`MAP_TEAM_ROCKET_BASE_B1F`, `MAP_TEAM_ROCKET_BASE_B2F`, `MAP_TEAM_ROCKET_BASE_B3F`, -`MAP_MAHOGANY_GYM` - -Badges / key milestones in this section: - -- `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` - the hideout entrance exists -- `EVENT_LEARNED_SLOWPOKETAIL`, `EVENT_LEARNED_RATICATE_TAIL` - Giovanni's-office door -- `EVENT_LEARNED_HAIL_GIOVANNI` - transmitter-room door -- `EVENT_GOT_HM06_WHIRLPOOL` (HM06 = `HM_WHIRLPOOL`) -- `EVENT_CLEARED_ROCKET_HIDEOUT` + `clearflag ENGINE_ROCKET_SIGNAL_ON_CH20` -- `ENGINE_GLACIERBADGE` (GLACIERBADGE, badge 7) + `EVENT_GOT_TM16_ICY_WIND` (TM16 = `TM_ICY_WIND`) - -Entry precondition, set in the previous section: `maps/LakeOfRage.asm` -`LakeOfRageLanceScript.AgreedToHelp` runs -`clearevent EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE`, -`setevent EVENT_DECIDED_TO_HELP_LANCE`, -`setmapscene MAHOGANY_MART_1F, SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS`. -Without that scene set, the mart is an ordinary shop and there is no staircase. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_MAHOGANY_TOWN` | `maps/MahoganyTown.asm` | from Route 43 gate (warp 5, `9,1`) or fly point `ENGINE_FLYPOINT_MAHOGANY` | warp 4 `15,13` -> Pokecenter, warp 1 `11,7` -> Mart | "Heal at the Pokemon Center and withdraw your Gyarados" | -| 2 | `MAP_MAHOGANY_POKECENTER_1F` | `maps/MahoganyPokecenter1F.asm` | warp 1/2 `3,7` / `4,7` | same | heal / PC withdraw | -| 3 | `MAP_MAHOGANY_MART_1F` | `maps/MahoganyMart1F.asm` | warp 1/2 `3,7` / `4,7` | warp 3 `7,3` | Lance's Dragonite Hyper Beams the shop open, uncovers the staircase | -| 4 | `MAP_TEAM_ROCKET_BASE_B1F` | `maps/TeamRocketBaseB1F.asm` | warp 1 `27,2` | warp 2 `3,14` | security cameras, GruntM16, Scientist Jed, secret switch, 3 item balls | -| 5 | `MAP_TEAM_ROCKET_BASE_B2F` (bottom region) | `maps/TeamRocketBaseB2F.asm` | warp 1 `3,14` | warp 5 `27,14` | Lance heals you; GruntM19 then GruntM17 | -| 6 | `MAP_TEAM_ROCKET_BASE_B3F` (right region) | `maps/TeamRocketBaseB3F.asm` | warp 4 `27,14` | warp 2 `27,2` | Lance's two-password speech, 4 item balls, Ross / GruntF5 / Mitch / GruntM28, both passwords | -| 7 | `MAP_TEAM_ROCKET_BASE_B2F` (top region) | `maps/TeamRocketBaseB2F.asm` | warp 3 `27,2` | warp 2 `3,2` | walk left past GruntM18 | -| 8 | `MAP_TEAM_ROCKET_BASE_B3F` (left region) | `maps/TeamRocketBaseB3F.asm` | warp 1 `3,2` | warp 3 `3,6` | rival cutscene, password door, ExecutiveM 4, Murkrow gives HAIL GIOVANNI | -| 9 | `MAP_TEAM_ROCKET_BASE_B2F` (TM room) | `maps/TeamRocketBaseB2F.asm` | warp 4 `3,6` | warp 4 `3,6` | "the second stairs that you did NOT come out of" - TM46 Thief at `3,10` | -| 10 | back through 8 -> 7 -> 6 -> 5 | | | | "Head back the way you came, past the spinning Team Rocket member" | -| 11 | `MAP_TEAM_ROCKET_BASE_B2F` (transmitter room) | `maps/TeamRocketBaseB2F.asm` | password door at `14,12` / `15,12` | warp 1 `3,14` (then B1F warp panel `5,15`) | ExecutiveF 2 ambush, 3 Electrode, HM06 Whirlpool from Lance | -| 12 | `MAP_MAHOGANY_TOWN` | `maps/MahoganyTown.asm` | Mart warps 1/2 | warp 3 `6,13` | heal, then the gym | -| 13 | `MAP_MAHOGANY_GYM` | `maps/MahoganyGym.asm` | warp 1/2 `4,17` / `5,17` | same | five ice-puzzle trainers, then Pryce -> GLACIERBADGE + TM16 | - -Spill into the next section: the last paragraph ("You can now use Whirlpool, once you -find that in a dark cavern") points at `MAP_ICE_PATH_1F` / `MAP_ICE_PATH_B2F_MAHOGANY_SIDE`. -Not covered here. - ---- - -## 2. Maps - -### MAP_MAHOGANY_TOWN - -- Script: `maps/MahoganyTown.asm` -- Blocks: `maps/MahoganyTown.blk` -- Header: `data/maps/maps.asm:74` -> `map MahoganyTown, TILESET_JOHTO, TOWN, LANDMARK_MAHOGANY_TOWN, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm` -> `map_const MAHOGANY_TOWN, 10, 9` (group `MAHOGANY` = 2, map id 7); 10x9 blocks = 20x18 map cells -- Connections: `data/maps/attributes.asm:152-155` -> north `Route43`, west `Route42`, east `Route44` -- Scene ids (declared inline by `scene_script`): `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` = 0, `SCENE_MAHOGANYTOWN_NOOP` = 1 -- Callback: `MAPCALLBACK_NEWMAP, MahoganyTownFlypointCallback` -> `setflag ENGINE_FLYPOINT_MAHOGANY` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 7 | `MAHOGANY_MART_1F` | 1 | -| 2 | 17 | 7 | `MAHOGANY_RED_GYARADOS_SPEECH_HOUSE` | 1 | -| 3 | 6 | 13 | `MAHOGANY_GYM` | 1 | -| 4 | 15 | 13 | `MAHOGANY_POKECENTER_1F` | 1 | -| 5 | 9 | 1 | `ROUTE_43_MAHOGANY_GATE` | 3 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` (0) | 19 | 8 | `MahoganyTownTryARageCandyBarScript` | merchant steps in front of you and pitches a RAGECANDYBAR | -| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` (0) | 19 | 9 | `MahoganyTownTryARageCandyBarScript` | same | - -The script never calls `setscene`, so the pitch re-fires every time you cross x=19 -until `RadioTowerRocketsScript` (`engine/events/std_scripts.asm:263`) runs -`setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP`. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 5 | `BGEVENT_READ` | `MahoganyTownSign` | -| 9 | 7 | `BGEVENT_READ` | `MahoganyTownRagecandybarSign` | -| 3 | 13 | `BGEVENT_READ` | `MahoganyGymSign` | -| 16 | 13 | `BGEVENT_READ` | `MahoganyTownPokecenterSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MAHOGANYTOWN_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownPokefanMScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` | -| `MAHOGANYTOWN_GRAMPS` | `SPRITE_GRAMPS` | 6 | 9 | `WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `MahoganyTownGrampsScript` | -1 | -| `MAHOGANYTOWN_FISHER` | `SPRITE_FISHER` | 6 | 14 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownFisherScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` | -| `MAHOGANYTOWN_LASS` | `SPRITE_LASS` | 12 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownLassScript` | `EVENT_MAHOGANY_MART_OWNERS` | - -Object-event flag polarity (verified in `engine/overworld/map_objects_2.asm` -`CheckObjectFlag`): the flag being **set** masks (hides) the object; `-1` means -always visible. So `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` clear = the fisher -stands on `6,14`, one cell south of the gym warp at `6,13`, blocking it. That flag -is set by `RocketBaseElectrodeScript` at the end of the hideout, which is the real -gate on the Mahogany gym (see section 3). - -### MAP_MAHOGANY_POKECENTER_1F - -- Script: `maps/MahoganyPokecenter1F.asm` -- Header: `data/maps/maps.asm:70` -> `TILESET_POKECENTER, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const MAHOGANY_POKECENTER_1F, 5, 4` (group 2, map id 3) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `MAHOGANY_TOWN` | 4 | -| 2 | 4 | 7 | `MAHOGANY_TOWN` | 4 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -No coord events, no bg events. Nurse is `MAHOGANYPOKECENTER1F_NURSE` -`SPRITE_NURSE` at `3,1`, script `MahoganyPokecenter1FNurseScript` -> -`jumpstd PokecenterNurseScript`. - -### MAP_MAHOGANY_MART_1F - -- Script: `maps/MahoganyMart1F.asm` (`45:4000 MahoganyMart1F_MapScripts`) -- Blocks: shares `maps/GiftShop.blk` (`data/maps/blocks.asm:700` -> `MahoganyMart1F_Blocks: MountMoonGiftShop_Blocks: INCBIN "maps/GiftShop.blk"`) -- Header: `data/maps/maps.asm:118` -> `TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_MAHOGANY_MART, TRUE (no phone), PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const MAHOGANY_MART_1F, 4, 4` (group `DUNGEONS` = 3, map id 40) -- Scene ids: `SCENE_MAHOGANYMART1F_NOOP` = 0, `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` = 1 -- Callback: `MAPCALLBACK_TILES, MahoganyMart1FStaircaseCallback` - if `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` is set, `changeblock 6, 2, $1e ; stairs` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `MAHOGANY_TOWN` | 1 | -| 2 | 4 | 7 | `MAHOGANY_TOWN` | 1 | -| 3 | 7 | 3 | `TEAM_ROCKET_BASE_B1F` | 1 | - -`def_coord_events` and `def_bg_events` are both empty. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MAHOGANYMART1F_PHARMACIST` | `SPRITE_PHARMACIST` | 4 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FPharmacistScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `MAHOGANYMART1F_BLACK_BELT` | `SPRITE_BLACK_BELT` | 1 | 6 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FBlackBeltScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `MAHOGANYMART1F_LANCE` | `SPRITE_LANCE` | 4 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` | -| `MAHOGANYMART1F_DRAGONITE` | `SPRITE_DRAGON` | 3 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` | -| `MAHOGANYMART1F_GRANNY` | `SPRITE_GRANNY` | 1 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FGrannyScript` | `EVENT_MAHOGANY_MART_OWNERS` | - -**Scripts of interest** - -`MahoganyMart1FLanceUncoversStaircaseScript` (`45:4046`), reached as -`scene_script ... SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` via `sdefer`, so it -fires on map entry without any player input: - -1. Dragonite `big_step LEFT / big_step RIGHT` into the Black Belt, `disappear MAHOGANYMART1F_DRAGONITE` -2. Lance walks over (`MahoganyMart1FLanceApproachPlayerMovement`), `follow MAHOGANYMART1F_LANCE, PLAYER`, shoves the pharmacist aside -3. `changeblock 6, 2, $1e` + `refreshmap` + `setevent EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` -4. `disappear MAHOGANYMART1F_LANCE`, `setscene SCENE_MAHOGANYMART1F_NOOP` - -`changeblock`'s operands are **map cell coordinates**, not block coordinates -(`Script_changeblock` at `engine/overworld/scripting.asm:2031` adds 4 to each and -calls `GetBlockLocation`, which halves them - `home/map.asm:2099`). `6,2` is -therefore block (3,1), covering cells (6..7, 2..3); block `$1e` in -`data/tilesets/traditional_house_collision.asm` is `FLOOR, FLOOR, FLOOR, LADDER`, -putting the LADDER on cell `7,3` - exactly the warp row above. - -Marts: `MahoganyMart1FPharmacistScript` -> `pokemart MARTTYPE_STANDARD, MART_MAHOGANY_1` -(closed once `EVENT_DECIDED_TO_HELP_LANCE` is set), -`MahoganyMart1FGrannyScript` -> `pokemart MARTTYPE_STANDARD, MART_MAHOGANY_2`. - -### MAP_TEAM_ROCKET_BASE_B1F - -- Script: `maps/TeamRocketBaseB1F.asm` (`45:430c TeamRocketBaseB1F_MapScripts`) -- Blocks: `maps/TeamRocketBaseB1F.blk` -- Header: `data/maps/maps.asm:119` -> `TILESET_UNDERGROUND, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const TEAM_ROCKET_BASE_B1F, 15, 9` (group 3, map id 41) = 30x18 cells -- Scene ids: `SCENE_TEAMROCKETBASEB1F_TRAPS` = 0 (the only one) -- Callback: `MAPCALLBACK_OBJECTS, TeamRocketBaseB1FHideSecurityGruntCallback` -> `disappear TEAMROCKETBASEB1F_ROCKET1` (the camera grunt is parked off-screen at `0,0` and teleported in by `moveobject`) - -The whole floor is one connected walkable region (flood-filled from -`maps/TeamRocketBaseB1F.blk` against `data/tilesets/underground_collision.asm`). - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 27 | 2 | `MAHOGANY_MART_1F` | 3 | -| 2 | 3 | 14 | `TEAM_ROCKET_BASE_B2F` | 1 | -| 3 | 5 | 15 | `TEAM_ROCKET_BASE_B1F` | 4 | -| 4 | 25 | 2 | `TEAM_ROCKET_BASE_B1F` | 3 | - -Warps 3 and 4 are the warp panel pair Scientist Jed describes: cell `5,15` has -collision `WARP_PANEL` and dumps you at `25,2`, two cells from the exit ladder. - -**Coord events** (`def_coord_events`) - all on scene `SCENE_TEAMROCKETBASEB1F_TRAPS` (0) - -Security cameras (each triggers `GRUNTM_20` then `GRUNTM_21`): - -| x | y | script label | guarding statue (bg_event) | -|---|---|---|---| -| 24 | 2 | `SecurityCamera1a` | `24,1` | -| 24 | 3 | `SecurityCamera1b` | `24,1` | -| 6 | 2 | `SecurityCamera2a` | `6,1` | -| 6 | 3 | `SecurityCamera2b` | `6,1` | -| 24 | 6 | `SecurityCamera3a` | `24,5` | -| 24 | 7 | `SecurityCamera3b` | `24,5` | -| 22 | 16 | `SecurityCamera4` | `22,15` | -| 8 | 16 | `SecurityCamera5` | `8,15` | - -Exploding-trap tiles (each is a one-shot wild battle with -`loadvar VAR_BATTLETYPE, BATTLETYPE_TRAP`): - -| x | y | script | mon | -|---|---|---|---| -| 2 | 7 | `ExplodingTrap1` | `KoffingExplodingTrap` - KOFFING L21 | -| 3 | 7 | `ExplodingTrap2` | `VoltorbExplodingTrap` - VOLTORB L23 | -| 4 | 7 | `ExplodingTrap3` | `GeodudeExplodingTrap` - GEODUDE L21 | -| 1 | 8 | `ExplodingTrap4` | VOLTORB L23 | -| 3 | 8 | `ExplodingTrap5` | GEODUDE L21 | -| 5 | 8 | `ExplodingTrap6` | KOFFING L21 | -| 3 | 9 | `ExplodingTrap7` | VOLTORB L23 | -| 4 | 9 | `ExplodingTrap8` | KOFFING L21 | -| 1 | 10 | `ExplodingTrap9` | KOFFING L21 | -| 2 | 10 | `ExplodingTrap10` | VOLTORB L23 | -| 3 | 10 | `ExplodingTrap11` | GEODUDE L21 | -| 5 | 10 | `ExplodingTrap12` | GEODUDE L21 | -| 2 | 11 | `ExplodingTrap13` | GEODUDE L21 | -| 4 | 11 | `ExplodingTrap14` | KOFFING L21 | -| 1 | 12 | `ExplodingTrap15` | VOLTORB L23 | -| 2 | 12 | `ExplodingTrap16` | KOFFING L21 | -| 4 | 12 | `ExplodingTrap17` | VOLTORB L23 | -| 5 | 12 | `ExplodingTrap18` | GEODUDE L21 | -| 1 | 13 | `ExplodingTrap19` | GEODUDE L21 | -| 3 | 13 | `ExplodingTrap20` | VOLTORB L23 | -| 4 | 13 | `ExplodingTrap21` | KOFFING L21 | -| 5 | 13 | `ExplodingTrap22` | VOLTORB L23 | - -Safe cells inside the 5x7 trap field (x 1..5, y 7..13) are exactly those with no -row above: `(1,7)`, `(5,7)`, `(2,8)`, `(4,8)`, `(1,9)`, `(2,9)`, `(5,9)`, `(4,10)`, -`(1,11)`, `(3,11)`, `(5,11)`, `(3,12)`, `(2,13)`. `(3,11)` is also the hidden REVIVE. -That is the "zig-zag" the walkthrough describes. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 19 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FSecretSwitch` | -| 24 | 1 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` | -| 6 | 1 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` | -| 8 | 15 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` | -| 22 | 15 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` | -| 24 | 5 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` | -| 20 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FBookshelf` (`jumpstd TeamRocketOathScript`) | -| 21 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FBookshelf` | -| 3 | 11 | `BGEVENT_ITEM` | `TeamRocketBaseB1FHiddenRevive` -> `hiddenitem REVIVE, EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `TEAMROCKETBASEB1F_ROCKET1` | `SPRITE_ROCKET` | 0 | 0 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_SECURITY_GRUNTS` | -| - | `SPRITE_ROCKET` | 2 | 4 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerGruntM16` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| - | `SPRITE_SCIENTIST` | 18 | 12 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerScientistJed` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 27 | 6 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FHyperPotion` | `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` | -| `TEAMROCKETBASEB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 15 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FNugget` | `EVENT_TEAM_ROCKET_BASE_B1F_NUGGET` | -| `TEAMROCKETBASEB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 21 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FXAccuracy` | `EVENT_TEAM_ROCKET_BASE_B1F_X_ACCURACY` | - -**Scripts of interest** - -- `SecurityCamera1a` .. `SecurityCamera5`: `checkevent EVENT_SECURITY_CAMERA_n` -> bail; - `PlaySecurityCameraSounds` (6x `SFX_LICK`); `checkevent EVENT_TEAM_ROCKET_BASE_POPULATION` - -> bail (never true, see below); `showemote EMOTE_SHOCK, PLAYER`; - `playmusic MUSIC_ROCKET_ENCOUNTER`; `moveobject TEAMROCKETBASEB1F_ROCKET1, <x>, <y>`; - `appear`; `applymovement SecurityCameraMovement*`; `scall TrainerCameraGrunt1`; - repeat for `TrainerCameraGrunt2`; `setevent EVENT_SECURITY_CAMERA_n`. - Both grunt scripts use `loadtrainer` + `startbattle` directly (no `trainer` macro, - so no per-trainer beat flag) - the same two teams are re-fought at every camera. -- `TeamRocketBaseB1FSecretSwitch` (`45:4757`): if `EVENT_TURNED_OFF_SECURITY_CAMERAS` - is already set, prints "the switch is turned off"; otherwise - `playsound SFX_TALLY` and sets - `EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5` in one go. - Cell `19,11` is a wall, read from `19,12` facing up. -- `TrainerScientistJed` -> `.Script` after-battle text tells you about the warp panel. -- `TrainerGruntM16` -> after-battle text about the traps. - -**Flags and events** (B1F) - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_SECURITY_CAMERA_1` .. `_5` | `constants/event_flags.asm:403-407` | set by each camera script and by the switch | camera n is spent | -| `EVENT_TURNED_OFF_SECURITY_CAMERAS` | `event_flags.asm:402` | `TeamRocketBaseB1FSecretSwitch`, `RocketBaseElectrodeScript` | switch state only | -| `EVENT_TEAM_ROCKET_BASE_SECURITY_GRUNTS` | `event_flags.asm:1147` | `disappear`/`appear` of `TEAMROCKETBASEB1F_ROCKET1` | ambush grunt visibility | -| `EVENT_TEAM_ROCKET_BASE_POPULATION` | `event_flags.asm:1148` | read by 8 camera scripts, shared visibility flag of 11 base NPCs | **never set anywhere in the ROM** - grep confirms no `setevent`/`disappear` on it, so those `checkevent` arms are dead and the base NPCs never vanish | -| `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` / `_NUGGET` / `_X_ACCURACY` | `event_flags.asm:1034-1036` | `itemball` | item ball taken | -| `EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` | `event_flags.asm:144` | `hiddenitem` | hidden REVIVE at `3,11` taken | -| `EVENT_EXPLODING_TRAP_1..22` | `constants/event_flags.asm` | each `ExplodingTrapN` | trap spent | - -**Items** (B1F) - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HYPER_POTION` | item ball at `27,6` | `TeamRocketBaseB1FHyperPotion` | `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` | -| `NUGGET` | item ball at `14,15` | `TeamRocketBaseB1FNugget` | `EVENT_TEAM_ROCKET_BASE_B1F_NUGGET` | -| `X_ACCURACY` | item ball at `21,12` | `TeamRocketBaseB1FXAccuracy` | `EVENT_TEAM_ROCKET_BASE_B1F_X_ACCURACY` | -| `REVIVE` | hidden, face cell `3,11` | `bg_event 3, 11, BGEVENT_ITEM` | `EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` | - -**Trainers** (B1F) - -| const | class | id | party (`data/trainers/parties.asm`) | script label | notes | -|---|---|---|---|---|---| -| `GRUNTM`, `GRUNTM_16` | `GRUNTM` (`$1f`) | 16 | RATTATA 16, RATTATA 16, RATTATA 16, RATTATA 16 | `TrainerGruntM16` | `EVENT_BEAT_ROCKET_GRUNTM_16`, sight 3 | -| `SCIENTIST`, `JED` | `SCIENTIST` (`$0e`) | 3 | MAGNEMITE 20 x3 | `TrainerScientistJed` | `EVENT_BEAT_SCIENTIST_JED`, sight 3 | -| `GRUNTM`, `GRUNTM_20` | `GRUNTM` | 20 | DROWZEE 17, ZUBAT 19 | `TrainerCameraGrunt1` | no beat flag, repeats per camera | -| `GRUNTM`, `GRUNTM_21` | `GRUNTM` | 21 | ZUBAT 16, GRIMER 17, RATTATA 18 | `TrainerCameraGrunt2` | no beat flag, repeats per camera | - -**Wild encounters**: none. `TEAM_ROCKET_BASE_B1F` has no entry in -`data/wild/johto_grass.asm`, `johto_water.asm`, `fish.asm` or `treemons.asm`. The -only wild battles are the `loadwildmon` traps above and the three Electrode on B2F. - -### MAP_TEAM_ROCKET_BASE_B2F - -- Script: `maps/TeamRocketBaseB2F.asm` (`45:4c2b TeamRocketBaseB2F_MapScripts`) -- Blocks: `maps/TeamRocketBaseB2F.blk` -- Header: `data/maps/maps.asm:120` -> `TILESET_FACILITY, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const TEAM_ROCKET_BASE_B2F, 15, 9` (group 3, map id 42) = 30x18 cells -- Scene ids: `SCENE_TEAMROCKETBASEB2F_LANCE_HEALS` = 0, `..._ROCKET_BOSS` = 1, `..._ELECTRODES` = 2, `..._NOOP` = 3 -- Callback: `MAPCALLBACK_TILES, TeamRocketBaseB2FTransmitterDoorCallback` - if `EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`, `changeblock 14, 12, $07 ; floor` - -Walkable regions (flood fill of `maps/TeamRocketBaseB2F.blk` against -`data/tilesets/facility_collision.asm`) - this floor is four disconnected rooms: - -| region | contents | -|---|---| -| top corridor | warp 2 `3,2`, warp 3 `27,2`, GruntM18 `2,1` | -| transmitter room | boss coord `14,11`/`15,11`, Electrodes `7,5`/`7,7`/`7,9` and `22,5`/`22,7`/`22,9`; sealed until the password door opens | -| TM room | warp 4 `3,6`, TM_THIEF item ball `3,10` | -| bottom corridor | warp 1 `3,14` (to B1F), warp 5 `27,14`, Lance heal coords, GruntM19 `21,14`, GruntM17 `25,13` | - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 14 | `TEAM_ROCKET_BASE_B1F` | 2 | -| 2 | 3 | 2 | `TEAM_ROCKET_BASE_B3F` | 1 | -| 3 | 27 | 2 | `TEAM_ROCKET_BASE_B3F` | 2 | -| 4 | 3 | 6 | `TEAM_ROCKET_BASE_B3F` | 3 | -| 5 | 27 | 14 | `TEAM_ROCKET_BASE_B3F` | 4 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `LANCE_HEALS` (0) | 5 | 14 | `LanceHealsScript1` | face UP, then `LanceHealsCommon` | -| `LANCE_HEALS` (0) | 4 | 13 | `LanceHealsScript2` | face RIGHT, then `LanceHealsCommon` | -| `ROCKET_BOSS` (1) | 14 | 11 | `RocketBaseBossFLeft` | Executive F ambush (Lance spawns at `9,13`) | -| `ROCKET_BOSS` (1) | 15 | 11 | `RocketBaseBossFRight` | same, actors nudged one cell right | -| `ELECTRODES` (2) | 14 | 12 | `RocketBaseCantLeaveScript` | shoves you back into the room | -| `ELECTRODES` (2) | 15 | 12 | `RocketBaseCantLeaveScript` | same | -| `ELECTRODES` (2) | 12 | 3 | `RocketBaseLancesSideScript` | "Leave this side to me", step LEFT | -| `ELECTRODES` (2) | 12 | 10 | `RocketBaseLancesSideScript` | same | -| `ELECTRODES` (2) | 12 | 11 | `RocketBaseLancesSideScript` | same | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 14 | 12 | `BGEVENT_IFNOTSET` | `conditional_event EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER, TeamRocketBaseB2FLockedDoor.Script` | -| 15 | 12 | `BGEVENT_IFNOTSET` | same | -| 12..17, 4 and 9; 12, 5..8; 17, 5..8 (20 cells) | | `BGEVENT_READ` | `TeamRocketBaseB2FTransmitterScript` | -| 26 | 7 | `BGEVENT_ITEM` | `TeamRocketBaseB2FHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_TEAM_ROCKET_BASE_B2F_HIDDEN_FULL_HEAL` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `TEAMROCKETBASEB2F_ROCKET1` | `SPRITE_ROCKET` | 20 | 16 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_GRUNT_WITH_EXECUTIVE` | -| `TEAMROCKETBASEB2F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 20 | 16 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_EXECUTIVE` | -| `TEAMROCKETBASEB2F_LANCE` | `SPRITE_LANCE` | 5 | 13 | `STANDING_DOWN` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_LANCE` | -| `TEAMROCKETBASEB2F_DRAGON` | `SPRITE_DRAGON` | 9 | 13 | `STANDING_RIGHT` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_DRAGONITE` | -| `TEAMROCKETBASEB2F_ELECTRODE1` | `SPRITE_VOLTORB` | 7 | 5 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode1` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_1` | -| `TEAMROCKETBASEB2F_ELECTRODE2` | `SPRITE_VOLTORB` | 7 | 7 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode2` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_2` | -| `TEAMROCKETBASEB2F_ELECTRODE3` | `SPRITE_VOLTORB` | 7 | 9 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode3` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_3` | -| `TEAMROCKETBASEB2F_ELECTRODE4` | `SPRITE_VOLTORB` | 22 | 5 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_1` | -| `TEAMROCKETBASEB2F_ELECTRODE5` | `SPRITE_VOLTORB` | 22 | 7 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_2` | -| `TEAMROCKETBASEB2F_ELECTRODE6` | `SPRITE_VOLTORB` | 22 | 9 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_3` | -| `TEAMROCKETBASEB2F_ROCKET2` | `SPRITE_ROCKET` | 25 | 13 | `STANDING_DOWN` | `TRAINER` | 3 | `TrainerGruntM17` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB2F_ROCKET3` | `SPRITE_ROCKET` | 2 | 1 | `STANDING_RIGHT` | `TRAINER` | 3 | `TrainerGruntM18` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB2F_ROCKET4` | `SPRITE_ROCKET` | 21 | 14 | `STANDING_LEFT` | `TRAINER` | 4 | `TrainerGruntM19` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB2F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 10 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB2FTMThief` | `EVENT_TEAM_ROCKET_BASE_B2F_TM_THIEF` | - -The Electrodes on the right (`22,5/7/9`) mirror the left three and share their flags; -`RocketElectrodeN` disappears both of a pair, so Lance's three "count" without ever -being battled. - -**Scripts of interest** - -- `LanceHealsCommon` (`45:4d4c`): `special FadeOutToWhite`, `playsound SFX_FULL_HEAL`, - `special HealParty`, `special FadeInFromWhite`, - `setscene SCENE_TEAMROCKETBASEB2F_ROCKET_BOSS`, - `setevent EVENT_LANCE_HEALED_YOU_IN_TEAM_ROCKET_BASE`, then `readvar VAR_FACING` - picks one of two exit movements and `disappear TEAMROCKETBASEB2F_LANCE`. - **This is a hard ordering dependency**: the boss ambush coord events only exist - on scene 1, so the transmitter room cannot be entered until Lance has healed you. -- `TeamRocketBaseB2FLockedDoor` (`45:4e8f`): `conditional_event` on - `EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`; `.Script` checks - `EVENT_LEARNED_HAIL_GIOVANNI`. If known: `playsound SFX_ENTER_DOOR`, - `changeblock 14, 12, $07`, `refreshmap`, `setevent EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`. -- `RocketBaseBossFScript` (`45:4c67`): cutscene, then - `loadtrainer EXECUTIVEF, EXECUTIVEF_2` + `startbattle`; - sets `EVENT_TEAM_ROCKET_BASE_B2F_EXECUTIVE`, `..._GRUNT_WITH_EXECUTIVE`, - `..._LANCE`, `EVENT_BEAT_ROCKET_EXECUTIVEF_2`; then - `setscene SCENE_TEAMROCKETBASEB2F_ELECTRODES`, `clearevent EVENT_TEAM_ROCKET_BASE_B2F_LANCE` - (re-showing Lance), the Electrode briefing, `disappear TEAMROCKETBASEB2F_LANCE`. -- `RocketElectrode1/2/3`: `cry ELECTRODE`, `loadwildmon ELECTRODE, 23`, `startbattle`. - `iftrue TeamRocketBaseB2FReloadMap` covers the "you lost/fled" path. After all three - events are set, the player is auto-walked out by - `RocketBasePlayerLeavesElectrodesMovement1/2/3` into `RocketBaseElectrodeScript`. - No `BATTLETYPE_*` is armed here, so these are catchable ordinary wild battles. -- `RocketBaseElectrodeScript` (`45:4e3c`) - the section's payoff: - `verbosegiveitem HM_WHIRLPOOL`, `setevent EVENT_GOT_HM06_WHIRLPOOL`, then - `setevent EVENT_CLEARED_ROCKET_HIDEOUT`, - `clearflag ENGINE_ROCKET_SIGNAL_ON_CH20`, - `setevent EVENT_ROUTE_43_GATE_ROCKETS`, - `setevent EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` (hides the fisher -> gym opens), - `setscene SCENE_TEAMROCKETBASEB2F_NOOP`, - `clearevent EVENT_LAKE_OF_RAGE_CIVILIANS`, - `setevent EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5`. - -**Items** (B2F) - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_THIEF` (TM46) | item ball at `3,10` (TM room) | `TeamRocketBaseB2FTMThief` | `EVENT_TEAM_ROCKET_BASE_B2F_TM_THIEF` | -| `FULL_HEAL` | hidden, face cell `26,7` | `bg_event 26, 7, BGEVENT_ITEM` | `EVENT_TEAM_ROCKET_BASE_B2F_HIDDEN_FULL_HEAL` | -| `HM_WHIRLPOOL` (HM06) | `verbosegiveitem` from Lance | `RocketBaseElectrodeScript` | `EVENT_GOT_HM06_WHIRLPOOL` | - -TM numbering check: `constants/item_constants.asm` `add_tm THIEF ; ee` is the 46th -`add_tm` (the `const ITEM_C3` / `const ITEM_DC` holes do not consume TM numbers), so -TM46 = Thief, matching the walkthrough. `add_tm ICY_WIND ; cf` is the 16th = TM16. - -**Trainers** (B2F) - -| const | class | id | party | script label | sight | -|---|---|---|---|---|---| -| `GRUNTM_17` | `GRUNTM` | 17 | GOLBAT 18 | `TrainerGruntM17` | 3 | -| `GRUNTM_18` | `GRUNTM` | 18 | RATTATA 17, ZUBAT 17, RATTATA 17 | `TrainerGruntM18` | 3 | -| `GRUNTM_19` | `GRUNTM` | 19 | VENONAT 18, VENONAT 18 | `TrainerGruntM19` | 4 | -| `EXECUTIVEF_2` | `EXECUTIVEF` (`$37`) | 2 | ARBOK 23 (WRAP/LEER/POISON_STING/BITE), GLOOM 23 (ABSORB/SWEET_SCENT/SLEEP_POWDER/ACID), MURKROW 25 (PECK/PURSUIT/HAZE/-) | `RocketBaseBossFScript` | cutscene | - -**Wild encounters**: none in the tables; only `loadwildmon ELECTRODE, 23` x3. - -### MAP_TEAM_ROCKET_BASE_B3F - -- Script: `maps/TeamRocketBaseB3F.asm` (`45:5c8c TeamRocketBaseB3F_MapScripts`) -- Blocks: `maps/TeamRocketBaseB3F.blk` -- Header: `data/maps/maps.asm:121` -> `TILESET_FACILITY, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const TEAM_ROCKET_BASE_B3F, 15, 9` (group 3, map id 43) = 30x18 cells -- Scene ids: `SCENE_TEAMROCKETBASEB3F_LANCE_GETS_PASSWORD` = 0, `..._RIVAL_ENCOUNTER` = 1, `..._ROCKET_BOSS` = 2, `..._NOOP` = 3 -- Callback: `MAPCALLBACK_TILES, TeamRocketBaseB3FCheckGiovanniDoorCallback` - if `EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`, `changeblock 10, 8, $07 ; floor` - -Walkable regions (same flood-fill method): - -| region | contents | -|---|---| -| left | warp 1 `3,2`, warp 3 `3,6`, rival coord `8,10` | -| Giovanni's office | boss coords `10,8`/`11,8`, ExecutiveM `8,3`, Murkrow `7,2`; sealed until both passwords | -| right / main | warp 2 `27,2`, warp 4 `27,14`, Lance `25,14`, GruntF5 `21,7`, GruntM28 `5,15`, Ross `25,12`, Mitch `14,15`, all four item balls | - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 2 | `TEAM_ROCKET_BASE_B2F` | 2 | -| 2 | 27 | 2 | `TEAM_ROCKET_BASE_B2F` | 3 | -| 3 | 3 | 6 | `TEAM_ROCKET_BASE_B2F` | 4 | -| 4 | 27 | 14 | `TEAM_ROCKET_BASE_B2F` | 5 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `ROCKET_BOSS` (2) | 10 | 8 | `RocketBaseBossLeft` | auto-walk `UP UP UP LEFT LEFT`, then `RocketBaseBoss` | -| `ROCKET_BOSS` (2) | 11 | 8 | `RocketBaseBossRight` | auto-walk `UP UP LEFT UP LEFT LEFT`, then `RocketBaseBoss` | -| `RIVAL_ENCOUNTER` (1) | 8 | 10 | `RocketBaseRival` | rival cutscene, no battle | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 10 | 9 | `BGEVENT_IFNOTSET` | `conditional_event EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE, TeamRocketBaseB3FLockedDoor.Script` | -| 11 | 9 | `BGEVENT_IFNOTSET` | same | -| 10..13, 1 | | `BGEVENT_READ` | `TeamRocketBaseB3FOathScript` (`jumpstd TeamRocketOathScript`) | -| 4..7, 13 | | `BGEVENT_READ` | `TeamRocketBaseB3FOathScript` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `TEAMROCKETBASEB3F_LANCE` | `SPRITE_LANCE` | 25 | 14 | `STANDING_DOWN` | `SCRIPT` | 0 | `LanceGetPasswordScript` | `EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS` | -| `TEAMROCKETBASEB3F_ROCKET1` | `SPRITE_ROCKET` | 8 | 3 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B3F_EXECUTIVE` | -| `TEAMROCKETBASEB3F_MOLTRES` | `SPRITE_MOLTRES` | 7 | 2 | `POKEMON` | `SCRIPT` | 0 | `RocketBaseMurkrow` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 21 | 7 | `STANDING_UP` | `TRAINER` | **0** | `SlowpokeTailGrunt` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_ROCKET2` | `SPRITE_ROCKET` | 5 | 15 | `SPINRANDOM_FAST` | `TRAINER` | 3 | `RaticateTailGrunt` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_SCIENTIST1` | `SPRITE_SCIENTIST` | 25 | 12 | `STANDING_LEFT` | `TRAINER` | 4 | `TrainerScientistRoss` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_SCIENTIST2` | `SPRITE_SCIENTIST` | 14 | 15 | `STANDING_UP` | `TRAINER` | 3 | `TrainerScientistMitch` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_ROCKET3` | `SPRITE_ROCKET` | 24 | 14 | `STANDING_DOWN` | `SCRIPT` | 0 | `TeamRocketBaseB3FRocketScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` | -| `TEAMROCKETBASEB3F_RIVAL` | `SPRITE_RIVAL` | 4 | 5 | `STANDING_DOWN` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_RIVAL_TEAM_ROCKET_BASE` | -| `TEAMROCKETBASEB3F_POKE_BALL1` | `SPRITE_POKE_BALL` | 1 | 12 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FFullHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_FULL_HEAL` | -| `TEAMROCKETBASEB3F_POKE_BALL2` | `SPRITE_POKE_BALL` | 3 | 12 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FDireHit` | `EVENT_TEAM_ROCKET_BASE_B3F_DIRE_HIT` | -| `TEAMROCKETBASEB3F_POKE_BALL3` | `SPRITE_POKE_BALL` | 28 | 9 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FProtein` | `EVENT_TEAM_ROCKET_BASE_B3F_PROTEIN` | -| `TEAMROCKETBASEB3F_POKE_BALL4` | `SPRITE_POKE_BALL` | 17 | 2 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FIceHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_ICE_HEAL` | - -The Murkrow the walkthrough talks to is the object using `SPRITE_MOLTRES` at `7,2`; -its script is `RocketBaseMurkrow` and its text says MURKROW. The sprite constant is -the generic large-bird overworld sprite, not a Moltres encounter. - -**Scripts of interest** - -- `LanceGetPasswordScript` (`45:5cb4`): both the `sdefer` scene script for scene 0 - and the Lance object's own script. Runs on entry to the right region, prints - `LanceGetPasswordText`, `disappear TEAMROCKETBASEB3F_LANCE` (which sets - `EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS`), `setscene SCENE_TEAMROCKETBASEB3F_RIVAL_ENCOUNTER`. -- `SlowpokeTailGrunt` -> `trainer GRUNTF, GRUNTF_5, EVENT_BEAT_ROCKET_GRUNTF_5, ...`; - `GruntF5Script` (the after-battle arm) does `setevent EVENT_LEARNED_SLOWPOKETAIL`. - **Sight range 0** - she never initiates; the bot must walk up and talk, then talk - a second time for the password. -- `RaticateTailGrunt` -> `trainer GRUNTM, GRUNTM_28, EVENT_BEAT_ROCKET_GRUNTM_28, ...`; - `GruntM28Script` does `setevent EVENT_LEARNED_RATICATE_TAIL`. Sight 3, - `SPINRANDOM_FAST` (the "spinning Team Rocket member"). -- `TeamRocketBaseB3FLockedDoor` (`45:5da9`): `.Script` requires - `EVENT_LEARNED_SLOWPOKETAIL` **and** `EVENT_LEARNED_RATICATE_TAIL`; on success - `changeblock 10, 8, $07`, `refreshmap`, `setevent EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`. -- `RocketBaseRival` (`45:5cd1`): `appear TEAMROCKETBASEB3F_RIVAL`, walk-in, dialogue, - `applymovement PLAYER, RocketBaseRivalShovesPlayerMovement` (a `big_step RIGHT` - with `fix_facing`), `disappear`, `setscene SCENE_TEAMROCKETBASEB3F_ROCKET_BOSS`. - No battle - matches the walkthrough ("He can't battle you though"). -- `RocketBaseBoss` (`45:5d0e`): `loadtrainer EXECUTIVEM, EXECUTIVEM_4`, `startbattle`, - `setevent EVENT_BEAT_ROCKET_EXECUTIVEM_4`, long exit movement, - `disappear TEAMROCKETBASEB3F_ROCKET1`, `setscene SCENE_TEAMROCKETBASEB3F_NOOP`. -- `RocketBaseMurkrow` (`45:5d49`): text, then `setevent EVENT_LEARNED_HAIL_GIOVANNI`. - -**Items** (B3F) - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `FULL_HEAL` | item ball `1,12` | `TeamRocketBaseB3FFullHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_FULL_HEAL` | -| `DIRE_HIT` | item ball `3,12` | `TeamRocketBaseB3FDireHit` | `EVENT_TEAM_ROCKET_BASE_B3F_DIRE_HIT` | -| `PROTEIN` | item ball `28,9` | `TeamRocketBaseB3FProtein` | `EVENT_TEAM_ROCKET_BASE_B3F_PROTEIN` | -| `ICE_HEAL` | item ball `17,2` | `TeamRocketBaseB3FIceHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_ICE_HEAL` | - -**Trainers** (B3F) - -| const | class | id | party | script label | sight | -|---|---|---|---|---|---| -| `SCIENTIST`, `ROSS` | `SCIENTIST` (`$0e`) | 1 | KOFFING 22, KOFFING 22 | `TrainerScientistRoss` | 4 | -| `SCIENTIST`, `MITCH` | `SCIENTIST` | 2 | DITTO 24 | `TrainerScientistMitch` | 3 | -| `GRUNTF`, `GRUNTF_5` | `GRUNTF` (`$42`) | 5 | EKANS 18 (WRAP/LEER/POISON_STING/BITE), GLOOM 18 (ABSORB/SWEET_SCENT/STUN_SPORE/SLEEP_POWDER) | `SlowpokeTailGrunt` | 0 | -| `GRUNTM`, `GRUNTM_28` | `GRUNTM` (`$1f`) | 28 | RATICATE 19 | `RaticateTailGrunt` | 3 | -| `EXECUTIVEM`, `EXECUTIVEM_4` | `EXECUTIVEM` (`$33`) | 4 | ZUBAT 22, RATICATE 24, KOFFING 22 | `RocketBaseBoss` | cutscene | - -**Wild encounters**: none. - -### MAP_MAHOGANY_GYM - -- Script: `maps/MahoganyGym.asm` (`51:536c MahoganyGym_MapScripts`) -- Blocks: `maps/MahoganyGym.blk` -- Header: `data/maps/maps.asm:69` -> `map MahoganyGym, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `map_const MAHOGANY_GYM, 5, 9` (group 2, map id 2) = 10x18 cells -- `def_scene_scripts` and `def_callbacks` are both **empty** - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `MAHOGANY_TOWN` | 3 | -| 2 | 5 | 17 | `MAHOGANY_TOWN` | 3 | - -`def_coord_events` is empty. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `MahoganyGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `MahoganyGymStatue` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `MAHOGANYGYM_PRYCE` | `SPRITE_PRYCE` | 5 | 3 | `STANDING_DOWN` | `SCRIPT` | 0 | `MahoganyGymPryceScript` | -1 | -| `MAHOGANYGYM_BEAUTY1` | `SPRITE_BEAUTY` | 4 | 6 | `STANDING_DOWN` | `TRAINER` | 1 | `TrainerSkierRoxanne` | -1 | -| `MAHOGANYGYM_ROCKER1` | `SPRITE_ROCKER` | 0 | 17 | `STANDING_UP` | `TRAINER` | 1 | `TrainerBoarderRonald` | -1 | -| `MAHOGANYGYM_BEAUTY2` | `SPRITE_BEAUTY` | 9 | 17 | `STANDING_UP` | `TRAINER` | 1 | `TrainerSkierClarissa` | -1 | -| `MAHOGANYGYM_ROCKER2` | `SPRITE_ROCKER` | 5 | 9 | `STANDING_DOWN` | `TRAINER` | 1 | `TrainerBoarderBrad` | -1 | -| `MAHOGANYGYM_ROCKER3` | `SPRITE_ROCKER` | 2 | 4 | `SPINRANDOM_FAST` | `TRAINER` | 1 | `TrainerBoarderDouglas` | -1 | -| `MAHOGANYGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 15 | `STANDING_DOWN` | `SCRIPT` | 0 | `MahoganyGymGuideScript` | -1 | - -**Ice-floor map**, decoded from `maps/MahoganyGym.blk` against -`data/tilesets/elite_four_room_collision.asm`. `I` = `COLL_ICE` (`$23`), -`.` = walkable floor, `#` = wall, `v` = warp carpet: - -``` - x0123456789 - y0 ########## - y1 ########## - y2 .IIIII.I#I - y3 IIIIIIII#I - y4 I#I.I.IIII - y5 I#IIII.III - y6 I#IIIIIIII - y7 I#II.IIII. - y8 .IIIIIII#I - y9 IIIIIIII#I -y10 I#.II.III. -y11 I#IIIII.II -y12 I#IIIIII#I -y13 I#I.II.I#I -y14 I#.#..#.#I -y15 I#.#..#.#I -y16 .I......I. -y17 II..vv..II -``` - -Sliding rule (`engine/overworld/player_movement.asm`): `CheckIceTile` on the tile -you land on -> `STEP_ICE`; `.CheckForced` / `CheckStandingOnIce` re-injects the same -d-pad direction, so a single press slides until you land on a non-ice tile, hit a -wall, or hit an NPC. - -Simulating that rule against the grid above (NPCs treated as blockers) reproduces -the walkthrough's directions exactly: - -| from | press | lands on | who | -|---|---|---|---| -| `4,17` (warp) | `UP` | `4,16` | - | -| `4,16` | `LEFT`, `LEFT`, `LEFT` | `3,16` -> `2,16` -> `0,16` | faces Boarder Ronald `0,17` | -| `0,16` | `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT` | `0,8` -> `0,2` -> `6,2` -> `6,5` -> `2,5` | faces Boarder Douglas `2,4` | -| `2,5` | `DOWN`,`RIGHT` | `2,10` -> `5,10` | faces Boarder Brad `5,9` | -| `5,10` | `RIGHT`,`DOWN` | `9,10` -> `9,16` | faces Skier Clarissa `9,17` | -| `9,16` | `UP`,`UP`,`LEFT` | `9,10` -> `9,7` -> `4,7` | faces Skier Roxanne `4,6` | -| `4,7` | `DOWN` | `4,14` | back toward the exit | -| `2,14` ("just left of the left pillar", pillar block at `3,14`/`3,15`) | `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT`,`UP`,`RIGHT` | `2,10` -> `2,5` -> `6,5` -> `6,13` -> `3,13` -> `3,4` -> `5,4` | standing directly below Pryce `5,3` | - -Reaching `2,14` from the entrance: `UP` (to `4,16`), `LEFT` (`3,16`), `LEFT` -(`2,16`), `UP` (`2,15`), `UP` (`2,14`) - `2,15`/`2,16`/`2,14` are ordinary floor, -not ice, so those are single steps. - -**Scripts of interest** - -`MahoganyGymPryceScript` (`51:536e`): - -1. `checkevent EVENT_BEAT_PRYCE` -> `.FightDone` -2. `writetext PryceText_Intro`; `winlosstext PryceText_Impressed, 0`; - `loadtrainer PRYCE, PRYCE1`; `startbattle`; `reloadmapafterbattle` -3. `setevent EVENT_BEAT_PRYCE`; `Text_ReceivedGlacierBadge`; - `playsound SFX_GET_BADGE`; `setflag ENGINE_GLACIERBADGE` -4. `readvar VAR_BADGES`; `scall MahoganyGymActivateRockets` - - `ifequal 7, .RadioTowerRockets` / `ifequal 6, .GoldenrodRockets`. - Badge count is read **after** GLACIERBADGE is set, so the normal route - (Glacier as the 7th badge) takes `jumpstd RadioTowerRocketsScript` - (`engine/events/std_scripts.asm:255`), which sets - `ENGINE_ROCKETS_IN_RADIO_TOWER`, `EVENT_GOLDENROD_CITY_CIVILIANS`, - `EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS`, - `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST`, - `specialphonecall SPECIALCALL_WEIRDBROADCAST`, and - `setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP`. - Skipping Jasmine first (Glacier as the 6th) takes - `GoldenrodRocketsScript` instead, which only clears - `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER`. -5. `.FightDone`: if `EVENT_GOT_TM16_ICY_WIND` is set -> farewell text. Otherwise - `setevent EVENT_BEAT_SKIER_ROXANNE / _CLARISSA / _BOARDER_RONALD / _BRAD / _DOUGLAS` - (so the five gym trainers never battle again), `PryceText_GlacierBadgeSpeech`, - `verbosegiveitem TM_ICY_WIND`, `setevent EVENT_GOT_TM16_ICY_WIND`. - -`MahoganyGymStatue`: `checkflag ENGINE_GLACIERBADGE`; before the badge -`jumpstd GymStatue1Script`, after it `gettrainername STRING_BUFFER_4, PRYCE, PRYCE1` -+ `jumpstd GymStatue2Script`. - -**Flags and events** (gym) - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_PRYCE` | `constants/event_flags.asm:712` | `MahoganyGymPryceScript` | leader beaten | -| `EVENT_GOT_TM16_ICY_WIND` | `event_flags.asm:20` | `MahoganyGymPryceScript` | TM handed over | -| `ENGINE_GLACIERBADGE` | `constants/engine_flags.asm:44` | set here; read by `WhirlpoolFunction` and `MahoganyGymStatue` | badge 7 | -| `EVENT_BEAT_SKIER_ROXANNE`, `_CLARISSA`, `EVENT_BEAT_BOARDER_RONALD`, `_BRAD`, `_DOUGLAS` | `constants/event_flags.asm` | the five `trainer` macros, force-set by Pryce | gym trainer beaten | - -**Trainers** (gym) - -| const | class | id | party (`data/trainers/parties.asm`) | script label | -|---|---|---|---|---| -| `BOARDER`, `RONALD` | `BOARDER` (`$3a`) | 1 | SEEL 24, DEWGONG 25, SEEL 24 | `TrainerBoarderRonald` | -| `BOARDER`, `BRAD` | `BOARDER` | 2 | SWINUB 26, SWINUB 26 | `TrainerBoarderBrad` | -| `BOARDER`, `DOUGLAS` | `BOARDER` | 3 | SHELLDER 24, CLOYSTER 25, SHELLDER 24 | `TrainerBoarderDouglas` | -| `SKIER`, `ROXANNE` | `SKIER` (`$21`) | 1 | JYNX 28 | `TrainerSkierRoxanne` | -| `SKIER`, `CLARISSA` | `SKIER` | 2 | DEWGONG 28 | `TrainerSkierClarissa` | -| `PRYCE`, `PRYCE1` | `PRYCE` (`$05`) | 1 | SEEL 27 (HEADBUTT/ICY_WIND/AURORA_BEAM/REST), DEWGONG 29 (HEADBUTT/ICY_WIND/AURORA_BEAM/REST), PILOSWINE 31 (ICY_WIND/FURY_ATTACK/MIST/BLIZZARD) | `MahoganyGymPryceScript` | - -**Wild encounters**: none. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Hideout entrance does not exist | `maps/MahoganyMart1F.asm:MahoganyMart1FStaircaseCallback` + `MahoganyMart1FLanceUncoversStaircaseScript` (`45:4046`) | `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` for the block; the cutscene needs `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` | `maps/LakeOfRage.asm:LakeOfRageLanceScript.AgreedToHelp` (previous section) | -| Camera grunt ambushes | `maps/TeamRocketBaseB1F.asm:SecurityCamera1a..5` | `EVENT_SECURITY_CAMERA_n` clear | beat both grunts at that camera, or press the switch at `19,11` (`TeamRocketBaseB1FSecretSwitch`, `45:4757`) | -| Exploding floor traps | `maps/TeamRocketBaseB1F.asm:ExplodingTrap1..22` | `EVENT_EXPLODING_TRAP_n` clear | step on the tile once (or route around; safe cells listed above) | -| B2F transmitter room sealed (block) | `maps/TeamRocketBaseB2F.asm:TeamRocketBaseB2FLockedDoor` (`45:4e8f`) + `TeamRocketBaseB2FTransmitterDoorCallback` | `EVENT_LEARNED_HAIL_GIOVANNI` | talk to the Murkrow object at B3F `7,2` (`RocketBaseMurkrow`, `45:5d49`) after beating ExecutiveM 4 | -| B2F boss ambush will not fire | `maps/TeamRocketBaseB2F.asm` coord events on `SCENE_TEAMROCKETBASEB2F_ROCKET_BOSS` | map scene must be 1 | trip `LanceHealsScript1/2` at `5,14` or `4,13` first | -| B3F Giovanni's office sealed | `maps/TeamRocketBaseB3F.asm:TeamRocketBaseB3FLockedDoor` (`45:5da9`) + `TeamRocketBaseB3FCheckGiovanniDoorCallback` | `EVENT_LEARNED_SLOWPOKETAIL` **and** `EVENT_LEARNED_RATICATE_TAIL` | beat `GRUNTF_5` at `21,7` then talk again; beat `GRUNTM_28` at `5,15` then talk again | -| Cannot leave the Electrode room | `maps/TeamRocketBaseB2F.asm:RocketBaseCantLeaveScript` / `RocketBaseLancesSideScript` | scene `SCENE_TEAMROCKETBASEB2F_ELECTRODES` | faint all three left-hand Electrodes | -| Mahogany Gym door blocked | `maps/MahoganyTown.asm` object `SPRITE_FISHER` at `6,14` gated on `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM`; visibility rule in `engine/overworld/map_objects_2.asm:CheckObjectFlag` | flag set = NPC hidden | `RocketBaseElectrodeScript` (`45:4e3c`) sets it when the hideout is cleared | -| Whirlpool unusable in the field | `engine/events/overworld.asm:WhirlpoolFunction.TryWhirlpool` (`03:4da0`, `ld de, ENGINE_GLACIERBADGE / call CheckBadge`) and `TryWhirlpoolOW` (`03:4e41`, `CheckPartyMove WHIRLPOOL` then `CheckEngineFlag ENGINE_GLACIERBADGE`) | GLACIERBADGE + a party member that knows WHIRLPOOL | beat Pryce | -| Rage Candy Bar merchant trip-wire | `maps/MahoganyTown.asm` coord events `19,8`/`19,9` on scene 0 | scene `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` | only cleared later, by `RadioTowerRocketsScript` `setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP` | - ---- - -## 4. Bot checklist - -Coordinates are `(x, y)` map cells as written in the asm. "talk" = face the target -and press A. - -1. `MAHOGANY_TOWN`: warp 4 at `(15,13)` -> `MAHOGANY_POKECENTER_1F`; talk to nurse at `(3,1)`. Pre: none. Post: party healed. -2. `MAHOGANY_TOWN`: warp 1 at `(11,7)` -> `MAHOGANY_MART_1F`. Pre: `EVENT_DECIDED_TO_HELP_LANCE` set and map scene = 1. Post: `MahoganyMart1FLanceUncoversStaircaseScript` runs unattended; wait for `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART`. -3. `MAHOGANY_MART_1F`: walk to `(7,3)` -> `TEAM_ROCKET_BASE_B1F` warp 1, arriving at `(27,2)`. -4. B1F: walk west along y=2. Crossing `(24,2)` or `(24,3)` fires `SecurityCamera1a/1b` -> battle `GRUNTM_20` then `GRUNTM_21`. Post: `EVENT_SECURITY_CAMERA_1`. -5. B1F: item ball at `(27,6)` = HYPER_POTION. Post: `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION`. -6. B1F: continue west; `(6,2)`/`(6,3)` fires camera 2, `(24,6)`/`(24,7)` camera 3, `(22,16)` camera 4, `(8,16)` camera 5. Each is the same two grunts. -7. B1F: talk to `GRUNTM_16` at `(2,4)` (sight 3, faces RIGHT - approaching from the east triggers him). Post: `EVENT_BEAT_ROCKET_GRUNTM_16`. -8. B1F: cross the trap field x1..5 / y7..13 using only the safe cells listed in section 2, or accept the one-shot `BATTLETYPE_TRAP` battles. Optional: hidden REVIVE by facing `(3,11)`. -9. B1F: battle `SCIENTIST JED` at `(18,12)` (sight 3, faces LEFT). Post: `EVENT_BEAT_SCIENTIST_JED`. Talk again for the warp-panel hint. -10. B1F: stand at `(19,12)`, face UP, press A on the bg event at `(19,11)`. Post: `EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5`. -11. B1F: item balls `(21,12)` = X_ACCURACY, `(14,15)` = NUGGET. -12. B1F: walk to `(3,14)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(3,14)`. -13. B2F: step on `(5,14)` or `(4,13)`. Pre: map scene 0. Post: party healed, `EVENT_LANCE_HEALED_YOU_IN_TEAM_ROCKET_BASE`, scene -> 1. -14. B2F: battle `GRUNTM_19` at `(21,14)` (sight 4, faces LEFT), then `GRUNTM_17` at `(25,13)` (sight 3, faces DOWN). -15. B2F: walk to `(27,14)` -> `TEAM_ROCKET_BASE_B3F` arriving at `(27,14)`. Pre: none. Post: `LanceGetPasswordScript` auto-runs, scene -> 1, `EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS`. -16. B3F: item balls `(28,9)` = PROTEIN, `(17,2)` = ICE_HEAL. -17. B3F: battle `SCIENTIST ROSS` at `(25,12)` (sight 4, faces LEFT). -18. B3F: walk to `(21,8)` and talk UP to `GRUNTF_5` at `(21,7)` - **sight 0, she will not initiate**. Post: `EVENT_BEAT_ROCKET_GRUNTF_5`; talk again -> `EVENT_LEARNED_SLOWPOKETAIL`. -19. B3F: battle `SCIENTIST MITCH` at `(14,15)` (sight 3, faces UP). -20. B3F: battle `GRUNTM_28` at `(5,15)` (sight 3, spinning). Post: `EVENT_BEAT_ROCKET_GRUNTM_28`; talk again -> `EVENT_LEARNED_RATICATE_TAIL`. -21. B3F: item balls `(3,12)` = DIRE_HIT, `(1,12)` = FULL_HEAL. -22. B3F: walk to `(27,2)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(27,2)`. -23. B2F top corridor: walk west along y=1..2; battle `GRUNTM_18` at `(2,1)` (sight 3, faces RIGHT). -24. B2F: walk to `(3,2)` -> `TEAM_ROCKET_BASE_B3F` arriving at `(3,2)`. -25. B3F left region: walk to `(8,10)`. Pre: scene 1. Post: rival cutscene, no battle, scene -> 2, `EVENT_RIVAL_TEAM_ROCKET_BASE` set by `disappear`. -26. B3F: stand at `(10,10)` or `(11,10)`, face UP, press A on the bg event at `(10,9)`/`(11,9)`. Pre: both password events. Post: `EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`, block at `(10,8)` becomes floor. -27. B3F: step onto `(10,8)` or `(11,8)`. Pre: scene 2. Post: auto-walk, battle `EXECUTIVEM_4`, `EVENT_BEAT_ROCKET_EXECUTIVEM_4`, scene -> 3. -28. B3F: talk to the bird object at `(7,2)`. Post: `EVENT_LEARNED_HAIL_GIOVANNI`. -29. B3F: walk to `(3,6)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(3,6)`; item ball `(3,10)` = TM_THIEF (TM46). Return via `(3,6)`. -30. Back-track: B3F `(3,2)` -> B2F `(3,2)`; B2F top corridor east to `(27,2)` -> B3F `(27,2)`; B3F right region south to `(27,14)` -> B2F `(27,14)`; walk west to the door. -31. B2F: stand at `(14,13)`/`(15,13)`, face UP, press A on `(14,12)`/`(15,12)`. Pre: `EVENT_LEARNED_HAIL_GIOVANNI`. Post: `EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`, block becomes floor. -32. B2F: step onto `(14,11)` or `(15,11)`. Pre: scene 1. Post: `EXECUTIVEF_2` battle, `EVENT_BEAT_ROCKET_EXECUTIVEF_2`, scene -> 2, Lance briefing. -33. B2F: talk to the three left Electrodes at `(7,5)`, `(7,7)`, `(7,9)`. Each is a wild ELECTRODE L23 that may Selfdestruct. Do **not** walk east past x=12 (`RocketBaseLancesSideScript` bounces you back). Post: three `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_n`. -34. After the third, `RocketBaseElectrodeScript` auto-runs: accept `HM_WHIRLPOOL`. Post: `EVENT_GOT_HM06_WHIRLPOOL`, `EVENT_CLEARED_ROCKET_HIDEOUT`, `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM`, `clearflag ENGINE_ROCKET_SIGNAL_ON_CH20`, scene -> 3. -35. Exit: B2F `(3,14)` -> B1F `(3,14)`; B1F warp panel `(5,15)` -> `(25,2)`; `(27,2)` -> mart; mart `(3,7)`/`(4,7)` -> town. -36. `MAHOGANY_TOWN`: heal at the Pokecenter, then warp 3 at `(6,13)` -> `MAHOGANY_GYM`, arriving at `(4,17)`/`(5,17)`. Pre: `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` set (otherwise the fisher stands on `(6,14)`). -37. Gym: `UP`, `LEFT`, `LEFT`, `LEFT` -> `(0,16)`, Boarder Ronald spots you from `(0,17)`. -38. Gym: `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT` -> `(2,5)`, Boarder Douglas at `(2,4)`. Talk again for the Pryce-waterfall line. -39. Gym: `DOWN`,`RIGHT` -> `(5,10)`, Boarder Brad at `(5,9)`. -40. Gym: `RIGHT`,`DOWN` -> `(9,16)`, Skier Clarissa at `(9,17)`. -41. Gym: `UP`,`UP`,`LEFT` -> `(4,7)`, Skier Roxanne at `(4,6)`. -42. Gym: `DOWN` -> `(4,14)`, then walk to `(2,14)`. -43. Gym: from `(2,14)`: `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT`,`UP`,`RIGHT` -> `(5,4)`. Talk UP to Pryce at `(5,3)`. -44. Battle `PRYCE1`. Post: `EVENT_BEAT_PRYCE`, `ENGINE_GLACIERBADGE`, the five gym-trainer beat flags, `EVENT_GOT_TM16_ICY_WIND`, and (at 7 badges) `RadioTowerRocketsScript`. -45. Gym: leave via `(4,17)`/`(5,17)`. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers / warps / coord events / bg events / objects for all six maps | `src/import/RomExtractorGen2.lua`, `src/world/gen2/Map.lua` | implemented (generic extraction, nothing map-specific needed) | -| Map scenes + `sdefer` scene scripts | `src/world/gen2/World.lua` (`World:scene`, `mapScenes`), `src/script/gen2/Vm.lua` (`sdefer`, `setscene`, `setmapscene`) | implemented | -| Coord-event trip-wires | `src/world/gen2/World.lua` (coord event dispatch) | implemented | -| `changeblock` (mart staircase, both password doors) | `src/script/gen2/Vm.lua:1006` - the comment cites `MahoganyMart1F`'s `changeblock 6, 2, $1e` and cell `(7,3)` by name | implemented | -| `BGEVENT_IFNOTSET` / `conditional_event` (both password doors) | `src/world/gen2/World.lua:5147 World:bgEventAt` only matches `(ev.kind or 0) == 0`; `src/world/gen2/HiddenItems.lua` handles kind 7 | **missing** - pressing A on the transmitter door or Giovanni's door does nothing in the port | -| Ice sliding (`STEP_ICE`, `CheckStandingOnIce` forced d-pad) | `src/world/gen2/Permissions.lua:128 ICE` exists but is only consumed by `FieldMoves.canEncounterWildMon`; `World:movePlayer` picks step frames from `Bike.stepFrames` only | **missing** - Mahogany Gym floor behaves as ordinary floor, so the puzzle and the trainer sight lines are wrong | -| Trainer sight range / approach | `src/world/gen2/Trainers.lua` (`Trainers.sees`, `Trainers.approach`) | implemented (`sight 0` on GruntF5 falls out naturally) | -| `itemball` / `hiddenitem` | `src/script/gen2/CallAsm.lua`, `src/world/gen2/HiddenItems.lua` | implemented | -| `verbosegiveitem HM_WHIRLPOOL` | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented | -| Whirlpool field move + GLACIERBADGE gate | `src/world/gen2/FieldMoves.lua:109` (`WHIRLPOOL = "GLACIER"`), `.somethingToWhirlpool`, `.whirlpoolFromMenu`, `.tryWhirlpoolOW`, `WHIRLPOOL_BLOCKS` | implemented | -| `loadwildmon` + `BATTLETYPE_TRAP` | `src/world/gen2/World.lua:4587` (names "TRAP 9 (the Rocket base)"), `src/script/gen2/Vm.lua:699` | implemented | -| `special HealParty` / `FadeOutToWhite` / `FadeInFromWhite` / `PlayMapMusic` / `ReloadSpritesNoPalettes` / `FadeOutToBlack` / `FadeInFromBlack` / `RestartMapMusic` / `FadeOutMusic` | `src/script/gen2/Specials.lua:450, 992-1066` | implemented | -| `moveobject` / `appear` / `disappear` / `applymovement` / `follow` / `showemote` | `src/script/gen2/Vm.lua`, `src/script/gen2/Movement.lua`, `src/world/gen2/World.lua` | implemented | -| `jumpstd` (`TeamRocketOathScript`, `GymStatue1/2Script`, `PokecenterSignScript`, `RadioTowerRocketsScript`) | `src/script/gen2/Vm.lua:742`, `data/generated/std_scripts.lua` via `World.lua:635` | implemented (generic) | -| `readvar VAR_BADGES` for `MahoganyGymActivateRockets` | `src/world/gen2/World.lua:117,1240` | implemented | -| `specialphonecall SPECIALCALL_WEIRDBROADCAST` | `src/script/gen2/Vm.lua:1364` stores the id; `src/core/gen2/Phone.lua` delivers | implemented | -| `ENGINE_ROCKET_SIGNAL_ON_CH20` radio behaviour | `src/ui/gen2/Pokegear.lua:816-821, 1097` | implemented | -| Driver coverage for this stretch | `tests/drivers/gold_*.lua` - none touches Mahogany, the hideout or Pryce (`gold_icepath_boulder.lua` is the only Mahogany-adjacent one, and it is Ice Path boulders) | **missing** | - -The two "missing" rows are the blocking ones: without `BGEVENT_IFNOTSET` the -hideout cannot be completed, and without ice sliding the gym cannot be crossed. - ---- - -## 6. Unresolved / verify by hand - -- **Floor labels in the walkthrough are wrong past the first password.** It says - "go up the stairs at the upper-right part of the floor, then head left on Negative - Floor 2 ... Head up the stairs to Negative Floor 1. Head right and then before you - reach the locked door, your rival will come." The rival coord event - (`8,10`) and Giovanni's office are both on **B3F**, not B1F. The route is - B3F(right) -> B2F(top) -> B3F(left). Verified by flood-filling - `maps/TeamRocketBaseB2F.blk` / `maps/TeamRocketBaseB3F.blk`: the four B2F warps - and four B3F warps sit in disconnected rooms, and B2F warp 2 `(3,2)` reaches - B3F `(3,2)` which is the region containing `8,10`. -- **Boarder Ronald's party order.** Walkthrough lists Seel 24 / Seel 24 / Dewgong 25; - `data/trainers/parties.asm` `BOARDER (1)` is SEEL 24, DEWGONG 25, SEEL 24. - Same three mon, different lead order. Same class of discrepancy for - `GRUNTM_18` (walkthrough Rattata/Rattata/Zubat, asm Rattata/Zubat/Rattata). -- **Item list is incomplete.** The walkthrough's "Items in the Team Rocket Hideout" - block lists six; the asm has ten: the six named plus `FULL_HEAL` and `DIRE_HIT` - item balls on B3F (mentioned later in the prose) and two hidden items the - walkthrough never mentions - `REVIVE` at B1F `(3,11)` and `FULL_HEAL` at B2F - `(26,7)`. -- **"Every time you pass one of those statues, two Team Rocket grunts will fight - you"** is right, but the walkthrough implies only two statues on the first floor. - There are five (`bg_event` at `24,1`, `6,1`, `24,5`, `8,15`, `22,15`) with eight - trigger cells, i.e. up to ten grunt battles if none are skipped. -- **`EVENT_TEAM_ROCKET_BASE_POPULATION` is dead.** It is the shared visibility flag - of eleven base NPCs and is read by all eight `SecurityCamera*` scripts, but a - full-tree grep finds no `setevent`, `clearevent` or `disappear` that writes it. - Those `checkevent ... iftrue NoSecurityCamera` arms therefore never take, and the - Rocket NPCs stay on the maps forever - including after the hideout is cleared. - Flagged in case a bot author expects the base to empty out. -- **Lance's three right-hand Electrodes are never battled.** `RocketElectrodeN` - disappears both members of a mirrored pair, and the objects at `22,5`/`22,7`/`22,9` - point at the no-op `ObjectEvent` script. The walkthrough's "Lance runs to the right - to take the Electrodes on the right" is flavour; the game just hides them. -- **Exact reachability of `19,11` (the secret switch)** was checked by collision - (`19,11` is a WALL, `19,12` is FLOOR and in the single B1F region), but the - in-game facing direction for the `BGEVENT_READ` was not observed running - the - bg event type is `BGEVENT_READ`, not `BGEVENT_UP`, so any adjacent facing should - work; worth confirming on hardware if a driver misses it. -- The gym slide table in section 2 was produced by simulating - `engine/overworld/player_movement.asm`'s ice rule against the decoded - `maps/MahoganyGym.blk`, treating NPCs as blockers. It reproduces every direction - string in the walkthrough, but it has not been executed in an emulator. diff --git a/docs/gold-walkthrough/asm-walk/section-12-team-rocket-radio-tower.md b/docs/gold-walkthrough/asm-walk/section-12-team-rocket-radio-tower.md deleted file mode 100644 index 6af7b91f..00000000 --- a/docs/gold-walkthrough/asm-walk/section-12-team-rocket-radio-tower.md +++ /dev/null @@ -1,1050 +0,0 @@ -# Section 12 - Team Rocket Radio Tower - -Source: `../section-12-team-rocket-radio-tower.txt` (the FAQ's own heading is -"18 > Team Rocket Radio Tower") -Maps covered: `MAP_GOLDENROD_CITY`, `MAP_RADIO_TOWER_1F`, `MAP_RADIO_TOWER_2F`, -`MAP_RADIO_TOWER_3F`, `MAP_RADIO_TOWER_4F`, `MAP_RADIO_TOWER_5F`, -`MAP_GOLDENROD_UNDERGROUND`, `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`, -`MAP_GOLDENROD_UNDERGROUND_WAREHOUSE`, `MAP_GOLDENROD_DEPT_STORE_B1F` - -Badges / key milestones in this section: - -- No badge. The section is bracketed by two key items and one story flag: - `BASEMENT_KEY` (from the fake director, `RADIO_TOWER_5F`), `CARD_KEY` (from the - real director, `GOLDENROD_UNDERGROUND_WAREHOUSE`), and - `EVENT_CLEARED_RADIO_TOWER` + `RAINBOW_WING` (Gold) / `SILVER_WING` (Silver) - from `RadioTower5FRocketBossScript`. -- The rival's fourth battle (`RIVAL1_4_*`) happens in the middle of it. -- Entry condition is the 7th badge, not any item: see section 3. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_GOLDENROD_CITY` | `maps/GoldenrodCity.asm` | Fly (`ENGINE_FLYPOINT_GOLDENROD`, set by `GoldenrodCityFlypointCallback`) | warp 12 at (5,15) | Elm's phone call; city is full of Rockets | -| 2 | `MAP_RADIO_TOWER_1F` | `maps/RadioTower1F.asm` | warps 1/2 at (2,7)/(3,7) | warp 3 at (15,0) | first Grunt (`TrainerGruntM3`) | -| 3 | `MAP_RADIO_TOWER_2F` | `maps/RadioTower2F.asm` | warp 2 at (15,0) | warp 1 at (0,0) | four Grunts, head left | -| 4 | `MAP_RADIO_TOWER_3F` | `maps/RadioTower3F.asm` | warp 1 at (0,0) | warp 2 at (7,0) | two Grunts + Scientist Marc | -| 5 | `MAP_RADIO_TOWER_4F` | `maps/RadioTower4F.asm` | warp 2 at (9,0) | warp 1 at (0,0) | `TrainerGruntM10`, `TrainerScientistRich` | -| 6 | `MAP_RADIO_TOWER_5F` | `maps/RadioTower5F.asm` | warp 1 at (0,0) | warp 1 at (0,0) | `coord_event 0, 3` -> `FakeDirectorScript`, `EXECUTIVEM_3`, `BASEMENT_KEY` | -| 7 | `MAP_GOLDENROD_CITY` | `maps/GoldenrodCity.asm` | radio tower warp | warp 14 at (9,5) | "northwesternmost building" = north Underground entrance | -| 8 | `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` | warp 8 at (20,29) | warp 7 at (21,25) | just the stairwell room on this pass | -| 9 | `MAP_GOLDENROD_UNDERGROUND` | `maps/GoldenrodUnderground.asm` | warp 1 at (3,2) | warp 3 at (18,6) | Pokemon Salon (haircut brothers); the locked door | -| 10 | `MAP_GOLDENROD_UNDERGROUND` (south half) | same file | warp 4 at (21,31) | warp 6 at (22,27) | the basement door is a same-map warp pair | -| 11 | `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` | warp 1 at (23,3) | warps 2/3 at (22,10)/(23,10) | rival battle, three switch Grunts, two Burglars, the shutter puzzle | -| 12 | `MAP_GOLDENROD_UNDERGROUND_WAREHOUSE` | `maps/GoldenrodUndergroundWarehouse.asm` | warps 1/2 at (2,12)/(3,12) | warp 3 at (17,2) | three Grunts, Max Ether, TM35, the Director + `CARD_KEY` | -| 13 | `MAP_GOLDENROD_DEPT_STORE_B1F` | `maps/GoldenrodDeptStoreB1F.asm` | warp 1 at (17,2) | warp 1 back, or elevator warps 2/3 at (9,4)/(10,4) | Amulet Coin at (14,2) | -| 14 | `MAP_RADIO_TOWER_3F` | `maps/RadioTower3F.asm` | 2F warp 1 at (0,0) | warp 3 at (17,0) | `bg_event 14, 2, BGEVENT_UP, CardKeySlotScript`; `TrainerGruntM9` | -| 15 | `MAP_RADIO_TOWER_4F` | `maps/RadioTower4F.asm` | warp 4 at (17,0) | warp 3 at (12,0) | `TrainerGruntF4`, `TrainerExecutivem2` | -| 16 | `MAP_RADIO_TOWER_5F` | `maps/RadioTower5F.asm` | warp 2 at (12,0) | warp 2 at (12,0) | `TrainerExecutivef1`, then `coord_event 16, 5` -> `RadioTower5FRocketBossScript` | - -Spill into the next section: the walkthrough's closing party-level lists and the -video link's mention of Route 44 belong to whatever section follows; nothing -after `RadioTower5FRocketBossScript` is covered here. - ---- - -## 2. Maps - -### MAP_GOLDENROD_CITY - -- Script: `maps/GoldenrodCity.asm` -- Blocks: `maps/GoldenrodCity.blk` -- Header: `data/maps/maps.asm:269` -> `TILESET_JOHTO_MODERN`, `TOWN`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_GOLDENROD_CITY`, phone `FALSE`, - `PALETTE_AUTO`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:247` -> `map_const GOLDENROD_CITY, 20, 18` -- Connections: `data/maps/attributes.asm:139` -> north `ROUTE_35` (offset 5), - south `ROUTE_34` (offset 5). No east/west. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 24 | 7 | `GOLDENROD_GYM` | 1 | -| 2 | 29 | 29 | `GOLDENROD_BIKE_SHOP` | 1 | -| 3 | 31 | 21 | `GOLDENROD_HAPPINESS_RATER` | 1 | -| 4 | 5 | 25 | `BILLS_FAMILYS_HOUSE` | 1 | -| 5 | 9 | 13 | `GOLDENROD_MAGNET_TRAIN_STATION` | 2 | -| 6 | 33 | 5 | `GOLDENROD_FLOWER_SHOP` | 1 | -| 7 | 15 | 27 | `GOLDENROD_POKECENTER_1F` | 1 | -| 8 | 33 | 9 | `GOLDENROD_PP_SPEECH_HOUSE` | 1 | -| 9 | 15 | 7 | `GOLDENROD_NAME_RATER` | 1 | -| 10 | 24 | 27 | `GOLDENROD_DEPT_STORE_1F` | 1 | -| 11 | 14 | 21 | `GOLDENROD_GAME_CORNER` | 1 | -| 12 | 5 | 15 | `RADIO_TOWER_1F` | 1 | -| 13 | 19 | 1 | `ROUTE_35_GOLDENROD_GATE` | 3 | -| 14 | 9 | 5 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 8 | -| 15 | 11 | 29 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 5 | - -Warp 14 is the "northwesternmost building" the walkthrough sends you to for the -Basement Key trip; warp 15 is the southern Underground entrance. - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 10 | 14 | `BGEVENT_READ` | `GoldenrodCityStationSign` | -| 4 | 17 | `BGEVENT_READ` | `GoldenrodCityRadioTowerSign` | -| 26 | 27 | `BGEVENT_READ` | `GoldenrodDeptStoreSign` | -| 26 | 9 | `BGEVENT_READ` | `GoldenrodGymSign` | -| 22 | 18 | `BGEVENT_READ` | `GoldenrodCitySign` | -| 28 | 30 | `BGEVENT_READ` | `GoldenrodCityBikeShopSign` | -| 16 | 22 | `BGEVENT_READ` | `GoldenrodCityGameCornerSign` | -| 12 | 7 | `BGEVENT_READ` | `GoldenrodCityNameRaterSign` | -| 8 | 6 | `BGEVENT_READ` | `GoldenrodCityUndergroundSignNorth` | -| 12 | 30 | `BGEVENT_READ` | `GoldenrodCityUndergroundSignSouth` | -| 16 | 27 | `BGEVENT_READ` | `GoldenrodCityPokecenterSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODCITY_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 7 | 18 | `STANDING_UP` | `SCRIPT` | `GoldenrodCityPokefanMScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 30 | 17 | `WANDER` | `SCRIPT` | `GoldenrodCityYoungster1Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 12 | 16 | `STANDING_LEFT` | `SCRIPT` | `GoldenrodCityCooltrainerF1Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 20 | 26 | `WANDER` | `SCRIPT` | `GoldenrodCityCooltrainerF2Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 19 | 17 | `WANDER` | `SCRIPT` | `GoldenrodCityYoungster2Script` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_LASS` | `SPRITE_LASS` | 17 | 10 | `WALK_LEFT_RIGHT` | `SCRIPT` | `GoldenrodCityLassScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_GRAMPS` | `SPRITE_GRAMPS` | 11 | 27 | `WALK_LEFT_RIGHT` | `SCRIPT` | `GoldenrodCityGrampsScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `GOLDENRODCITY_ROCKETSCOUT` | `SPRITE_ROCKET` | 4 | 16 | `STANDING_UP` | `SCRIPT` | `GoldenrodCityRocketScoutScript` | `EVENT_GOLDENROD_CITY_ROCKET_SCOUT` | -| `GOLDENRODCITY_ROCKET1` | `SPRITE_ROCKET` | 28 | 20 | `STANDING_UP` | `SCRIPT` | `GoldenrodCityRocket1Script` | `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET2` | `SPRITE_ROCKET` | 8 | 15 | `STANDING_DOWN` | `SCRIPT` | `GoldenrodCityRocket2Script` | `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET3` | `SPRITE_ROCKET` | 16 | 23 | `STANDING_RIGHT` | `SCRIPT` | `GoldenrodCityRocket3Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET4` | `SPRITE_ROCKET` | 29 | 20 | `STANDING_UP` | `SCRIPT` | `GoldenrodCityRocket4Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET5` | `SPRITE_ROCKET` | 29 | 7 | `STANDING_DOWN` | `SCRIPT` | `GoldenrodCityRocket5Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `GOLDENRODCITY_ROCKET6` | `SPRITE_ROCKET` | 30 | 10 | `STANDING_LEFT` | `SCRIPT` | `GoldenrodCityRocket6Script` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | - -None of the city Rockets are trainers - they are all `jumptextfaceplayer`. - -**Scripts of interest** - -- `GoldenrodCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): `setflag - ENGINE_FLYPOINT_GOLDENROD` / `setflag ENGINE_REACHED_GOLDENROD`. This is why - the walkthrough can say "fly to the city". - ---- - -### MAP_RADIO_TOWER_1F - -- Script: `maps/RadioTower1F.asm` -- Blocks: `maps/RadioTower1F.blk` -- Header: `data/maps/maps.asm:95` -> `TILESET_RADIO_TOWER`, `INDOOR`, - `LANDMARK_RADIO_TOWER`, `RADIO_TOWER_MUSIC | MUSIC_GOLDENROD_CITY`, phone - `TRUE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:82` -> `map_const RADIO_TOWER_1F, 9, 4` -- Connections: none (`data/maps/attributes.asm:414`, environment byte `$00`) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `GOLDENROD_CITY` | 12 | -| 2 | 3 | 7 | `GOLDENROD_CITY` | 12 | -| 3 | 15 | 0 | `RADIO_TOWER_2F` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 0 | `BGEVENT_READ` | `RadioTower1FDirectory` | -| 13 | 0 | `BGEVENT_READ` | `RadioTower1FLuckyChannelSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RADIOTOWER1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 5 | 6 | `STANDING_LEFT` | `SCRIPT` | `RadioTower1FReceptionistScript` | -1 | -| `RADIOTOWER1F_LASS` | `SPRITE_LASS` | 16 | 4 | `STANDING_LEFT` | `SCRIPT` | `RadioTower1FLassScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `RADIOTOWER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 15 | 4 | `STANDING_RIGHT` | `SCRIPT` | `RadioTower1FYoungsterScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `RADIOTOWER1F_ROCKET` | `SPRITE_ROCKET` | 14 | 1 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM3` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER1F_LUCKYNUMBERMAN` | `SPRITE_GENTLEMAN` | 8 | 6 | `STANDING_UP` | `SCRIPT` | `RadioTower1FLuckyNumberManScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `RADIOTOWER1F_CARD_WOMAN` | `SPRITE_COOLTRAINER_F` | 12 | 6 | `STANDING_UP` | `SCRIPT` | `RadioTower1FRadioCardWomanScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | - -**Scripts of interest** - -- `RadioTower1FReceptionistScript` - `checkflag ENGINE_ROCKETS_IN_RADIO_TOWER`; - if set, the "no tours today" line. Pure flavour, no gate. -- `TrainerGruntM3` - `trainer GRUNTM, GRUNTM_3, EVENT_BEAT_ROCKET_GRUNTM_3, ...`. - Sight range 3 facing down from (14,1): he intercepts the walk to warp 3 at - (15,0). -- `RadioTower1FRadioCardWomanScript` / `RadioTower1FLuckyNumberManScript` are the - Radio Card quiz and the Lucky Number Show. Both are hidden while - `EVENT_GOLDENROD_CITY_CIVILIANS` is set, i.e. for the whole of this section. - ---- - -### MAP_RADIO_TOWER_2F - -- Script: `maps/RadioTower2F.asm` -- Blocks: `maps/RadioTower2F.blk` -- Header: `data/maps/maps.asm:96` -> same row shape as 1F -- Dimensions: `constants/map_constants.asm:83` -> `map_const RADIO_TOWER_2F, 9, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `RADIO_TOWER_3F` | 1 | -| 2 | 15 | 0 | `RADIO_TOWER_1F` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 0 | `BGEVENT_READ` | `RadioTower2FSalesSign` | -| 5 | 0 | `BGEVENT_READ` | `RadioTower2FOaksPKMNTalkSign` | -| 9 | 1 | `BGEVENT_READ` | `RadioTower2FBookshelf` | -| 10 | 1 | `BGEVENT_READ` | `RadioTower2FBookshelf` | -| 11 | 1 | `BGEVENT_READ` | `RadioTower2FBookshelf` | -| 13 | 0 | `BGEVENT_READ` | `RadioTower2FPokemonRadioSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RADIOTOWER2F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 5 | 6 | `WALK_LEFT_RIGHT` | `SCRIPT` | `RadioTower2FSuperNerdScript` | `EVENT_GOLDENROD_CITY_CIVILIANS` | -| `RADIOTOWER2F_TEACHER` | `SPRITE_TEACHER` | 13 | 2 | `WALK_LEFT_RIGHT` | `SCRIPT` | `RadioTower2FTeacherScript` | -1 | -| `RADIOTOWER2F_ROCKET1` | `SPRITE_ROCKET` | 1 | 4 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerGruntM4` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER2F_ROCKET2` | `SPRITE_ROCKET` | 8 | 4 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM5` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER2F_ROCKET3` | `SPRITE_ROCKET` | 4 | 1 | `STANDING_DOWN` | `TRAINER` (sight 2) | `TrainerGruntM6` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER2F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 10 | 5 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerGruntF2` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER2F_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 0 | 1 | `STANDING_DOWN` | `SCRIPT` | `RadioTower2FBlackBelt1Script` | `EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS` | -| `RADIOTOWER2F_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 1 | 1 | `STANDING_DOWN` | `SCRIPT` | `RadioTower2FBlackBelt2Script` | `EVENT_RADIO_TOWER_CIVILIANS_AFTER` | -| `RADIOTOWER2F_JIGGLYPUFF` | `SPRITE_JIGGLYPUFF` | 12 | 1 | `POKEMON` | `SCRIPT` | `RadioTowerJigglypuff` | -1 | - -**Scripts of interest** - -- `RadioTower2FBlackBelt1Script` at (0,1) is the pre-takeover stairs blocker - ("Authorized personnel only beyond this point"). `RadioTowerRocketsScript` - (`engine/events/std_scripts.asm:255`) does `setevent - EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS`, and a SET object event flag MASKS - the object (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32`), so - during the takeover he is gone and warp 1 at (0,0) is walkable. -- Walkthrough order on this floor is `TrainerGruntF2` (10,5) -> `TrainerGruntM5` - (8,4) -> `TrainerGruntM6` (4,1) -> `TrainerGruntM4` (1,4). - ---- - -### MAP_RADIO_TOWER_3F - -- Script: `maps/RadioTower3F.asm` -- Blocks: `maps/RadioTower3F.blk` -- Header: `data/maps/maps.asm:97` -- Dimensions: `constants/map_constants.asm:84` -> `map_const RADIO_TOWER_3F, 9, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `RADIO_TOWER_2F` | 1 | -| 2 | 7 | 0 | `RADIO_TOWER_4F` | 2 | -| 3 | 17 | 0 | `RADIO_TOWER_4F` | 4 | - -Warp 3 is only reachable once the Card Key shutter is open. - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 0 | `BGEVENT_READ` | `RadioTower3FPersonnelSign` | -| 9 | 0 | `BGEVENT_READ` | `RadioTower3FPokemonMusicSign` | -| 14 | 2 | `BGEVENT_UP` | `CardKeySlotScript` | - -`BGEVENT_UP` means the player must be FACING UP on the tile below (14,2) for the -press to register (`engine/overworld/events.asm:644` `.up` -> `.checkdir` -compares `wPlayerDirection & %1100` against `OW_UP`). - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RADIOTOWER3F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 7 | 4 | `STANDING_UP` | `SCRIPT` | `RadioTower3FSuperNerdScript` | `EVENT_RADIO_TOWER_CIVILIANS_AFTER` | -| `RADIOTOWER3F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 3 | 4 | `SPINRANDOM_FAST` | `SCRIPT` | `RadioTower3FGymGuideScript` | -1 | -| `RADIOTOWER3F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 11 | 3 | `WANDER` | `SCRIPT` | `RadioTower3FCooltrainerFScript` | -1 | -| `RADIOTOWER3F_ROCKET1` | `SPRITE_ROCKET` | 5 | 1 | `STANDING_RIGHT` | `TRAINER` (sight 2) | `TrainerGruntM7` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER3F_ROCKET2` | `SPRITE_ROCKET` | 6 | 2 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM8` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER3F_ROCKET3` | `SPRITE_ROCKET` | 16 | 6 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerGruntM9` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER3F_SCIENTIST` | `SPRITE_SCIENTIST` | 9 | 6 | `STANDING_UP` | `TRAINER` (sight 5) | `TrainerScientistMarc` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | - -`TrainerGruntM9` at (16,6) sits behind the shutter: he is only reachable on the -second pass. - -**Scripts of interest** - -- `RadioTower3FCardKeyShutterCallback` (`callback MAPCALLBACK_TILES`, label at - `43:5be1`): `checkevent EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER`; if true, - `changeblock 14, 2, $2a` (open shutter) and `changeblock 14, 4, $01` (floor). - This is what makes the opening persist across map loads. -- `CardKeySlotScript` (`43:5c91`, exported with `::` so other maps can reach it): - writes `RadioTower3FCardKeySlotText`, then `checkevent - EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER` (already used -> just close), - else `checkitem CARD_KEY`. With the key: `InsertedTheCardKeyText`, `setevent - EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER`, `playsound SFX_ENTER_DOOR`, the - same two `changeblock`s, `refreshmap`. -- `RadioTower3FCooltrainerFScript` - after `EVENT_CLEARED_RADIO_TOWER`, - `verbosegiveitem TM_SUNNY_DAY` and `setevent - EVENT_GOT_SUNNY_DAY_FROM_RADIO_TOWER`. The walkthrough does not mention this - reward. - ---- - -### MAP_RADIO_TOWER_4F - -- Script: `maps/RadioTower4F.asm` -- Blocks: `maps/RadioTower4F.blk` -- Header: `data/maps/maps.asm:98` -- Dimensions: `constants/map_constants.asm:85` -> `map_const RADIO_TOWER_4F, 9, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `RADIO_TOWER_5F` | 1 | -| 2 | 9 | 0 | `RADIO_TOWER_3F` | 2 | -| 3 | 12 | 0 | `RADIO_TOWER_5F` | 2 | -| 4 | 17 | 0 | `RADIO_TOWER_3F` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 0 | `BGEVENT_READ` | `RadioTower4FProductionSign` | -| 15 | 0 | `BGEVENT_READ` | `RadioTower4FStudio2Sign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RADIOTOWER4F_FISHER` | `SPRITE_FISHER` | 6 | 4 | `STANDING_UP` | `SCRIPT` | `RadioTower4FFisherScript` | `EVENT_RADIO_TOWER_CIVILIANS_AFTER` | -| `RADIOTOWER4F_TEACHER` | `SPRITE_TEACHER` | 14 | 6 | `SPINRANDOM_SLOW` | `SCRIPT` | `RadioTower4FDJMaryScript` | -1 | -| `RADIOTOWER4F_GROWLITHE` | `SPRITE_GROWLITHE` | 12 | 7 | `POKEMON` | `SCRIPT` | `RadioTowerMeowth` | -1 | -| `RADIOTOWER4F_ROCKET1` | `SPRITE_ROCKET` | 5 | 6 | `SPINCLOCKWISE` | `TRAINER` (sight 3) | `TrainerGruntM10` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER4F_ROCKET2` | `SPRITE_ROCKET` | 14 | 1 | `STANDING_LEFT` (radius 2,0) | `TRAINER` (sight 2) | `TrainerExecutivem2` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER4F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 12 | 4 | `STANDING_RIGHT` | `TRAINER` (sight 1) | `TrainerGruntF4` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER4F_SCIENTIST` | `SPRITE_SCIENTIST` | 4 | 2 | `STANDING_LEFT` | `TRAINER` (sight 4) | `TrainerScientistRich` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | - -**Scripts of interest** - -- `TrainerExecutivem2` at (14,1) guards warp 3 (12,0), the second route to 5F. -- `RadioTower4FDJMaryScript` - after `EVENT_CLEARED_RADIO_TOWER`, - `verbosegiveitem PINK_BOW` and `setevent EVENT_GOT_PINK_BOW_FROM_MARY`. Another - reward the walkthrough skips. - ---- - -### MAP_RADIO_TOWER_5F - -- Script: `maps/RadioTower5F.asm` -- Blocks: `maps/RadioTower5F.blk` -- Header: `data/maps/maps.asm:99` -- Dimensions: `constants/map_constants.asm:86` -> `map_const RADIO_TOWER_5F, 9, 4` - -Scene ids come from this map's own `def_scene_scripts` block (`scene_script` -allocates them in order, `macros/scripts/maps.asm:25`): -`SCENE_RADIOTOWER5F_FAKE_DIRECTOR` = 0, `SCENE_RADIOTOWER5F_ROCKET_BOSS` = 1, -`SCENE_RADIOTOWER5F_NOOP` = 2. All three scene bodies are `end`; the work is -entirely in the coord events. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `RADIO_TOWER_4F` | 1 | -| 2 | 12 | 0 | `RADIO_TOWER_4F` | 3 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_RADIOTOWER5F_FAKE_DIRECTOR` (0) | 0 | 3 | `FakeDirectorScript` | fake director battle, `BASEMENT_KEY` | -| `SCENE_RADIOTOWER5F_ROCKET_BOSS` (1) | 16 | 5 | `RadioTower5FRocketBossScript` | final Executive, tower cleared | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 0 | `BGEVENT_READ` | `RadioTower5FDirectorsOfficeSign` | -| 11 | 0 | `BGEVENT_READ` | `RadioTower5FStudio1Sign` | -| 15 | 0 | `BGEVENT_READ` | `RadioTower5FStudio1Sign` | -| 16 | 1 | `BGEVENT_READ` | `RadioTower5FBookshelf` | -| 17 | 1 | `BGEVENT_READ` | `RadioTower5FBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `RADIOTOWER5F_DIRECTOR` | `SPRITE_GENTLEMAN` | 3 | 6 | `SPINRANDOM_SLOW` | `SCRIPT` | `Director` | -1 | -| `RADIOTOWER5F_ROCKET` | `SPRITE_ROCKET` | 13 | 5 | `STANDING_LEFT` | `SCRIPT` | `ObjectEvent` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER5F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 17 | 2 | `STANDING_LEFT` | `TRAINER` (sight 1) | `TrainerExecutivef1` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `RADIOTOWER5F_ROCKER` | `SPRITE_ROCKER` | 13 | 5 | `STANDING_LEFT` | `SCRIPT` | `Ben` | `EVENT_RADIO_TOWER_CIVILIANS_AFTER` | - -Note the deliberate coordinate collision at (13,5): the Rocket boss and DJ Ben -occupy the same cell, masked by complementary flags. - -**Scripts of interest** - -- `FakeDirectorScript` (`43:6748`) - `turnobject RADIOTOWER5F_DIRECTOR, UP`, - `showemote EMOTE_SHOCK`, text, `applymovement RADIOTOWER5F_DIRECTOR, - FakeDirectorMovement` (LEFT x3, UP x2, from (3,6) to (0,4)), `playmusic - MUSIC_ROCKET_ENCOUNTER`, `winlosstext FakeDirectorWinText, 0`, `setlasttalked - RADIOTOWER5F_DIRECTOR`, `loadtrainer EXECUTIVEM, EXECUTIVEM_3`, `startbattle`, - `reloadmapafterbattle`, `verbosegiveitem BASEMENT_KEY`, `setscene - SCENE_RADIOTOWER5F_ROCKET_BOSS`, `setevent EVENT_BEAT_ROCKET_EXECUTIVEM_3`. - Note the `verbosegiveitem` result is not checked - there is no bag-full arm. -- `RadioTower5FRocketBossScript` (`43:67a5`) - `applymovement PLAYER` two steps - LEFT (from (16,5) to (14,5)), `playmusic MUSIC_ROCKET_ENCOUNTER`, `turnobject - RADIOTOWER5F_ROCKET, RIGHT`, `loadtrainer EXECUTIVEM, EXECUTIVEM_1`, - `startbattle`, `reloadmapafterbattle`. Afterwards it fades out, disappears both - Rockets, then does the whole flag cascade: - - ``` - setevent EVENT_BEAT_ROCKET_EXECUTIVEM_1 - setevent EVENT_CLEARED_RADIO_TOWER - clearflag ENGINE_ROCKETS_IN_RADIO_TOWER - setevent EVENT_GOLDENROD_CITY_ROCKET_SCOUT - setevent EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER - setevent EVENT_RADIO_TOWER_ROCKET_TAKEOVER - clearevent EVENT_MAHOGANY_MART_OWNERS - clearflag ENGINE_ROCKETS_IN_MAHOGANY - clearevent EVENT_GOLDENROD_CITY_CIVILIANS - clearevent EVENT_RADIO_TOWER_CIVILIANS_AFTER - setevent EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM - clearevent EVENT_BLACKTHORN_CITY_SUPER_NERD_DOES_NOT_BLOCK_GYM - ``` - - Then `moveobject RADIOTOWER5F_DIRECTOR, 12, 0` / `appear` / `applymovement - RadioTower5FDirectorWalksIn`, `checkver`: Silver takes `.SilverWing` - (`verbosegiveitem SILVER_WING`, `setevent EVENT_GOT_SILVER_WING`), Gold falls - through (`verbosegiveitem RAINBOW_WING`, `setevent EVENT_GOT_RAINBOW_WING`, - `setevent EVENT_TEAM_ROCKET_DISBANDED`). Both arms `setscene - SCENE_RADIOTOWER5F_NOOP` and end with `RadioTower5FDirectorWalksOut` + - `disappear`. -- `Director` (the talk-to script) branches on `EVENT_CLEARED_RADIO_TOWER`. - ---- - -### MAP_GOLDENROD_UNDERGROUND - -- Script: `maps/GoldenrodUnderground.asm` -- Blocks: `maps/GoldenrodUnderground.blk` -- Header: `data/maps/maps.asm:123` -> `TILESET_GATE`, `DUNGEON`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:110` -> `map_const GOLDENROD_UNDERGROUND, 15, 18` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 7 | -| 2 | 3 | 34 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 4 | -| 3 | 18 | 6 | `GOLDENROD_UNDERGROUND` | 4 | -| 4 | 21 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 5 | 22 | 31 | `GOLDENROD_UNDERGROUND` | 3 | -| 6 | 22 | 27 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 1 | - -Warps 3/4/5 are a same-map pair: the Basement Key door at (18,6) drops you at -(21,31), and (22,27) is the stairs into the switch room proper. - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 18 | 6 | `BGEVENT_READ` | `BasementDoorScript` | -| 19 | 6 | `BGEVENT_READ` | `GoldenrodUndergroundNoEntrySign` | -| 6 | 13 | `BGEVENT_ITEM` | `hiddenitem PARLYZ_HEAL, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_PARLYZ_HEAL` | -| 4 | 18 | `BGEVENT_ITEM` | `hiddenitem SUPER_POTION, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_SUPER_POTION` | -| 17 | 8 | `BGEVENT_ITEM` | `hiddenitem ANTIDOTE, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_ANTIDOTE` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `GOLDENRODUNDERGROUND_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 31 | `STANDING_LEFT` | `TRAINER` (sight 3) | `TrainerSupernerdEric` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 6 | 9 | `STANDING_UP` | `TRAINER` (sight 2) | `TrainerSupernerdTeru` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD3` | `SPRITE_SUPER_NERD` | 3 | 27 | `SPINRANDOM_FAST` | `TRAINER` (sight 2) | `TrainerPokemaniacIssac` | -1 | -| `GOLDENRODUNDERGROUND_SUPER_NERD4` | `SPRITE_SUPER_NERD` | 2 | 6 | `STANDING_RIGHT` | `TRAINER` (sight 3) | `TrainerPokemaniacDonald` | -1 | -| `GOLDENRODUNDERGROUND_POKE_BALL` | `SPRITE_POKE_BALL` | 7 | 25 | `STILL` | `ITEMBALL` | `itemball COIN_CASE` | `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE` | -| `GOLDENRODUNDERGROUND_GRAMPS` | `SPRITE_GRAMPS` | 7 | 11 | `STANDING_LEFT` | `SCRIPT` | `BargainMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRAMPS` | -| `GOLDENRODUNDERGROUND_OLDER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 14 | `STANDING_LEFT` | `SCRIPT` | `OlderHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 15 | `STANDING_LEFT` | `SCRIPT` | `YoungerHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | -| `GOLDENRODUNDERGROUND_GRANNY` | `SPRITE_GRANNY` | 7 | 21 | `STANDING_LEFT` | `SCRIPT` | `BitterMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRANNY` | - -**Scripts of interest** - -- `BasementDoorScript` (`46:429e`, exported `::`) - `checkevent - EVENT_USED_BASEMENT_KEY` (already open -> "The door is open"), else `checkitem - BASEMENT_KEY`; with the key: `playsound SFX_TRANSACTION`, `changeblock 18, 6, - $2e` (unlocked door), `refreshmap`, `setevent EVENT_USED_BASEMENT_KEY`. Without - it: "The door's locked..." -- `GoldenrodUndergroundCheckBasementKeyCallback` (`MAPCALLBACK_TILES`) re-locks - the block on every map load until `EVENT_USED_BASEMENT_KEY` is set: - `changeblock 18, 6, $3d`. -- `GoldenrodUndergroundResetSwitchesCallback` (`MAPCALLBACK_NEWMAP`) - clears - `EVENT_SWITCH_1..3`, `EVENT_EMERGENCY_SWITCH`, `EVENT_DOOR_1_OPEN .. - EVENT_DOOR_11_OPEN`, and `setval 0` / `writemem wUndergroundSwitchPositions` - (`01:d6a8`). This is the "exit to the stairs then come back up so the switches - are reset" step the walkthrough relies on. The warehouse map has an identical - callback (`GoldenrodUndergroundWarehouseResetSwitchesCallback`). -- `OlderHaircutBrotherScript` / `YoungerHaircutBrotherScript` - the "Pokemon - Salon" happiness boost. Day-gated by - `GoldenrodUndergroundCheckDayOfWeekCallback` (`MAPCALLBACK_OBJECTS`): older - brother TUE/THU/SAT (500), younger brother SUN/WED/FRI (300). Once per day via - `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT`. - ---- - -### MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES - -- Script: `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` -- Blocks: `maps/GoldenrodUndergroundSwitchRoomEntrances.blk` -- Header: `data/maps/maps.asm:124` -> `TILESET_ELITE_FOUR_ROOM`, `DUNGEON`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:111` -> `map_const GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES, 15, 18` - -Scene ids from this map's own block: -`SCENE_GOLDENRODUNDERGROUNDSWITCHROOMENTRANCES_RIVAL_BATTLE` = 0, -`..._NOOP` = 1. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 3 | `GOLDENROD_UNDERGROUND` | 6 | -| 2 | 22 | 10 | `GOLDENROD_UNDERGROUND_WAREHOUSE` | 1 | -| 3 | 23 | 10 | `GOLDENROD_UNDERGROUND_WAREHOUSE` | 2 | -| 4 | 5 | 25 | `GOLDENROD_UNDERGROUND` | 2 | -| 5 | 4 | 29 | `GOLDENROD_CITY` | 15 | -| 6 | 5 | 29 | `GOLDENROD_CITY` | 15 | -| 7 | 21 | 25 | `GOLDENROD_UNDERGROUND` | 1 | -| 8 | 20 | 29 | `GOLDENROD_CITY` | 14 | -| 9 | 21 | 29 | `GOLDENROD_CITY` | 14 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `..._RIVAL_BATTLE` (0) | 19 | 4 | `UndergroundRivalScene1` | rival appears from (23,3), battle | -| `..._RIVAL_BATTLE` (0) | 19 | 5 | `UndergroundRivalScene2` | same, one row lower | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 1 | `BGEVENT_READ` | `Switch1Script` | -| 10 | 1 | `BGEVENT_READ` | `Switch2Script` | -| 2 | 1 | `BGEVENT_READ` | `Switch3Script` | -| 20 | 11 | `BGEVENT_READ` | `EmergencySwitchScript` | -| 8 | 9 | `BGEVENT_ITEM` | `hiddenitem MAX_POTION, EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_HIDDEN_MAX_POTION` | -| 1 | 8 | `BGEVENT_ITEM` | `hiddenitem REVIVE, EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_HIDDEN_REVIVE` | - -The two hidden items are not mentioned by the walkthrough. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._PHARMACIST1` | `SPRITE_PHARMACIST` | 9 | 12 | `STANDING_RIGHT` | `TRAINER` (sight 2) | `TrainerBurglarDuncan` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._PHARMACIST2` | `SPRITE_PHARMACIST` | 4 | 8 | `STANDING_LEFT` | `TRAINER` (sight 2) | `TrainerBurglarEddie` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET1` | `SPRITE_ROCKET` | 17 | 2 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM13` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET2` | `SPRITE_ROCKET` | 11 | 2 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM11` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET3` | `SPRITE_ROCKET` | 3 | 2 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerGruntM25` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 19 | 12 | `STANDING_DOWN` | `TRAINER` (sight 1) | `TrainerGruntF3` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._TEACHER` | `SPRITE_TEACHER` | 3 | 27 | `STANDING_DOWN` | `SCRIPT` | `GoldenrodUndergroundSwitchRoomEntrancesTeacherScript` | -1 | -| `..._SUPER_NERD` | `SPRITE_SUPER_NERD` | 19 | 27 | `STANDING_DOWN` | `SCRIPT` | `GoldenrodUndergroundSwitchRoomEntrancesSuperNerdScript` | -1 | -| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 1 | 12 | `STILL` | `ITEMBALL` | `itemball SMOKE_BALL` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_SMOKE_BALL` | -| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 9 | `STILL` | `ITEMBALL` | `itemball FULL_HEAL` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_FULL_HEAL` | -| `..._RIVAL` | `SPRITE_RIVAL` | 23 | 3 | `STANDING_DOWN` | `SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_GOLDENROD_UNDERGROUND` | - -**Scripts of interest - the shutter puzzle** - -This is the load-bearing part of the section, and the asm behaves differently -from how the walkthrough describes it. - -The eleven shutters are declared at the top of the file by the local -`ugdoor_def` macro (coordinates are `changeblock` coordinates, i.e. the same -cell units the script uses): - -| door | x, y | closed block | open block | second half (x, y, closed, open) | -|---|---|---|---|---| -| 1 | 16, 6 | `$3e` | `$2d` | - | -| 2 | 10, 6 | `$3e` | `$2d` | - | -| 3 | 2, 6 | `$3e` | `$2d` | - | -| 4 | 2, 10 | `$3e` | `$2d` | - | -| 5 | 10, 10 | `$3e` | `$2d` | - | -| 6 | 16, 10 | `$3e` | `$2d` | - | -| 7 | 12, 6 | `$3f` | `$2a` | 12, 8, `$3d`, `$2d` | -| 8 | 6, 6 | `$3f` | `$2a` | 6, 8, `$3d`, `$2d` | -| 9 | 12, 10 | `$3f` | `$2a` | 12, 12, `$3d`, `$2d` | -| 10 | 6, 10 | `$3f` | `$2a` | 6, 12, `$3d`, `$2d` | -| 11 | 18, 10 | `$3f` | `$2a` | 18, 12, `$3d`, `$2d` | - -`Switch1Script` / `Switch2Script` / `Switch3Script` each `readmem -wUndergroundSwitchPositions` (`01:d6a8`), `addval +N` when turning ON or -`addval -N` when turning OFF (N = 1, 2, 3 respectively), `writemem` it back, -set/clear `EVENT_SWITCH_N`, then `sjump -GoldenrodUndergroundSwitchRoomEntrances_UpdateDoors` (`46:4c57`). - -`EmergencySwitchScript` at (20,11) is different: ON does `setval 7` (not an -add), sets `EVENT_EMERGENCY_SWITCH` and all three `EVENT_SWITCH_*`; OFF does -`setval 0` and clears all four. - -`..._UpdateDoors` dispatches on the stored byte. Crucially each position only -touches SOME doors - the rest keep whatever state the previous position left -them in, which is the entire reason order matters: - -| value | opens | closes | untouched | -|---|---|---|---| -| 0 | - | 1..11 | - | -| 1 | 1, 7, 10 | 6, 8, 9, 11 | 2, 3, 4, 5 | -| 2 | 2, 8, 9 | 5, 7, 10, 11 | 1, 3, 4, 6 | -| 3 | 3, 7, 10 | 4, 8, 9, 11 | 1, 2, 5, 6 | -| 4 | 4, 8, 9 | 3, 7, 10, 11 | 1, 2, 5, 6 | -| 5 | 5, 7, 10 | 2, 8, 9, 11 | 1, 3, 4, 6 | -| 6 | 6, 8, 9, 11 | 1, 7, 10 | 2, 3, 4, 5 | -| 7 (emergency) | 3, 5, 6, 8, 9, 11 | 1, 2, 4, 7, 10 | - | - -The emergency arm ends with `setval 6` / `writemem`, so the stored byte becomes -6 afterwards even though the door layout is the emergency one. - -Consequences a bot needs (derived by tracing the table, from the reset state -where the stored byte is 0 and all doors closed): - -- Flip 1, then 2, then 3 (values 1 -> 3 -> 6). Final open doors: 3, 6, 8, 9, 11. - Door 8 at (6,6)/(6,8) is what lets you drop south from the switch-3 alcove to - `TrainerBurglarEddie` at (4,8). -- From there flip 3 off then 2 off (values 6 -> 3 -> 1). Final open doors: 1, 3, - 7, 10. Door 7 at (12,6)/(12,8) is what opens the way to the Full Heal ball at - (14,9). -- Reset (leave via a warp so the `MAPCALLBACK_NEWMAP` reset runs) and flip 3, - then 2, then 1 (values 3 -> 5 -> 6). Final open doors: 3, 5, 6, 8, 9, 11 - - the same set as 1/2/3 PLUS door 5 at (10,10). Door 5 is the one that opens the - route south to `TrainerBurglarDuncan` (9,12), `TrainerGruntF3` (19,12) and the - warehouse warps at (22,10)/(23,10). - -So the walkthrough's "turn them on in the opposite direction" is correct in -effect, but the mechanism is not "order is remembered": the stored byte is a -plain SUM (1+2+3 = 3+2+1 = 6), and what differs is which doors the intermediate -positions left untouched. - -`UndergroundRivalBattleScript` (`46:4a9e`): - -``` -checkevent EVENT_RIVAL_BURNED_TOWER -iftrue .Continue -setevent EVENT_RIVAL_BURNED_TOWER -setmapscene BURNED_TOWER_1F, SCENE_BURNEDTOWER1F_FIREBREATHER_DICK -.Continue: -playmusic MUSIC_RIVAL_ENCOUNTER -... setevent EVENT_RIVAL_GOLDENROD_UNDERGROUND -checkevent EVENT_GOT_TOTODILE_FROM_ELM -> loadtrainer RIVAL1, RIVAL1_4_CHIKORITA -checkevent EVENT_GOT_CHIKORITA_FROM_ELM -> loadtrainer RIVAL1, RIVAL1_4_CYNDAQUIL -else -> loadtrainer RIVAL1, RIVAL1_4_TOTODILE -``` - -i.e. the rival's starter is the one with type advantage over yours, as usual. -`winlosstext UndergroundRivalWinText, UndergroundRivalLossText` - this battle is -LOSABLE (a real loss text exists), unlike the Executive fights. - ---- - -### MAP_GOLDENROD_UNDERGROUND_WAREHOUSE - -- Script: `maps/GoldenrodUndergroundWarehouse.asm` -- Blocks: `maps/GoldenrodUndergroundWarehouse.blk` -- Header: `data/maps/maps.asm:126` -> `TILESET_UNDERGROUND`, `DUNGEON`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DAY`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:113` -> `map_const GOLDENROD_UNDERGROUND_WAREHOUSE, 10, 9` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 12 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 2 | -| 2 | 3 | 12 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 3 | -| 3 | 17 | 2 | `GOLDENROD_DEPT_STORE_B1F` | 1 | - -**Coord events** - none. -**BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._ROCKET1` | `SPRITE_ROCKET` | 9 | 8 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerGruntM24` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET2` | `SPRITE_ROCKET` | 8 | 15 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerGruntM14` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._ROCKET3` | `SPRITE_ROCKET` | 14 | 3 | `STANDING_RIGHT` | `TRAINER` (sight 4) | `TrainerGruntM15` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._GENTLEMAN` | `SPRITE_GENTLEMAN` | 12 | 8 | `SPINRANDOM_SLOW` | `SCRIPT` | `GoldenrodUndergroundWarehouseDirectorScript` | `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | -| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 18 | 15 | `STILL` | `ITEMBALL` | `itemball MAX_ETHER` | `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_MAX_ETHER` | -| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 13 | 9 | `STILL` | `ITEMBALL` | `itemball TM_SLEEP_TALK` | `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_TM_SLEEP_TALK` | - -Note the Director object is masked by `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` the -same way the Grunts are: once the tower is cleared he is gone from here. - -**Scripts of interest** - -- `GoldenrodUndergroundWarehouseDirectorScript` (`46:598a`) - `checkevent - EVENT_RECEIVED_CARD_KEY`; if not: `DirectorIntroText`, `verbosegiveitem - CARD_KEY`, `setevent EVENT_RECEIVED_CARD_KEY`, `setevent - EVENT_GOLDENROD_DEPT_STORE_B1F_LAYOUT_1`, `clearevent ..._LAYOUT_2`, - `clearevent ..._LAYOUT_3`, `DirectorCardKeyText` ("Use that to open the - shutters on 3F"), then `DirectorAfterText`. The `verbosegiveitem` result is - again unchecked. - ---- - -### MAP_GOLDENROD_DEPT_STORE_B1F - -- Script: `maps/GoldenrodDeptStoreB1F.asm` -- Blocks: `maps/GoldenrodDeptStoreB1F.blk` -- Header: `data/maps/maps.asm:125` -> `TILESET_UNDERGROUND`, `DUNGEON`, - `LANDMARK_GOLDENROD_CITY`, `MUSIC_GOLDENROD_CITY`, phone `TRUE`, - `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:112` -> `map_const GOLDENROD_DEPT_STORE_B1F, 10, 9` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 2 | `GOLDENROD_UNDERGROUND_WAREHOUSE` | 3 | -| 2 | 9 | 4 | `GOLDENROD_DEPT_STORE_ELEVATOR` | 1 | -| 3 | 10 | 4 | `GOLDENROD_DEPT_STORE_ELEVATOR` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 10 | 15 | `STILL` | `ITEMBALL` | `itemball ETHER` | `EVENT_GOLDENROD_DEPT_STORE_B1F_ETHER` | -| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 2 | `STILL` | `ITEMBALL` | `itemball AMULET_COIN` | `EVENT_GOLDENROD_DEPT_STORE_B1F_AMULET_COIN` | -| `..._POKE_BALL3` | `SPRITE_POKE_BALL` | 6 | 3 | `STILL` | `ITEMBALL` | `itemball BURN_HEAL` | `EVENT_GOLDENROD_DEPT_STORE_B1F_BURN_HEAL` | -| `..._POKE_BALL4` | `SPRITE_POKE_BALL` | 15 | 15 | `STILL` | `ITEMBALL` | `itemball ULTRA_BALL` | `EVENT_GOLDENROD_DEPT_STORE_B1F_ULTRA_BALL` | -| `..._BLACK_BELT1` | `SPRITE_BLACK_BELT` | 9 | 10 | `WALK_UP_DOWN` | `SCRIPT` | `GoldenrodDeptStoreB1FBlackBelt1Script` | -1 | -| `..._BLACK_BELT2` | `SPRITE_BLACK_BELT` | 4 | 8 | `SPINRANDOM_SLOW` | `SCRIPT` | `GoldenrodDeptStoreB1FBlackBelt2Script` | -1 | -| `..._BLACK_BELT3` | `SPRITE_BLACK_BELT` | 6 | 13 | `WALK_LEFT_RIGHT` | `SCRIPT` | `GoldenrodDeptStoreB1FBlackBelt3Script` | -1 | -| `..._MACHOP` | `SPRITE_MACHOP` | 7 | 7 | `POKEMON` | `SCRIPT` | `GoldenrodDeptStoreB1FMachopScript` | -1 | - -**Scripts of interest** - -- `GoldenRodDeptStoreB1FClearBoxesCallback` (`MAPCALLBACK_TILES`) - `checkevent - EVENT_RECEIVED_CARD_KEY` -> `changeblock 16, 4, $0d` (floor). Then the crate - layout: `EVENT_GOLDENROD_DEPT_STORE_B1F_LAYOUT_2` -> `changeblock 4, 10, $0d`; - `..._LAYOUT_3` -> `changeblock 10, 12, $0d`; default (LAYOUT_1) -> - `changeblock 10, 8, $0d`. -- `GoldenRodDeptStoreUnblockCallback` (`MAPCALLBACK_NEWMAP`) - `clearevent - EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_BLOCKED_OFF`. That event starts SET - (`InitializeEventsScript`, `engine/events/std_scripts.asm:518`) and is what - `GoldenrodDeptStoreElevatorScript` (`maps/GoldenrodDeptStoreElevator.asm:15`) - checks before rotating the crate layout - so the layout only ever changes - once you have entered B1F on foot from the warehouse. - -**Flags and events (whole section)** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_ROCKETS_IN_RADIO_TOWER` | `constants/engine_flags.asm:28` | set by `RadioTowerRocketsScript`; cleared by `RadioTower5FRocketBossScript`; read by `RadioTower1FReceptionistScript`, `RadioTower2FTeacherScript`, `MahoganyRedGyaradosSpeechHouse`, `engine/phone/scripts/trainers.asm` | "the takeover arc is live"; also switches the radio programming | -| `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` | `constants/event_flags.asm:1136` | CLEARED by `RadioTowerRocketsScript`, SET again by `RadioTower5FRocketBossScript` | object mask for every Rocket trainer in this section; SET = hidden | -| `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` | `:1135` | cleared by `GoldenrodRocketsScript` (6 badges), set by `RadioTower5FRocketBossScript` | the two earliest city Rockets | -| `EVENT_GOLDENROD_CITY_ROCKET_SCOUT` | `:1134` | set by `RadioTower5FRocketBossScript` | hides the scout at (4,16) once cleared | -| `EVENT_GOLDENROD_CITY_CIVILIANS` | `:1137` | set by `RadioTowerRocketsScript`, cleared by `RadioTower5FRocketBossScript` | SET hides the city + 1F/2F civilians for the whole section | -| `EVENT_RADIO_TOWER_CIVILIANS_AFTER` | `:1138` | set at `InitializeEventsScript`, cleared by `RadioTower5FRocketBossScript` | the "after" NPCs (Ben, 3F super nerd, 4F fisher, 2F black belt 2) | -| `EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS` | `:1139` | set by `RadioTowerRocketsScript` | SET removes the 2F stairs blocker, i.e. the takeover is what opens 3F | -| `EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER` | `:46` | cleared by `RadioTowerRocketsScript`, set by `CardKeySlotScript`, read by `RadioTower3FCardKeyShutterCallback` and `RadioTower3FCooltrainerFScript` | the 3F shutter | -| `EVENT_CLEARED_RADIO_TOWER` | `:42` | set by `RadioTower5FRocketBossScript` | section-complete flag; gates the TM Sunny Day / Pink Bow rewards | -| `EVENT_USED_BASEMENT_KEY` | `:82` | set by `BasementDoorScript`, read by `GoldenrodUndergroundCheckBasementKeyCallback` | the Underground locked door | -| `EVENT_RECEIVED_CARD_KEY` | `:83` | set by `GoldenrodUndergroundWarehouseDirectorScript`, read by `GoldenRodDeptStoreB1FClearBoxesCallback` | one-shot on the Card Key | -| `EVENT_SWITCH_1/2/3`, `EVENT_EMERGENCY_SWITCH` | `:386-389` | the four switch scripts; cleared by both reset callbacks | which switch is lit | -| `EVENT_DOOR_1_OPEN .. EVENT_DOOR_11_OPEN` | `:390-400` | `..._UpdateDoors` `.OpenDoorN` / `.CloseDoorN`; replayed by `..._UpdateDoorPositionsCallback` (`MAPCALLBACK_TILES`) | per-shutter persistent state | -| `EVENT_RIVAL_GOLDENROD_UNDERGROUND` | `:1123` | set inside `UndergroundRivalBattleScript` | masks the rival object afterwards | -| `EVENT_RIVAL_BURNED_TOWER` | grep `constants/event_flags.asm` | set by `UndergroundRivalBattleScript` if not already | side effect: sets `BURNED_TOWER_1F` scene to `SCENE_BURNEDTOWER1F_FIREBREATHER_DICK` | -| `EVENT_GOLDENROD_DEPT_STORE_B1F_LAYOUT_1/2/3` | `:435-437` | director script, `GoldenrodDeptStoreElevatorScript` | which crate row is open on B1F | -| `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_BLOCKED_OFF` | `:438` | set by `InitializeEventsScript`, cleared by `GoldenRodDeptStoreUnblockCallback` | freezes the crate rotation until you have walked B1F | -| `EVENT_TEAM_ROCKET_DISBANDED` | `:1283` | set by `RadioTower5FRocketBossScript` **only on the Gold branch**; also set in `maps/PewterCity.asm:51` | masks the `TIN_TOWER_1F` sage at (10,2) | -| `EVENT_GOT_RAINBOW_WING` / `EVENT_GOT_SILVER_WING` | `:129-130` | `RadioTower5FRocketBossScript` `checkver` arms | Gold vs Silver reward | -| `SCENE_RADIOTOWER5F_FAKE_DIRECTOR` = 0, `_ROCKET_BOSS` = 1, `_NOOP` = 2 | `maps/RadioTower5F.asm:9-11` | `setscene` in both coord scripts | which 5F cutscene is armed | -| `SCENE_GOLDENRODUNDERGROUNDSWITCHROOMENTRANCES_RIVAL_BATTLE` = 0, `_NOOP` = 1 | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm:57-58` | `setscene` at the end of both rival scenes | one-shot rival trip-wire | -| `SPECIALCALL_WEIRDBROADCAST` = 4 | `constants/phone_constants.asm:48` | queued by `RadioTowerRocketsScript`; row 4 of `data/phone/special_calls.asm` is `SpecialCallOnlyWhenOutside, PHONECONTACT_ELM, ElmPhoneCallerScript`; consumed at `engine/phone/scripts/elm.asm:68` `.rocket` | Elm's call the walkthrough opens with. Only rings OUTDOORS. | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `BASEMENT_KEY` | `verbosegiveitem` after `EXECUTIVEM_3` | `FakeDirectorScript`, `maps/RadioTower5F.asm` | `EVENT_BEAT_ROCKET_EXECUTIVEM_3` (scene also advances) | -| `SMOKE_BALL` | ground ball at (1,12) | `GoldenrodUndergroundSwitchRoomEntrancesSmokeBall` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_SMOKE_BALL` | -| `FULL_HEAL` | ground ball at (14,9) | `GoldenrodUndergroundSwitchRoomEntrancesFullHeal` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_FULL_HEAL` | -| `MAX_POTION` | hidden at (8,9) | `bg_event 8, 9, BGEVENT_ITEM` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_HIDDEN_MAX_POTION` | -| `REVIVE` | hidden at (1,8) | `bg_event 1, 8, BGEVENT_ITEM` | `EVENT_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES_HIDDEN_REVIVE` | -| `MAX_ETHER` | ground ball at (18,15) | `GoldenrodUndergroundWarehouseMaxEther` | `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_MAX_ETHER` | -| `TM_SLEEP_TALK` (TM35) | ground ball at (13,9) | `GoldenrodUndergroundWarehouseTMSleepTalk` | `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_TM_SLEEP_TALK` | -| `CARD_KEY` | talk to Director at (12,8) | `GoldenrodUndergroundWarehouseDirectorScript` | `EVENT_RECEIVED_CARD_KEY` | -| `AMULET_COIN` | ground ball at (14,2) on B1F | `GoldenrodDeptStoreB1FAmuletCoin` | `EVENT_GOLDENROD_DEPT_STORE_B1F_AMULET_COIN` | -| `RAINBOW_WING` (Gold) / `SILVER_WING` (Silver) | director cutscene | `RadioTower5FRocketBossScript` `checkver` | `EVENT_GOT_RAINBOW_WING` / `EVENT_GOT_SILVER_WING` | -| `TM_SUNNY_DAY` (TM11) | talk 3F cooltrainer_f (11,3) after clearing | `RadioTower3FCooltrainerFScript` | `EVENT_GOT_SUNNY_DAY_FROM_RADIO_TOWER` | -| `PINK_BOW` | talk DJ Mary (14,6) after clearing | `RadioTower4FDJMaryScript` | `EVENT_GOT_PINK_BOW_FROM_MARY` | - -TM numbering checked against `constants/item_constants.asm:219` (`TM01` = -`DYNAMICPUNCH`): `SUNNY_DAY` is TM11 and `SLEEP_TALK` is TM35. - -**Trainers** - -All parties from `data/trainers/parties.asm`; class group labels named in the -"party" column. Constants from `constants/trainer_constants.asm`. - -| const | class | id | party (`data/trainers/parties.asm`) | script label | map | -|---|---|---|---|---|---| -| `GRUNTM_3` | `GRUNTM` | 3 | `GruntMGroup` "; GRUNTM (3)": 24 `RATICATE`, 24 `RATICATE` | `TrainerGruntM3` | `RADIO_TOWER_1F` (14,1) | -| `GRUNTF_2` | `GRUNTF` | 2 | `GruntFGroup` (2): 26 `ARBOK` | `TrainerGruntF2` | `RADIO_TOWER_2F` (10,5) | -| `GRUNTM_5` | `GRUNTM` | 5 | (5): 21 `RATTATA`, 21 `RATTATA`, 23 `RATTATA`, 23 `RATTATA`, 23 `RATTATA` | `TrainerGruntM5` | `RADIO_TOWER_2F` (8,4) | -| `GRUNTM_6` | `GRUNTM` | 6 | (6): 26 `ZUBAT`, 26 `ZUBAT` | `TrainerGruntM6` | `RADIO_TOWER_2F` (4,1) | -| `GRUNTM_4` | `GRUNTM` | 4 | (4): 23 `GRIMER`, 23 `GRIMER`, 25 `MUK` | `TrainerGruntM4` | `RADIO_TOWER_2F` (1,4) | -| `GRUNTM_8` | `GRUNTM` | 8 | (8): 26 `WEEZING` | `TrainerGruntM8` | `RADIO_TOWER_3F` (6,2) | -| `GRUNTM_7` | `GRUNTM` | 7 | (7): 23 `KOFFING`, 23 `GRIMER`, 23 `ZUBAT`, 23 `RATTATA` | `TrainerGruntM7` | `RADIO_TOWER_3F` (5,1) | -| `MARC` | `SCIENTIST` | 4 | `ScientistGroup` (4) "MARC": 27 `MAGNEMITE` x3 | `TrainerScientistMarc` | `RADIO_TOWER_3F` (9,6) | -| `GRUNTM_10` | `GRUNTM` | 10 | (10): 22 `ZUBAT`, 24 `GOLBAT`, 22 `GRIMER` | `TrainerGruntM10` | `RADIO_TOWER_4F` (5,6) | -| `RICH` | `SCIENTIST` | 5 | `ScientistGroup` (5) "RICH", `TRAINERTYPE_MOVES`: 30 `PORYGON` (Conversion, Conversion2, Recover, Tri Attack) | `TrainerScientistRich` | `RADIO_TOWER_4F` (4,2) | -| `EXECUTIVEM_3` | `EXECUTIVEM` | 3 | `ExecutiveMGroup` (3), `TRAINERTYPE_MOVES`: 30 `KOFFING` x3 (Tackle/Selfdestruct/Sludge/Smokescreen), 32 `WEEZING` (Tackle/Explosion/Sludge/Smokescreen), 30 `KOFFING`, 30 `KOFFING` (Tackle/Smog/Sludge/Smokescreen) | `FakeDirectorScript` (`loadtrainer`) | `RADIO_TOWER_5F` coord (0,3) | -| `RIVAL1_4_CHIKORITA` | `RIVAL1` | 10 | `Rival1Group` (10): 30 `GOLBAT`, 28 `MAGNEMITE`, 30 `HAUNTER`, 32 `SNEASEL`, 32 `MEGANIUM` | `UndergroundRivalBattleScript` | switch room coord (19,4)/(19,5) | -| `RIVAL1_4_CYNDAQUIL` | `RIVAL1` | 11 | (11): same first four, 32 `QUILAVA` | as above | as above | -| `RIVAL1_4_TOTODILE` | `RIVAL1` | 12 | (12): same first four, 32 `FERALIGATR` | as above | as above | -| `GRUNTM_13` | `GRUNTM` | 13 | (13): 27 `RATTATA` | `TrainerGruntM13` | switch room (17,2) | -| `GRUNTM_11` | `GRUNTM` | 11 | (11): 23 `MUK`, 23 `KOFFING`, 25 `RATTATA` | `TrainerGruntM11` | switch room (11,2) | -| `GRUNTM_25` | `GRUNTM` | 25 | (25): 24 `KOFFING`, 24 `MUK` | `TrainerGruntM25` | switch room (3,2) | -| `EDDIE` | `BURGLAR` | 2 | `BurglarGroup` (2) "EDDIE", `TRAINERTYPE_MOVES`: 26 `GROWLITHE` (Roar/Ember/Leer/Take Down), 24 `KOFFING` (Tackle/Smog/Sludge/Smokescreen) | `TrainerBurglarEddie` | switch room (4,8) | -| `DUNCAN` | `BURGLAR` | 1 | `BurglarGroup` (1) "DUNCAN": 23 `KOFFING`, 25 `MAGMAR`, 23 `KOFFING` | `TrainerBurglarDuncan` | switch room (9,12) | -| `GRUNTF_3` | `GRUNTF` | 3 | (3): 25 `GLOOM`, 25 `GLOOM` | `TrainerGruntF3` | switch room (19,12) | -| `GRUNTM_14` | `GRUNTM` | 14 | (14): 24 `RATICATE`, 24 `GOLBAT` | `TrainerGruntM14` | warehouse (8,15) | -| `GRUNTM_15` | `GRUNTM` | 15 | (15): 26 `GRIMER`, 23 `WEEZING` | `TrainerGruntM15` | warehouse (14,3) | -| `GRUNTM_24` | `GRUNTM` | 24 | (24): 25 `KOFFING`, 25 `KOFFING` | `TrainerGruntM24` | warehouse (9,8) | -| `GRUNTM_9` | `GRUNTM` | 9 | (9): 24 `RATICATE`, 26 `KOFFING` | `TrainerGruntM9` | `RADIO_TOWER_3F` (16,6), behind the shutter | -| `GRUNTF_4` | `GRUNTF` | 4 | (4): 21 `EKANS`, 23 `ODDISH`, 21 `EKANS`, 24 `GLOOM` | `TrainerGruntF4` | `RADIO_TOWER_4F` (12,4) | -| `EXECUTIVEM_2` | `EXECUTIVEM` | 2 | `ExecutiveMGroup` (2), `TRAINERTYPE_MOVES`: 36 `GOLBAT` (Leech Life/Bite/Confuse Ray/Wing Attack) | `TrainerExecutivem2` | `RADIO_TOWER_4F` (14,1) | -| `EXECUTIVEF_1` | `EXECUTIVEF` | 1 | `ExecutiveFGroup` (1), `TRAINERTYPE_MOVES`: 32 `ARBOK` (Wrap/Poison Sting/Bite/Glare), 32 `VILEPLUME` (Absorb/Sweet Scent/Sleep Powder/Acid), 32 `MURKROW` (Peck/Pursuit/Haze/Night Shade) | `TrainerExecutivef1` | `RADIO_TOWER_5F` (17,2) | -| `EXECUTIVEM_1` | `EXECUTIVEM` | 1 | `ExecutiveMGroup` (1), `TRAINERTYPE_MOVES`: 33 `HOUNDOUR` (Ember/Roar/Bite/Faint Attack), 33 `KOFFING` (Tackle/Sludge/Smokescreen/Haze), 35 `HOUNDOOM` (Ember/Smog/Bite/Faint Attack) | `RadioTower5FRocketBossScript` (`loadtrainer`) | `RADIO_TOWER_5F` coord (16,5) | - -Also present but optional in this stretch (`MAP_GOLDENROD_UNDERGROUND`, all -`event flag -1` so always visible): `TrainerSupernerdEric` (`SUPER_NERD`, -`ERIC`), `TrainerSupernerdTeru` (`SUPER_NERD`, `TERU`), -`TrainerPokemaniacIssac`, `TrainerPokemaniacDonald`. - -Neither Executive fight nor the fake director fight has a loss text -(`winlosstext ..., 0`), so a whiteout there is a normal blackout, not a scripted -loss. The rival fight DOES have one (`UndergroundRivalLossText`). - -**Wild encounters** - -None. Grepping `data/wild/johto_grass.asm`, `johto_water.asm`, `fish.asm` and -`treemons.asm` for `RADIO_TOWER` or `GOLDENROD` returns nothing: every map in -this section is `INDOOR` or `DUNGEON` with no wild table. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| The whole arc does not exist yet | `maps/*Gym.asm` `<Gym>ActivateRockets` -> `engine/events/std_scripts.asm:255 RadioTowerRocketsScript` (`40:41dc`) | `readvar VAR_BADGES` equals 7 immediately after a badge is set | Earn the 7th badge (normally Pryce's Glacierbadge). That script sets `ENGINE_ROCKETS_IN_RADIO_TOWER`, clears `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` (spawns every Grunt) and queues `SPECIALCALL_WEIRDBROADCAST`. `ifequal 6` instead runs `GoldenrodRocketsScript`, which only clears `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER`. | -| Elm's call never rings | `data/phone/special_calls.asm` row 4 = `SpecialCallOnlyWhenOutside` | Player must be on an outdoor map | Walk outside; the call is cosmetic, not a gate on anything | -| 2F -> 3F stairs (pre-takeover only) | `maps/RadioTower2F.asm` object `RADIOTOWER2F_BLACK_BELT1` at (0,1), masked by `EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS` | Flag must be SET for him to vanish (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm`) | `RadioTowerRocketsScript` sets it | -| 3F -> 4F east stairs (warp 3 at (17,0)) and `TrainerGruntM9` | `maps/RadioTower3F.asm:127 CardKeySlotScript` (`43:5c91`) + `RadioTower3FCardKeyShutterCallback` (`43:5be1`) | `checkitem CARD_KEY`, and you must press A **facing up** on the tile below (14,2) because the bg event is `BGEVENT_UP` | `EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER`, which rewrites blocks (14,2) and (14,4) | -| 5F Rocket boss unreachable on the first visit | `maps/RadioTower5F.asm` scene: `setscene SCENE_RADIOTOWER5F_ROCKET_BOSS` only at the end of `FakeDirectorScript` | Beat `EXECUTIVEM_3` | The (16,5) coord event only fires on scene 1 | -| Underground locked door at (18,6) | `maps/GoldenrodUnderground.asm:372 BasementDoorScript` (`46:429e`) + `GoldenrodUndergroundCheckBasementKeyCallback` | `checkitem BASEMENT_KEY` | `EVENT_USED_BASEMENT_KEY`; `changeblock 18, 6, $2e` | -| The eleven shutters in the switch room | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm:346 ..._UpdateDoors` (`46:4c57`), driven by `wUndergroundSwitchPositions` (`01:d6a8`) | No item. A pure state puzzle over a single byte | See the position table in section 2. Resets to 0 on any `MAPCALLBACK_NEWMAP` of `GOLDENROD_UNDERGROUND` or `GOLDENROD_UNDERGROUND_WAREHOUSE`; the emergency switch at (20,11) forces the "everything useful open" layout | -| Rival ambush | `coord_event 19, 4 / 19, 5` on scene 0 | Walking onto either cell | Scene set to `..._NOOP` at the end of `UndergroundRivalScene1/2` | -| Dept store B1F crate layout | `GoldenRodDeptStoreB1FClearBoxesCallback` | `EVENT_RECEIVED_CARD_KEY` opens block (16,4); layout events pick one of three crate gaps | Card Key from the Director; the elevator only rotates the layout once `EVENT_GOLDENROD_UNDERGROUND_WAREHOUSE_BLOCKED_OFF` is cleared by walking into B1F | - -No HM field move is required anywhere in this section - no Cut, Surf, Strength, -Whirlpool, Waterfall or Flash check appears in any of these maps. - ---- - -## 4. Bot checklist - -Coordinates are asm cell coordinates. "talk" means face the target and press A. - -Pass 1 - Radio Tower up to the Basement Key: - -1. `MAP_GOLDENROD_CITY`: precondition `ENGINE_ROCKETS_IN_RADIO_TOWER` set (7 - badges). Walk to (5,15), warp 12 -> `RADIO_TOWER_1F` (2,7). -2. `RADIO_TOWER_1F`: walk toward (15,0). `TrainerGruntM3` at (14,1) sights you - (range 3, facing down). Battle `GRUNTM_3`. Post: `EVENT_BEAT_ROCKET_GRUNTM_3`. -3. Warp 3 at (15,0) -> `RADIO_TOWER_2F` (15,0). -4. `RADIO_TOWER_2F`: walk left. Fight in this order: `TrainerGruntF2` (10,5), - `TrainerGruntM5` (8,4), `TrainerGruntM6` (4,1), `TrainerGruntM4` (1,4). - Precondition for all: `EVENT_RADIO_TOWER_ROCKET_TAKEOVER` clear. -5. Warp 1 at (0,0) -> `RADIO_TOWER_3F` (0,0). -6. `RADIO_TOWER_3F`: `TrainerGruntM8` (6,2), `TrainerGruntM7` (5,1), - `TrainerScientistMarc` (9,6, sight 5). Do NOT try (14,2) yet - no Card Key. -7. Warp 2 at (7,0) -> `RADIO_TOWER_4F` (9,0). -8. `RADIO_TOWER_4F`: `TrainerGruntM10` (5,6), `TrainerScientistRich` (4,2). -9. Warp 1 at (0,0) -> `RADIO_TOWER_5F` (0,0). Walk DOWN to (0,3): coord event - fires `FakeDirectorScript` on scene 0. Battle `EXECUTIVEM_3` (6 mons, four of - them know Selfdestruct/Explosion). Post: `BASEMENT_KEY` in bag, - `EVENT_BEAT_ROCKET_EXECUTIVEM_3`, 5F scene = 1. -10. Retrace: 5F warp 1 -> 4F warp 1 -> 4F warp 2 (9,0) -> 3F warp 2 -> 3F warp 1 - (0,0) -> 2F warp 1 -> 2F warp 2 (15,0) -> 1F warp 3 -> 1F warps 1/2 (2,7) -> - `GOLDENROD_CITY` (5,15). - -Pass 2 - Underground to the Card Key: - -11. `GOLDENROD_CITY`: heal at warp 7 (15,27) if wanted. Walk to (9,5), warp 14 -> - `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` (20,29). -12. Walk to (21,25), warp 7 -> `GOLDENROD_UNDERGROUND` (3,2). -13. Optional: haircut brothers at (7,14)/(7,15) (day-gated); Coin Case ball at - (7,25) if not taken. -14. Walk to the tile below/beside (18,6) and A-press it: `BasementDoorScript` - with `BASEMENT_KEY` -> `EVENT_USED_BASEMENT_KEY`. Then step onto (18,6) -> - warp 3 -> `GOLDENROD_UNDERGROUND` (21,31). -15. Walk to (22,27), warp 6 -> `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` - (23,3). SAVE HERE (the walkthrough's advice; the rival battle is losable). -16. Walk west to (19,4) or (19,5): `UndergroundRivalScene1/2` fires on scene 0. - Battle `RIVAL1`, member `RIVAL1_4_CHIKORITA` / `_CYNDAQUIL` / `_TOTODILE` - picked by which starter you took. Post: `EVENT_RIVAL_GOLDENROD_UNDERGROUND`, - scene -> 1. (Side effect: `EVENT_RIVAL_BURNED_TOWER` + Burned Tower scene.) -17. Fight `TrainerGruntM13` (17,2), `TrainerGruntM11` (11,2), `TrainerGruntM25` - (3,2) - west along the top corridor. -18. Switch byte starts at 0. A-press `Switch1Script` at (16,1) -> ON (byte 1), - then `Switch2Script` at (10,1) -> ON (byte 3), then `Switch3Script` at (2,1) - -> ON (byte 6). Doors 3, 6, 8, 9, 11 now open. -19. Go south through door 8 at (6,6)/(6,8) to `TrainerBurglarEddie` at (4,8). - Battle `BURGLAR EDDIE`. -20. A-press Switch3 (2,1) -> OFF (byte 3), then Switch2 (10,1) -> OFF (byte 1). - Doors 1, 3, 7, 10 open. Take the `FULL_HEAL` ball at (14,9). -21. Leave and re-enter the map so `..._ResetSwitchesCallback` zeroes everything - (e.g. warp 1 at (23,3) into `GOLDENROD_UNDERGROUND` and back through (22,27)). -22. Now A-press Switch3 (2,1) ON (byte 3), Switch2 (10,1) ON (byte 5), Switch1 - (16,1) ON (byte 6). Doors 3, 5, 6, 8, 9, 11 open - door 5 at (10,10) is the - new one. -23. Go south through door 5 to `TrainerBurglarDuncan` (9,12), then east to - `TrainerGruntF3` (19,12). Optionally the `SMOKE_BALL` ball at (1,12) and the - two hidden items at (8,9) and (1,8). -24. Warps 2/3 at (22,10)/(23,10) -> `GOLDENROD_UNDERGROUND_WAREHOUSE` (2,12). -25. Warehouse: `TrainerGruntM14` (8,15), `MAX_ETHER` ball at (18,15), - `TrainerGruntM15` (14,3). Optional side trip: warp 3 at (17,2) -> - `GOLDENROD_DEPT_STORE_B1F` (17,2), take `AMULET_COIN` at (14,2), warp back. -26. `TrainerGruntM24` (9,8). Take `TM_SLEEP_TALK` (TM35) at (13,9). Talk to the - Director at (12,8): `verbosegiveitem CARD_KEY`, post - `EVENT_RECEIVED_CARD_KEY` + `EVENT_GOLDENROD_DEPT_STORE_B1F_LAYOUT_1`. -27. Exit back through warps 1/2 (2,12)/(3,12). If a shutter blocks the way out, - A-press `EmergencySwitchScript` at (20,11) (forces doors 3,5,6,8,9,11 open). - Then warp 1 at (23,3) or warps 8/9 at (20,29)/(21,29) to leave. - -Pass 3 - retake the tower: - -28. `GOLDENROD_CITY` -> `RADIO_TOWER_1F` (warp 12) -> 2F -> 3F as in steps 2-5. -29. `RADIO_TOWER_3F`: stand at (14,3) FACING UP and press A on (14,2) - (`BGEVENT_UP`). `CardKeySlotScript` -> `EVENT_USED_THE_CARD_KEY_IN_THE_RADIO_TOWER`, - blocks (14,2)/(14,4) rewritten. -30. Walk east/south to `TrainerGruntM9` at (16,6). Battle `GRUNTM_9`. -31. Warp 3 at (17,0) -> `RADIO_TOWER_4F` (17,0). Battle `TrainerGruntF4` (12,4), - then `TrainerExecutivem2` (14,1) who guards warp 3. -32. Warp 3 at (12,0) -> `RADIO_TOWER_5F` (12,0). Battle `TrainerExecutivef1` - (17,2). -33. Walk to (16,5): coord event on scene 1 fires `RadioTower5FRocketBossScript`. - The script moves the PLAYER two cells left first. Battle `EXECUTIVEM_1`. -34. Post-conditions: `EVENT_CLEARED_RADIO_TOWER`, `ENGINE_ROCKETS_IN_RADIO_TOWER` - cleared, all Rocket objects masked, civilians restored, Blackthorn gym - blocker swapped, `RAINBOW_WING` (Gold) or `SILVER_WING` (Silver) received, 5F - scene -> 2. -35. Optional pickups now unlocked: `TM_SUNNY_DAY` from the 3F cooltrainer at - (11,3); `PINK_BOW` from DJ Mary at (14,6) on 4F. - ---- - -## 5. Port coverage - -The Gen 2 side of this repo is data-driven: maps, warps, coord events, bg events, -object events and scripts all come out of `src/import/RomExtractorGen2.lua` and -run through `src/script/gen2/Vm.lua`, so "implemented" below means the generic -machinery this section needs exists, not that anyone has walked the section. - -| Beat | Port file | Status | -|---|---|---| -| Map/warp/object/bg/coord tables for all nine maps | `src/import/RomExtractorGen2.lua` (`def_*` readers), `src/world/gen2/Map.lua` | implemented (generic extractor; not spot-checked against these maps) | -| Coord-event trip-wires gated by scene id | `src/world/gen2/World.lua:5011` (`coordEvents` scan against `World:scene()`), `:5025` scene scripts | implemented | -| `setscene` / `checkmapscene` / `setmapscene`, scene persistence in the save | `src/script/gen2/Vm.lua:274`, `src/world/gen2/World.lua:740-748`, `src/core/gen2/Save.lua` `mapScenes` | implemented | -| Trainer sight range + approach + `startbattle` | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua` `SEEN_BY_TRAINER_SCRIPT`, `src/script/gen2/Vm.lua:806/817` | implemented | -| `loadtrainer` for the three scripted Executive/rival fights | `src/script/gen2/Vm.lua:806` | implemented | -| Rival starter branch (`checkevent EVENT_GOT_*_FROM_ELM`) | `src/script/gen2/Vm.lua` `checkevent`/`iftrue` | implemented | -| Switch puzzle byte (`readmem`/`addval`/`writemem` on `wUndergroundSwitchPositions`) | `src/script/gen2/Vm.lua:706-740` (sparse WRAM store, explicitly documented for this map), persisted as `scriptMem` in `src/core/gen2/Save.lua:186` | implemented | -| Switch reset on map entry (`MAPCALLBACK_NEWMAP`) | `src/world/gen2/World.lua:5659` + assertion driver `tests/drivers/gold_map_callbacks.lua:59` which loads `GOLDENROD_UNDERGROUND` for real and checks all 15 events clear | implemented and covered by a driver | -| `changeblock` + `refreshmap` (shutters, basement door, dept store crates) | `src/script/gen2/Vm.lua:1002`, `:887`; `MAPCALLBACK_TILES` at `src/world/gen2/World.lua:5664` | implemented | -| `checkitem BASEMENT_KEY` / `checkitem CARD_KEY` | `src/script/gen2/Vm.lua:523` | implemented | -| **Card Key slot (`bg_event ... BGEVENT_UP`)** | `src/world/gen2/World.lua:5147` `World:bgEventAt` matches only `(ev.kind or 0) == 0`, i.e. `BGEVENT_READ` | **missing** - the directional bg-event arms (`BGEVENT_UP/DOWN/RIGHT/LEFT`, and `BGEVENT_IFSET`/`IFNOTSET`/`COPY`) are not dispatched, so `CardKeySlotScript` can never fire and the 3F shutter cannot be opened. This blocks pass 3 entirely. | -| **Ground item balls (`OBJECTTYPE_ITEMBALL`)** | extracted as `obj.itemball` (`src/import/RomExtractorGen2.lua:2968`) but nothing consumes it: `World:interact` (`src/world/gen2/World.lua:5257`) has arms for trainers, strength boulders, `scriptKey` NPCs, `BGEVENT_READ` and `BGEVENT_ITEM` only | **missing** - Smoke Ball, Full Heal, Max Ether, TM35 Sleep Talk and Amulet Coin are all unobtainable. `src/script/gen2/CallAsm.lua:547` stubs `TryReceiveItem` on the assumption this path exists. | -| Hidden items (`BGEVENT_ITEM`: Max Potion, Revive, and the three in `GOLDENROD_UNDERGROUND`) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5285` | implemented | -| `verbosegiveitem` (Basement Key, Card Key, wings, TM Sunny Day, Pink Bow) | `src/script/gen2/Vm.lua:490` | implemented | -| `checkver` Gold/Silver split on the wing | `src/script/gen2/Vm.lua:774` | implemented | -| `disappear` / `appear` / `moveobject` / `applymovement` / `turnobject` / `showemote` for the 5F director cutscene | `src/script/gen2/Vm.lua:297-380` | implemented | -| `special FadeOutToBlack` / `FadeInFromBlack` / `ReloadSpritesNoPalettes` / `PlayMapMusic` / `FadeOutMusic` | `src/script/gen2/Specials.lua:999-1066` | implemented | -| Object masking by event flag (every Rocket in this section) | `src/world/gen2/Npc.lua` + the extracted `eventFlag` field | implemented (semantics: SET = hidden, per `CheckObjectFlag`) | -| Elm's `SPECIALCALL_WEIRDBROADCAST` call | `src/core/gen2/Phone.lua:392` (`[4] = { name = "SPECIALCALL_WEIRDBROADCAST", condition = "outside" }`), `src/script/gen2/Vm.lua:1364` `specialphonecall` | implemented | -| Radio programming change while `ENGINE_ROCKETS_IN_RADIO_TOWER` is set | `src/ui/gen2/Pokegear.lua:348`, `:1249` | implemented | -| Dept store elevator (`elevator` opcode + crate layout rotation) | `src/script/gen2/Vm.lua:1302`, `src/ui/gen2/ElevatorMenu.lua` | implemented | -| Haircut brothers / bargain + bitter merchants (day gating) | `src/script/gen2/Specials.lua` (`OlderHaircutBrother` / `YoungerHaircutBrother`), `src/ui/gen2/MartMenu.lua:170` (`MARTTYPE_BITTER`) | implemented | -| A scripted driver that walks this section | none (`tests/drivers/gold_*.lua` has no radio tower / underground runner) | missing | - ---- - -## 6. Unresolved / verify by hand - -1. **"He gives you a Clear Bell."** The asm does not give a Clear Bell here. - `RadioTower5FRocketBossScript` (`maps/RadioTower5F.asm:122-139`) branches on - `checkver`: Silver gets `SILVER_WING`, Gold gets `RAINBOW_WING`. The Clear - Bell is a Crystal item and does not appear in this script. Treat the - walkthrough line as wrong for Gold/Silver. -2. **Switch ordering explanation.** The walkthrough says the shutters remember - the ORDER you flipped the switches in. `wUndergroundSwitchPositions` is a - single byte that switches 1/2/3 add 1/2/3 to, so 1-2-3 and 3-2-1 both end at - 6. The observable difference comes from which doors the INTERMEDIATE positions - left untouched (position 3 vs position 5 differ on door 5 at (10,10)). The - end result the walkthrough describes is correct; the stated reason is not. - The derivation above was done by hand from the `.PositionN` arms - worth - re-checking on hardware or an emulator before a bot depends on it. -3. **Party orderings.** The walkthrough lists `GRUNTF_4` as - Ekans/Ekans/Gloom/Oddish and `EXECUTIVEF_1` as Arbok/Murkrow/Vileplume. The - asm orders are `21 EKANS, 23 ODDISH, 21 EKANS, 24 GLOOM` and - `32 ARBOK, 32 VILEPLUME, 32 MURKROW`. Same mons, different send-out order; a - bot that hard-codes lead expectations should use the asm order. -4. **"the northwesternmost building"** - resolved to `warp_event 9, 5, - GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES, 8` and the adjacent - `bg_event 8, 6 GoldenrodCityUndergroundSignNorth`, but the walkthrough never - names it, so this is an inference from the map geometry rather than a quoted - label. -5. **EXP and money figures** ("595 EXP", "960G", etc.) were not verified. Money - is `base money x level` from the trainer class table, which was not opened; - EXP depends on the player's party. Nothing in this document depends on them. -6. **`EVENT_TEAM_ROCKET_DISBANDED` is set only on the Gold branch** of - `RadioTower5FRocketBossScript` (line 130), not on the Silver branch. Its only - other consumers are `maps/PewterCity.asm:51` (also `setevent`) and the - `TIN_TOWER_1F` sage object mask at `maps/TinTower1F.asm:56`. This looks - intentional (Gold gets the Rainbow Wing and therefore Ho-Oh access), but it is - an asymmetry a port could easily get wrong, so it is flagged rather than - asserted. -7. **`verbosegiveitem` with no `iffalse`** in `FakeDirectorScript` (Basement Key) - and `GoldenrodUndergroundWarehouseDirectorScript` (Card Key). If the Key - Items pocket were full the scripts would continue as if the item had been - given. Gen 2's Key Items pocket is large enough that this is presumably - unreachable, but the guard genuinely is not there. -8. **Radio Tower 3F `TrainerGruntM9`** reacts with "Why did the shutter open?" and - his after-battle text mentions the Underground, which confirms he is meant for - the post-Card-Key pass; but nothing in the asm prevents reaching (16,6) some - other way if the block edit were bypassed. Not verified against the `.blk` - collision data. diff --git a/docs/gold-walkthrough/asm-walk/section-13-ice-path-and-blackthorn-city-gym.md b/docs/gold-walkthrough/asm-walk/section-13-ice-path-and-blackthorn-city-gym.md deleted file mode 100644 index b322b69e..00000000 --- a/docs/gold-walkthrough/asm-walk/section-13-ice-path-and-blackthorn-city-gym.md +++ /dev/null @@ -1,1255 +0,0 @@ -# Section 13 - Ice Path and Blackthorn City Gym - -Source: `../section-13-ice-path-and-blackthorn-city-gym.txt` -(the FAQ's own internal heading for this block is `---- 19 > Ice Path and Blackthorn City Gym ----`) - -Maps covered: `MAP_ROUTE_44`, `MAP_ICE_PATH_1F`, `MAP_ICE_PATH_B1F`, -`MAP_ICE_PATH_B2F_MAHOGANY_SIDE`, `MAP_ICE_PATH_B3F`, -`MAP_ICE_PATH_B2F_BLACKTHORN_SIDE`, `MAP_BLACKTHORN_CITY`, -`MAP_BLACKTHORN_POKECENTER_1F`, `MAP_BLACKTHORN_MART`, `MAP_BLACKTHORN_EMYS_HOUSE`, -`MAP_MOVE_DELETERS_HOUSE`, `MAP_BLACKTHORN_DRAGON_SPEECH_HOUSE`, -`MAP_BLACKTHORN_GYM_1F`, `MAP_BLACKTHORN_GYM_2F`, `MAP_DRAGONS_DEN_1F`, -`MAP_DRAGONS_DEN_B1F` - -Badges / key milestones in this section: - -- `EVENT_GOT_HM07_WATERFALL` (HM07 on `MAP_ICE_PATH_1F`) -- `EVENT_BEAT_CLAIR` (the battle is won, but **no badge yet**) -- `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG` -> `ENGINE_RISINGBADGE` + `EVENT_GOT_TM24_DRAGONBREATH` -- `specialphonecall SPECIALCALL_MASTERBALL` queued (Elm's Master Ball call, redeemed next section) - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_MAHOGANY_TOWN` | (previous section) | Fly | east map connection | "fly back to Mahogany Town, heal, head east onto Route 44" | -| 1 | `MAP_ROUTE_44` | `maps/Route44.asm` | west connection from `MAHOGANY_TOWN` | warp 1 at (56, 7) | six trainers, Burnt Berry tree, two item balls | -| 2 | `MAP_ICE_PATH_1F` | `maps/IcePath1F.asm` | warp 1 (4, 19) from Route 44 | warp 3 (37, 5) | first ice maze, HM07 Waterfall | -| 3 | `MAP_ICE_PATH_B1F` | `maps/IcePathB1F.asm` | warp 1 (3, 15) | warp 2 (17, 3) | Strength boulder-into-hole puzzle | -| 4 | `MAP_ICE_PATH_B2F_MAHOGANY_SIDE` | `maps/IcePathB2FMahoganySide.asm` | warp 1 (17, 1), or drop through holes | warp 2 (9, 11) | Full Heal, Max Potion, hidden Carbos | -| 5 | `MAP_ICE_PATH_B3F` | `maps/IcePathB3F.asm` | warp 1 (3, 5) | warp 2 (15, 5) | Rock Smash the rock, Nevermeltice | -| 6 | `MAP_ICE_PATH_B2F_BLACKTHORN_SIDE` | `maps/IcePathB2FBlackthornSide.asm` | warp 2 (3, 3) | warp 1 (3, 15) | TM44 Rest, hidden Ice Heal | -| 7 | `MAP_ICE_PATH_B1F` (south region) | `maps/IcePathB1F.asm` | warp 8 (11, 27) | warp 7 (5, 25) | Iron at (5, 35), hidden Max Potion | -| 8 | `MAP_ICE_PATH_1F` (south region) | `maps/IcePath1F.asm` | warp 4 (37, 13) | warp 2 (36, 27) | PP Up at (32, 23), then out | -| 9 | `MAP_BLACKTHORN_CITY` | `maps/BlackthornCity.asm` | warp 7 (36, 9) | see below | heal, restock, Move Deleter, Emy's trade | -| 10 | `MAP_BLACKTHORN_POKECENTER_1F` | `maps/BlackthornPokecenter1F.asm` | city warp 5 (21, 29) | warp 1/2 | heal | -| 11 | `MAP_BLACKTHORN_MART` | `maps/BlackthornMart.asm` | city warp 4 (15, 29) | warp 1/2 | restock Ultra Balls | -| 12 | `MAP_BLACKTHORN_EMYS_HOUSE` | `maps/BlackthornEmysHouse.asm` | city warp 3 (29, 23) | warp 1/2 | Dragonair <-> Rhydon trade ("northeast house") | -| 13 | `MAP_MOVE_DELETERS_HOUSE` | `maps/MoveDeletersHouse.asm` | city warp 6 (9, 31) | warp 1/2 | delete HM moves ("house in the south") | -| 14 | `MAP_BLACKTHORN_GYM_1F` | `maps/BlackthornGym1F.asm` | city warp 1 (18, 11) | warp 1/2 (4/5, 17) | Paul, Mike, Lola, Clair | -| 15 | `MAP_BLACKTHORN_GYM_2F` | `maps/BlackthornGym2F.asm` | 1F warp 3 (1, 7) / warp 4 (7, 9) | holes and stairs | Cody, Fran, six-boulder puzzle | -| 16 | `MAP_DRAGONS_DEN_1F` | `maps/DragonsDen1F.asm` | city warp 8 (20, 1) | warp 3 (5, 15) | corridor + internal ladder pair | -| 17 | `MAP_DRAGONS_DEN_B1F` | `maps/DragonsDenB1F.asm` | warp 1 (20, 3) | same warp | Surf + Whirlpool to the Dragon Fang at (35, 16); Clair hands over `ENGINE_RISINGBADGE` | - -Not covered here (next section): the walkthrough's last paragraph sends the player -back to `MAP_ELMS_LAB` for the Master Ball (`ElmGiveMasterBallScript`, -`maps/ElmsLab.asm`) and on toward the Pokemon League. The video-link header also -names Dark Cave, but the prose never enters `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE`. - -`MAP_BLACKTHORN_DRAGON_SPEECH_HOUSE` (city warp 2, (13, 21)) is on the map but the -walkthrough never enters it; it is documented below for completeness. - -## 2. Maps - -### MAP_ROUTE_44 - -- Script: `maps/Route44.asm` (`Route44_MapEvents` = `4d:4dd9` in `pokegold.sym`) -- Blocks: `maps/Route44.blk` -- Header: `data/maps/maps.asm:73` -> `TILESET_JOHTO`, `ROUTE`, `LANDMARK_ROUTE_44`, - `MUSIC_LAKE_OF_RAGE`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_REMORAID` -- Dimensions: `constants/map_constants.asm:61` -> `map_const ROUTE_44, 30, 9` - (so x in 0..59, y in 0..17) -- Connections: `data/maps/attributes.asm:239` -> west `MAHOGANY_TOWN` (offset 0), - east `BLACKTHORN_CITY` (offset -9) -- Scene scripts: none (`def_scene_scripts` empty). Callbacks: none. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 56 | 7 | `ICE_PATH_1F` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 53 | 7 | `BGEVENT_READ` | `Route44Sign1` ("ROUTE 44 / ICE PATH AHEAD") | -| 6 | 10 | `BGEVENT_READ` | `Route44Sign2` ("MAHOGANY TOWN - BLACKTHORN CITY") | -| 32 | 9 | `BGEVENT_ITEM` | `Route44HiddenElixer` -> `hiddenitem ELIXER, EVENT_ROUTE_44_HIDDEN_ELIXER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE44_FISHER1` | `SPRITE_FISHER` | 42 | 5 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerFisherWilton1` | -1 | -| `ROUTE44_FISHER2` | `SPRITE_FISHER` | 19 | 13 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerFisherEdgar` | -1 | -| `ROUTE44_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 10 | 9 | `SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPsychicPhil` | -1 | -| `ROUTE44_SUPER_NERD` | `SPRITE_SUPER_NERD` | 35 | 2 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerPokemaniacZach` | -1 | -| `ROUTE44_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 50 | 7 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBirdKeeperVance1` | -1 | -| `ROUTE44_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 41 | 15 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerCooltrainermAllen` | -1 | -| `ROUTE44_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 31 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerCooltrainerfCybil` | -1 | -| `ROUTE44_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 9 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | 0 | `Route44FruitTree` | -1 | -| `ROUTE44_POKE_BALL1` | `SPRITE_POKE_BALL` | 30 | 8 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `Route44MaxRevive` | `EVENT_ROUTE_44_MAX_REVIVE` | -| `ROUTE44_POKE_BALL2` | `SPRITE_POKE_BALL` | 43 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `Route44UltraBall` | `EVENT_ROUTE_44_ULTRA_BALL` | - -**Scripts of interest** - -- `TrainerBirdKeeperVance1` / `.Script` - after the fight this is the phone-number - script. `checkevent EVENT_VANCE_READY_FOR_REMATCH` -> rematch branch; - `checkcellnum PHONE_BIRDKEEPER_VANCE` -> already registered; otherwise sets - `EVENT_VANCE_ASKED_FOR_PHONE_NUMBER` and runs - `askforphonenumber PHONE_BIRDKEEPER_VANCE`. The rematch branch picks - `VANCE2` if `EVENT_BEAT_ELITE_FOUR` and `VANCE3` if - `EVENT_RESTORED_POWER_TO_KANTO`, and clears `EVENT_VANCE_READY_FOR_REMATCH`. -- `TrainerFisherWilton1` / `.Script` - same shape with `PHONE_FISHER_WILTON`, - `EVENT_WILTON_ASKED_FOR_PHONE_NUMBER`, `EVENT_WILTON_READY_FOR_REMATCH`, - and `WILTON2` / `WILTON3` for the later fights. `FisherWiltonHugePoliwagText` - is the "you made me lose a POLIWAG" line the walkthrough quotes. -- `Route44FruitTree` -> `fruittree FRUITTREE_ROUTE_44`. `FRUITTREE_ROUTE_44` is - index 10 (`constants/script_constants.asm:222`) and - `data/items/fruit_trees.asm:19` gives `BURNT_BERRY`. -- `Route44MaxRevive` -> `itemball MAX_REVIVE`, `Route44UltraBall` -> `itemball ULTRA_BALL`. -- The four one-line trainers (`TrainerPsychicPhil`, `TrainerFisherEdgar`, - `TrainerCooltrainerfCybil`, `TrainerPokemaniacZach`, `TrainerCooltrainermAllen`) - are `endifjustbattled / opentext / writetext ... / waitbutton / closetext / end` - with no flag side effects beyond the `trainer` macro's own beaten flag. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BIRD_KEEPER_VANCE` | `constants/event_flags.asm` | `trainer` macro row in `TrainerBirdKeeperVance1` | set once Vance is beaten | -| `EVENT_BEAT_PSYCHIC_PHIL` | same | `TrainerPsychicPhil` | " | -| `EVENT_BEAT_FISHER_WILTON` | same | `TrainerFisherWilton1` | " | -| `EVENT_BEAT_FISHER_EDGAR` | same | `TrainerFisherEdgar` | " | -| `EVENT_BEAT_COOLTRAINERF_CYBIL` | same | `TrainerCooltrainerfCybil` | " | -| `EVENT_BEAT_POKEMANIAC_ZACH` | same | `TrainerPokemaniacZach` | " | -| `EVENT_BEAT_COOLTRAINERM_ALLEN` | same | `TrainerCooltrainermAllen` | " | -| `EVENT_ROUTE_44_MAX_REVIVE` | `constants/event_flags.asm:1111` | itemball object | set -> ball gone | -| `EVENT_ROUTE_44_ULTRA_BALL` | `constants/event_flags.asm:1112` | itemball object | set -> ball gone | -| `EVENT_ROUTE_44_HIDDEN_ELIXER` | `constants/event_flags.asm:184` | `hiddenitem` bg event | one-shot hidden item | -| `EVENT_VANCE_ASKED_FOR_PHONE_NUMBER` / `EVENT_WILTON_ASKED_FOR_PHONE_NUMBER` | `constants/event_flags.asm` | trainer scripts | second-ask dialogue branch | -| `EVENT_VANCE_READY_FOR_REMATCH` / `EVENT_WILTON_READY_FOR_REMATCH` | same | phone system | rematch pending | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `BURNT_BERRY` | headbutt-free fruit tree at (9, 5) | `Route44FruitTree` / `FRUITTREE_ROUTE_44` | daily, not an event flag | -| `MAX_REVIVE` | item ball at (30, 8) | `Route44MaxRevive` | `EVENT_ROUTE_44_MAX_REVIVE` | -| `ULTRA_BALL` | item ball at (43, 2) | `Route44UltraBall` | `EVENT_ROUTE_44_ULTRA_BALL` | -| `ELIXER` | hidden, bg_event (32, 9) | `Route44HiddenElixer` | `EVENT_ROUTE_44_HIDDEN_ELIXER` | - -**Trainers** - -Party data all from `data/trainers/parties.asm`; classes from -`constants/trainer_constants.asm`. - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `PSYCHIC_T`, `PHIL` | `PsychicGroup` | PSYCHIC_T (8) | L24 `NATU` (Leer / Night Shade / Future Sight / Confuse Ray), **L26** `KADABRA` (Disable / Psybeam / Recover / Future Sight); `TRAINERTYPE_MOVES` | `TrainerPsychicPhil` | none | -| `FISHER`, `EDGAR` | `FisherGroup` | FISHER (11) | L25 `REMORAID` x2 (Lock-On / Psybeam / Aurora Beam / Bubblebeam); `TRAINERTYPE_MOVES` | `TrainerFisherEdgar` | none | -| `COOLTRAINERF`, `CYBIL` | `CooltrainerFGroup` | COOLTRAINERF (16) | L25 `BUTTERFREE` (Confusion / Sleep Powder / Whirlwind / Gust), L25 `BELLOSSOM` (Absorb / Stun Spore / Acid / SolarBeam); `TRAINERTYPE_MOVES` | `TrainerCooltrainerfCybil` | none | -| `COOLTRAINERM`, `ALLEN` | `CooltrainerMGroup` | COOLTRAINERM (19) | L27 `CHARMELEON` (Ember / Smokescreen / Rage / Scary Face); `TRAINERTYPE_MOVES` | `TrainerCooltrainermAllen` | none | -| `POKEMANIAC`, `ZACH` | `PokemaniacGroup` | POKEMANIAC (13) | L27 `RHYHORN`; `TRAINERTYPE_NORMAL` | `TrainerPokemaniacZach` | none | -| `FISHER`, `WILTON1` | `FisherGroup` | FISHER (10) | L23 `GOLDEEN`, L23 `GOLDEEN`, L25 `SEAKING`; `TRAINERTYPE_NORMAL` | `TrainerFisherWilton1` | `PHONE_FISHER_WILTON`; `WILTON2`/`WILTON3` post-game | -| `BIRD_KEEPER`, `VANCE1` | `BirdKeeperGroup` | BIRD_KEEPER (7) | L25 `PIDGEOTTO` x2; `TRAINERTYPE_NORMAL` | `TrainerBirdKeeperVance1` | `PHONE_BIRDKEEPER_VANCE`; `VANCE2`/`VANCE3` post-game | - -**Wild encounters** - -- Grass, `data/wild/johto_grass.asm:2208` `def_grass_wildmons ROUTE_44`, rates - 10/10/10 percent, and morn == day == nite: - L23 `TANGELA`, L22 `WEEPINBELL`, L22 `BELLSPROUT`, L24 `LICKITUNG`, - L24 `WEEPINBELL`, L26 `LICKITUNG`, L26 `LICKITUNG`. -- Water, `data/wild/johto_water.asm:197` `def_water_wildmons ROUTE_44`, 2 percent: - L25 `POLIWAG`, L20 `POLIWAG`, L25 `POLIWHIRL`. This is the Poliwag the - walkthrough's "Pokemon on Route 44" list means; it is Surf-only, not grass. -- Fishing group `FISHGROUP_REMORAID` (`data/maps/maps.asm:73`), - `data/wild/fish.asm:195` `.Remoraid_Old/.Remoraid_Good/.Remoraid_Super`: - Old = Magikarp/Magikarp/Poliwag L10; Good = Magikarp L20, Poliwag L20 x2, - `time_group 6` (Poliwag L20); Super = Poliwag L40, `time_group 7` - (Poliwag L40), Magikarp L40, `REMORAID` L40. - ---- - -### MAP_ICE_PATH_1F - -- Script: `maps/IcePath1F.asm` (`IcePath1F_MapEvents` = `46:613f`) -- Blocks: `maps/IcePath1F.blk` -- Header: `data/maps/maps.asm:131` -> `TILESET_ICE_PATH`, `CAVE`, - `LANDMARK_ICE_PATH`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_NITE`, - `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:118` -> `map_const ICE_PATH_1F, 20, 18` -- Attributes: `data/maps/attributes.asm:450`, no connections -- Scene scripts and callbacks: both empty - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 19 | `ROUTE_44` | 1 | -| 2 | 36 | 27 | `BLACKTHORN_CITY` | 7 | -| 3 | 37 | 5 | `ICE_PATH_B1F` | 1 | -| 4 | 37 | 13 | `ICE_PATH_B1F` | 7 | - -**Coord events / BG events** - -None. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ICEPATH1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 31 | 7 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePath1FHMWaterfall` | `EVENT_GOT_HM07_WATERFALL` | -| `ICEPATH1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 32 | 23 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePath1FPPUp` | `EVENT_ICE_PATH_1F_PP_UP` | - -**Scripts of interest** - -- `IcePath1FHMWaterfall` -> `itemball HM_WATERFALL`. Nothing gates it beyond - reaching the tile; the badge check that makes the move usable is separate - (see section 3). -- `IcePath1FPPUp` -> `itemball PP_UP`. This ball is in the *southern* region of - 1F, reached on the way back up from B1F warp 7, not on the way in. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HM_WATERFALL` (HM07) | item ball at (31, 7) | `IcePath1FHMWaterfall` | `EVENT_GOT_HM07_WATERFALL` (`constants/event_flags.asm:1065`) | -| `PP_UP` | item ball at (32, 23) | `IcePath1FPPUp` | `EVENT_ICE_PATH_1F_PP_UP` (`:1066`) | - -**Wild encounters** - -`data/wild/johto_grass.asm:798` `def_grass_wildmons ICE_PATH_1F`, rates 2/2/2 percent. -Gold build (`IF DEF(_GOLD)`): - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L21 `SWINUB` | L21 `SWINUB` | L21 `SWINUB` | -| 2 | L22 `GOLBAT` | L22 `GOLBAT` | L22 `GOLBAT` | -| 3 | L22 `ZUBAT` | L22 `ZUBAT` | L22 `ZUBAT` | -| 4 | L23 `SWINUB` | L23 `SWINUB` | L23 `SWINUB` | -| 5 | L22 `ZUBAT` | L22 `JYNX` | L22 `ZUBAT` | -| 6 | L22 `JYNX` | L20 `JYNX` | L22 `JYNX` | -| 7 | L22 `JYNX` | L20 `JYNX` | L22 `JYNX` | - -Silver (`ELIF DEF(_SILVER)`) replaces slot 3 with L22 `DELIBIRD` in all three -time bands - that is the "Delibird (Silver only)" line in the walkthrough, and -it is a *slot swap*, not an extra entry. - ---- - -### MAP_ICE_PATH_B1F - -- Script: `maps/IcePathB1F.asm` (`IcePathB1F_MapEvents` = `46:61e7`, - `IcePathB1FSetUpStoneTableCallback` = `46:6178`) -- Blocks: `maps/IcePathB1F.blk` -- Header: `data/maps/maps.asm:132` -> `TILESET_ICE_PATH`, `CAVE`, - `LANDMARK_ICE_PATH`, `MUSIC_DARK_CAVE`, `TRUE`, `PALETTE_NITE`, `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:119` -> `map_const ICE_PATH_B1F, 10, 18` -- Callbacks: `callback MAPCALLBACK_CMDQUEUE, IcePathB1FSetUpStoneTableCallback` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | note | -|---|---|---|---|---|---| -| 1 | 3 | 15 | `ICE_PATH_1F` | 3 | up to the north 1F region | -| 2 | 17 | 3 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 1 | stairs down | -| 3 | 11 | 2 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 3 | hole | -| 4 | 4 | 7 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 4 | hole | -| 5 | 5 | 12 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 5 | hole | -| 6 | 12 | 13 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 6 | hole | -| 7 | 5 | 25 | `ICE_PATH_1F` | 4 | up to the south 1F region | -| 8 | 11 | 27 | `ICE_PATH_B2F_BLACKTHORN_SIDE` | 1 | | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 15 | 31 | `BGEVENT_ITEM` | `IcePathB1FHiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_ICE_PATH_B1F_HIDDEN_MAX_POTION` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ICEPATHB1F_BOULDER1` | `SPRITE_BOULDER` | 11 | 7 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `IcePathB1FBoulder` | `EVENT_BOULDER_IN_ICE_PATH_1` | -| `ICEPATHB1F_BOULDER2` | `SPRITE_BOULDER` | 7 | 8 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `IcePathB1FBoulder` | `EVENT_BOULDER_IN_ICE_PATH_2` | -| `ICEPATHB1F_BOULDER3` | `SPRITE_BOULDER` | 8 | 9 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `IcePathB1FBoulder` | `EVENT_BOULDER_IN_ICE_PATH_3` | -| `ICEPATHB1F_BOULDER4` | `SPRITE_BOULDER` | 17 | 7 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `IcePathB1FBoulder` | `EVENT_BOULDER_IN_ICE_PATH_4` | -| `ICEPATHB1F_POKE_BALL` | `SPRITE_POKE_BALL` | 5 | 35 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePathB1FIron` | `EVENT_ICE_PATH_B1F_IRON` | - -**Scripts of interest** - -- `IcePathB1FSetUpStoneTableCallback` writes a command queue with one - `cmdqueue CMDQUEUE_STONETABLE, .StoneTable` entry. `.StoneTable` is the - warp-id -> boulder-object -> script mapping: - - ``` - stonetable 3, ICEPATHB1F_BOULDER1, .Boulder1 - stonetable 4, ICEPATHB1F_BOULDER2, .Boulder2 - stonetable 5, ICEPATHB1F_BOULDER3, .Boulder3 - stonetable 6, ICEPATHB1F_BOULDER4, .Boulder4 - db -1 - ``` - - So boulder N must be pushed onto warp N+2 - i.e. boulder 1 onto the hole at - (11, 2), boulder 2 onto (4, 7), boulder 3 onto (5, 12), boulder 4 onto (12, 13). -- Each `.BoulderN` arm does `disappear ICEPATHB1F_BOULDERN` (which sets - `EVENT_BOULDER_IN_ICE_PATH_N`) and `clearevent EVENT_BOULDER_IN_ICE_PATH_NA` - (which makes the twin boulder object on `ICE_PATH_B2F_MAHOGANY_SIDE` - *appear*), then falls into `.FinishBoulder`: `pause 30`, - `scall .BoulderFallsThrough` (`playsound SFX_STRENGTH` + `earthquake 80`), - then `IcePathBoulderFellThroughText` ("The boulder fell / through."). -- `IcePathB1FBoulder` -> `jumpstd StrengthBoulderScript` (the "may be able to - push this with STRENGTH" prompt). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BOULDER_IN_ICE_PATH_1..4` | `constants/event_flags.asm:1195-1198` | `.BoulderN` `disappear` | set -> that B1F boulder is gone (fell) | -| `EVENT_BOULDER_IN_ICE_PATH_1A..4A` | `constants/event_flags.asm:1199-1202` | `.BoulderN` `clearevent`; **set at new game** by the init list in `engine/events/std_scripts.asm` | clear -> the fallen boulder is now visible on B2F Mahogany Side | -| `EVENT_ICE_PATH_B1F_IRON` | `:1067` | itemball object | | -| `EVENT_ICE_PATH_B1F_HIDDEN_MAX_POTION` | `:158` | hidden bg event | | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `IRON` | item ball at (5, 35) | `IcePathB1FIron` | `EVENT_ICE_PATH_B1F_IRON` | -| `MAX_POTION` | hidden at (15, 31) | `IcePathB1FHiddenMaxPotion` | `EVENT_ICE_PATH_B1F_HIDDEN_MAX_POTION` | - -**Wild encounters** - -`data/wild/johto_grass.asm:853` `def_grass_wildmons ICE_PATH_B1F`, 2/2/2 percent - -byte-for-byte the same table as `ICE_PATH_1F` above (same levels, same Silver -Delibird swap). - ---- - -### MAP_ICE_PATH_B2F_MAHOGANY_SIDE - -- Script: `maps/IcePathB2FMahoganySide.asm` (`..._MapEvents` = `46:6287`) -- Blocks: `maps/IcePathB2FMahoganySide.blk` -- Header: `data/maps/maps.asm:133` -> `TILESET_ICE_PATH`, `CAVE`, - `LANDMARK_ICE_PATH`, `MUSIC_DARK_CAVE`, `TRUE`, `PALETTE_NITE`, `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:120` -> `map_const ICE_PATH_B2F_MAHOGANY_SIDE, 10, 9` -- Scene scripts and callbacks: both empty - -**Warps** - -| idx | x | y | destination map | dest warp | note | -|---|---|---|---|---|---| -| 1 | 17 | 1 | `ICE_PATH_B1F` | 2 | stairs back up | -| 2 | 9 | 11 | `ICE_PATH_B3F` | 1 | ladder down | -| 3 | 11 | 4 | `ICE_PATH_B1F` | 3 | landing under hole 1 | -| 4 | 4 | 6 | `ICE_PATH_B1F` | 4 | landing under hole 2 | -| 5 | 4 | 12 | `ICE_PATH_B1F` | 5 | landing under hole 3 | -| 6 | 12 | 12 | `ICE_PATH_B1F` | 6 | landing under hole 4 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 17 | `BGEVENT_ITEM` | `IcePathB2FMahoganySideHiddenCarbos` -> `hiddenitem CARBOS, EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_HIDDEN_CARBOS` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ICEPATHB2FMAHOGANYSIDE_BOULDER1` | `SPRITE_BOULDER` | 11 | 3 | `STILL` | `OBJECTTYPE_SCRIPT` | `IcePathB2FMahoganySideBoulder` | `EVENT_BOULDER_IN_ICE_PATH_1A` | -| `ICEPATHB2FMAHOGANYSIDE_BOULDER2` | `SPRITE_BOULDER` | 4 | 7 | `STILL` | `OBJECTTYPE_SCRIPT` | same | `EVENT_BOULDER_IN_ICE_PATH_2A` | -| `ICEPATHB2FMAHOGANYSIDE_BOULDER3` | `SPRITE_BOULDER` | 3 | 12 | `STILL` | `OBJECTTYPE_SCRIPT` | same | `EVENT_BOULDER_IN_ICE_PATH_3A` | -| `ICEPATHB2FMAHOGANYSIDE_BOULDER4` | `SPRITE_BOULDER` | 12 | 13 | `STILL` | `OBJECTTYPE_SCRIPT` | same | `EVENT_BOULDER_IN_ICE_PATH_4A` | -| `ICEPATHB2FMAHOGANYSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 8 | 9 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePathB2FMahoganySideFullHeal` | `EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_FULL_HEAL` | -| `ICEPATHB2FMAHOGANYSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 0 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePathB2FMahoganySideMaxPotion` | `EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_MAX_POTION` | - -**Scripts of interest** - -- `IcePathB2FMahoganySideBoulder` -> `jumptext IcePathB2FMahoganySideBoulderText` - ("It's immovably / imbedded in ice."). These boulders are `SPRITEMOVEDATA_STILL`, - not `STRENGTH_BOULDER` - they are scenery once they land, and they are the - reason the four B1F boulders are one-way. -- `IcePathB2FMahoganySideFullHeal` -> `itemball FULL_HEAL`. This is the Full Heal - the walkthrough says to grab after dropping through a hole. -- `IcePathB2FMahoganySideMaxPotion` -> `itemball MAX_POTION` (the walkthrough's - item list omits it). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `FULL_HEAL` | item ball at (8, 9) | `IcePathB2FMahoganySideFullHeal` | `EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_FULL_HEAL` (`:1068`) | -| `MAX_POTION` | item ball at (0, 2) | `IcePathB2FMahoganySideMaxPotion` | `EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_MAX_POTION` (`:1069`) | -| `CARBOS` | hidden at (0, 17) | `IcePathB2FMahoganySideHiddenCarbos` | `EVENT_ICE_PATH_B2F_MAHOGANY_SIDE_HIDDEN_CARBOS` (`:159`) | - -**Wild encounters** - -`data/wild/johto_grass.asm:908`, 2/2/2 percent, one level higher than B1F: -morn L22 `SWINUB` / L23 `GOLBAT` / L23 `ZUBAT` / L24 `SWINUB` / L23 `ZUBAT` / -L23 `JYNX` / L23 `JYNX`; day swaps slots 5-7 to L23 `JYNX` / L21 `JYNX` / L21 `JYNX`; -nite matches morn. Silver replaces slot 3 with `DELIBIRD` at the same level. - ---- - -### MAP_ICE_PATH_B3F - -- Script: `maps/IcePathB3F.asm` (`IcePathB3F_MapEvents` = `46:632e`) -- Blocks: `maps/IcePathB3F.blk` -- Header: `data/maps/maps.asm:135` -> `TILESET_ICE_PATH`, `CAVE`, - `LANDMARK_ICE_PATH`, `MUSIC_DARK_CAVE`, `TRUE`, `PALETTE_NITE`, `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:122` -> `map_const ICE_PATH_B3F, 10, 9` -- Scene scripts and callbacks: both empty - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 5 | `ICE_PATH_B2F_MAHOGANY_SIDE` | 2 | -| 2 | 15 | 5 | `ICE_PATH_B2F_BLACKTHORN_SIDE` | 2 | - -**Coord events / BG events** - -None. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ICEPATHB3F_POKE_BALL` | `SPRITE_POKE_BALL` | 5 | 7 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePathB3FNevermeltice` | `EVENT_ICE_PATH_B3F_NEVERMELTICE` | -| `ICEPATHB3F_ROCK` | `SPRITE_ROCK` | 6 | 6 | `SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `IcePathB3FRock` | -1 | - -**Scripts of interest** - -- `IcePathB3FRock` -> `jumpstd SmashRockScript`. The rock at (6, 6) sits between - the ladder at (3, 5) and the Nevermeltice at (5, 7); no badge is required for - ROCK SMASH, only the move in the party (`HasRockSmash` in - `engine/events/overworld.asm` / `AskRockSmashScript`). -- `IcePathB3FNevermeltice` -> `itemball NEVERMELTICE`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `NEVERMELTICE` | item ball at (5, 7) | `IcePathB3FNevermeltice` | `EVENT_ICE_PATH_B3F_NEVERMELTICE` (`:1071`) | - -**Wild encounters** - -`data/wild/johto_grass.asm:1018`, 2/2/2 percent, one level above B2F: -morn/nite L23 `SWINUB` / L24 `GOLBAT` / L24 `ZUBAT` / L25 `SWINUB` / L24 `ZUBAT` / -L24 `JYNX` / L24 `JYNX`; day swaps slots 5-7 to `JYNX` L24/L22/L22. - ---- - -### MAP_ICE_PATH_B2F_BLACKTHORN_SIDE - -- Script: `maps/IcePathB2FBlackthornSide.asm` (`..._MapEvents` = `46:6305`) -- Blocks: `maps/IcePathB2FBlackthornSide.blk` -- Header: `data/maps/maps.asm:134` -> `TILESET_ICE_PATH`, `CAVE`, - `LANDMARK_ICE_PATH`, `MUSIC_DARK_CAVE`, `TRUE`, `PALETTE_NITE`, `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:121` -> `map_const ICE_PATH_B2F_BLACKTHORN_SIDE, 5, 9` -- Scene scripts and callbacks: both empty - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 15 | `ICE_PATH_B1F` | 8 | -| 2 | 3 | 3 | `ICE_PATH_B3F` | 2 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 10 | `BGEVENT_ITEM` | `IcePathB2FBlackthornSideHiddenIceHeal` -> `hiddenitem ICE_HEAL, EVENT_ICE_PATH_B2F_BLACKTHORN_SIDE_HIDDEN_ICE_HEAL` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ICEPATHB2FBLACKTHORNSIDE_POKE_BALL` | `SPRITE_POKE_BALL` | 8 | 16 | `STILL` | `OBJECTTYPE_ITEMBALL` | `IcePathB2FBlackthornSideTMRest` | `EVENT_ICE_PATH_B2F_BLACKTHORN_SIDE_TM_REST` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_REST` (TM44) | item ball at (8, 16) | `IcePathB2FBlackthornSideTMRest` | `EVENT_ICE_PATH_B2F_BLACKTHORN_SIDE_TM_REST` (`:1070`) | -| `ICE_HEAL` | hidden at (2, 10) | `IcePathB2FBlackthornSideHiddenIceHeal` | `EVENT_ICE_PATH_B2F_BLACKTHORN_SIDE_HIDDEN_ICE_HEAL` (`:160`) | - -**Wild encounters** - -`data/wild/johto_grass.asm:963`, identical to `ICE_PATH_B2F_MAHOGANY_SIDE`. - ---- - -### MAP_BLACKTHORN_CITY - -- Script: `maps/BlackthornCity.asm` (`BlackthornCity_MapEvents` = `49:5e4f`, - `BlackthornCityFlypointCallback` = `49:58f2`) -- Blocks: `maps/BlackthornCity.blk` -- Header: `data/maps/maps.asm:187` -> `TILESET_JOHTO`, `TOWN`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_AZALEA_TOWN`, phone `FALSE`, - `PALETTE_AUTO`, `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:172` -> `map_const BLACKTHORN_CITY, 20, 18` -- Connections: `data/maps/attributes.asm:160` -> south `ROUTE_45` (offset 0), - west `ROUTE_44` (offset 9) -- Callbacks: - - `MAPCALLBACK_NEWMAP, BlackthornCityFlypointCallback` -> `setflag ENGINE_FLYPOINT_BLACKTHORN` - - `MAPCALLBACK_OBJECTS, BlackthornCitySantosCallback` -> `readvar VAR_WEEKDAY`, - `appear`/`disappear BLACKTHORNCITY_SANTOS` on `SATURDAY` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 18 | 11 | `BLACKTHORN_GYM_1F` | 1 | -| 2 | 13 | 21 | `BLACKTHORN_DRAGON_SPEECH_HOUSE` | 1 | -| 3 | 29 | 23 | `BLACKTHORN_EMYS_HOUSE` | 1 | -| 4 | 15 | 29 | `BLACKTHORN_MART` | 2 | -| 5 | 21 | 29 | `BLACKTHORN_POKECENTER_1F` | 1 | -| 6 | 9 | 31 | `MOVE_DELETERS_HOUSE` | 1 | -| 7 | 36 | 9 | `ICE_PATH_1F` | 2 | -| 8 | 20 | 1 | `DRAGONS_DEN_1F` | 1 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 34 | 24 | `BGEVENT_READ` | `BlackthornCitySign` | -| 17 | 13 | `BGEVENT_READ` | `BlackthornGymSign` | -| 7 | 31 | `BGEVENT_READ` | `MoveDeletersHouseSign` | -| 21 | 3 | `BGEVENT_READ` | `DragonDensSign` | -| 5 | 25 | `BGEVENT_READ` | `BlackthornCityTrainerTips` | -| 16 | 29 | `BGEVENT_READ` | `BlackthornCityMartSign` -> `jumpstd MartSignScript` | -| 22 | 29 | `BGEVENT_READ` | `BlackthornCityPokecenterSign` -> `jumpstd PokecenterSignScript` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BLACKTHORNCITY_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 18 | 12 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornSuperNerdScript` | `EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM` | -| `BLACKTHORNCITY_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 19 | 12 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornSuperNerdScript` | `EVENT_BLACKTHORN_CITY_SUPER_NERD_DOES_NOT_BLOCK_GYM` | -| `BLACKTHORNCITY_GRAMPS1` | `SPRITE_GRAMPS` | 20 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornGramps1Script` | `EVENT_BLACKTHORN_CITY_GRAMPS_BLOCKS_DRAGONS_DEN` | -| `BLACKTHORNCITY_GRAMPS2` | `SPRITE_GRAMPS` | 21 | 2 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `BlackthornGramps2Script` | `EVENT_BLACKTHORN_CITY_GRAMPS_NOT_BLOCKING_DRAGONS_DEN` | -| `BLACKTHORNCITY_BLACK_BELT` | `SPRITE_BLACK_BELT` | 24 | 31 | `WALK_LEFT_RIGHT` (radius 1) | `OBJECTTYPE_SCRIPT` | `BlackthornBlackBeltScript` | -1 | -| `BLACKTHORNCITY_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 9 | 25 | `WALK_LEFT_RIGHT` (radius 2) | `OBJECTTYPE_SCRIPT` | `BlackthornCooltrainerF1Script` | -1 | -| `BLACKTHORNCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 13 | 15 | `WALK_LEFT_RIGHT` (radius 1) | `OBJECTTYPE_SCRIPT` | `BlackthornYoungsterScript` | -1 | -| `BLACKTHORNCITY_SANTOS` | `SPRITE_YOUNGSTER` | 22 | 20 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SantosScript` | `EVENT_BLACKTHORN_CITY_SANTOS_OF_SATURDAY` | -| `BLACKTHORNCITY_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 35 | 19 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BlackthornCooltrainerF2Script` | -1 | - -**Scripts of interest** - -- `BlackthornCityFlypointCallback` - `setflag ENGINE_FLYPOINT_BLACKTHORN` - (`constants/engine_flags.asm:89`). Fires on `MAPCALLBACK_NEWMAP`, i.e. the - first time the player walks out of the Ice Path. -- `BlackthornSuperNerdScript` - `checkevent EVENT_BEAT_CLAIR` -> `Text_ClairIsBeaten`; - else `checkevent EVENT_CLEARED_RADIO_TOWER` -> `Text_ClairIsIn`; else - `Text_ClairIsOut` ("Our GYM LEADER is training in the cave behind here"). - Both nerd objects share this script. -- `BlackthornGramps1Script` -> `BlackthornGrampsRefusesEntryText` - ("Only dragon users are permitted to train here"). `BlackthornGramps2Script` - -> `BlackthornGrampsGrantsEntryText` ("Since CLAIR ... has allowed it"). - These are the Dragon's Den doorman, one object per state. -- `SantosScript` - Saturday-only NPC, `verbosegiveitem SPELL_TAG`, guarded by - `EVENT_MET_SANTOS_OF_SATURDAY` / `EVENT_GOT_SPELL_TAG_FROM_SANTOS`. - Not mentioned by the walkthrough; free item for a bot that lands on Saturday. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_BLACKTHORN` | `constants/engine_flags.asm:89` | `BlackthornCityFlypointCallback` | Fly destination unlocked | -| `EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM` | `:1157` | `maps/RadioTower5F.asm:111` `setevent` | **set -> that object is hidden.** Set once the Radio Tower is cleared, which is what removes the blocker standing on (18, 12), the tile below the gym door | -| `EVENT_BLACKTHORN_CITY_SUPER_NERD_DOES_NOT_BLOCK_GYM` | `:1158` | set at new game (`engine/events/std_scripts.asm:460`), cleared by `maps/RadioTower5F.asm:112` | clear -> the non-blocking nerd stands beside the door at (19, 12) | -| `EVENT_BLACKTHORN_CITY_GRAMPS_BLOCKS_DRAGONS_DEN` | `:1262` | `maps/BlackthornGym1F.asm:54` `setevent` after beating Clair | set -> blocker at (20, 2) hidden | -| `EVENT_BLACKTHORN_CITY_GRAMPS_NOT_BLOCKING_DRAGONS_DEN` | `:1263` | set at new game (`std_scripts.asm:512`), cleared by `BlackthornGym1F.asm:55` | clear -> permissive gramps visible at (21, 2), path to (20, 1) open | -| `EVENT_CLEARED_RADIO_TOWER` | `:42` | read by `BlackthornSuperNerdScript` and `BlackthornBlackBeltScript` | | -| `EVENT_BLACKTHORN_CITY_SANTOS_OF_SATURDAY` | `:1279` | `BlackthornCitySantosCallback` | weekday visibility | - -**Items** - -None on the overworld map itself. - -**Trainers** - -None. - -**Wild encounters** - -Water only: `data/wild/johto_water.asm:264` `def_water_wildmons BLACKTHORN_CITY`, -4 percent: L15 `MAGIKARP`, L10 `MAGIKARP`, L5 `MAGIKARP`. -Fishing group `FISHGROUP_POND` (`data/wild/fish.asm:72`): -Old = Magikarp/Magikarp/Poliwag L10; Good = Magikarp L20, Poliwag L20 x2, -`time_group 6`; Super = Poliwag L40, `time_group 7`, Magikarp L40, Poliwag L40. - ---- - -### MAP_BLACKTHORN_POKECENTER_1F - -- Script: `maps/BlackthornPokecenter1F.asm` -- Header: `data/maps/maps.asm:183` -> `TILESET_POKECENTER`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_POKEMON_CENTER`, `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:168` -> `map_const BLACKTHORN_POKECENTER_1F, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `BLACKTHORN_CITY` | 5 | -| 2 | 4 | 7 | `BLACKTHORN_CITY` | 5 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BLACKTHORNPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` | -1 | -| `BLACKTHORNPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornPokecenter1FGentlemanScript` | -1 | -| `BLACKTHORNPOKECENTER1F_TWIN` | `SPRITE_TWIN` | 1 | 4 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `BlackthornPokecenter1FTwinScript` | -1 | -| `BLACKTHORNPOKECENTER1F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 6 | `WALK_LEFT_RIGHT` (radius 1) | `OBJECTTYPE_SCRIPT` | `BlackthornPokecenter1FCooltrainerMScript` -> `jumpstd HappinessCheckScript` | -1 | - -No bg events, no coord events. - ---- - -### MAP_BLACKTHORN_MART - -- Script: `maps/BlackthornMart.asm` -- Header: `data/maps/maps.asm:182` -> `TILESET_MART`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_AZALEA_TOWN`, `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:167` -> `map_const BLACKTHORN_MART, 6, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `BLACKTHORN_CITY` | 4 | -| 2 | 3 | 7 | `BLACKTHORN_CITY` | 4 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BLACKTHORNMART_CLERK` | `SPRITE_CLERK` | 1 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `BlackthornMartClerkScript` | -1 | -| `BLACKTHORNMART_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 6 | `WALK_LEFT_RIGHT` (radius 2) | `OBJECTTYPE_SCRIPT` | `BlackthornMartCooltrainerMScript` | -1 | -| `BLACKTHORNMART_BLACK_BELT` | `SPRITE_BLACK_BELT` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BlackthornMartBlackBeltScript` | -1 | - -**Scripts of interest** - -`BlackthornMartClerkScript` -> `pokemart MARTTYPE_STANDARD, MART_BLACKTHORN`. -Inventory, `data/items/marts.asm:216` `MartBlackthorn` (`05:63d0`), 9 items: -`GREAT_BALL`, `ULTRA_BALL`, `HYPER_POTION`, `MAX_POTION`, `FULL_HEAL`, -`REVIVE`, `MAX_REPEL`, `X_DEFEND`, `X_ATTACK`. -This is where the walkthrough's "restock Ultra Balls" and "have a few Revives, -Full Heals, and Hyper Potions" shopping list is actually purchasable. - ---- - -### MAP_BLACKTHORN_EMYS_HOUSE - -- Script: `maps/BlackthornEmysHouse.asm` -- Header: `data/maps/maps.asm:181` -> `TILESET_HOUSE`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_AZALEA_TOWN`, `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:166` -> `map_const BLACKTHORN_EMYS_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `BLACKTHORN_CITY` | 3 | -| 2 | 3 | 7 | `BLACKTHORN_CITY` | 3 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `EmysHouseBookshelf` -> `jumpstd MagazineBookshelfScript` | -| 1 | 1 | `BGEVENT_READ` | same | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BLACKTHORNEMYSHOUSE_EMY` | `SPRITE_LASS` | 2 | 3 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `Emy` | -1 | - -**Scripts of interest** - -`Emy` -> `faceplayer / opentext / trade NPC_TRADE_EMY / waitbutton / closetext / end`. -`NPC_TRADE_EMY` is index 3 (`constants/npc_trade_constants.asm:20`) and its row in -`data/events/npc_trades.asm` is: - -``` -npctrade TRADE_DIALOGSET_NEWBIE, DRAGONAIR, RHYDON, "DON", $77, $66, BITTER_BERRY, 00283, "EMY", TRADE_GENDER_FEMALE -``` - -So: wants a **female** `DRAGONAIR`, gives `RHYDON` nicknamed "DON", -DVs `$77`/`$66`, holding a `BITTER_BERRY`, OT "EMY" ID 00283. -The walkthrough's "lady that would trade her Rhydon for a Dragonair" omits the -gender requirement, which is load-bearing for a bot. - ---- - -### MAP_MOVE_DELETERS_HOUSE - -- Script: `maps/MoveDeletersHouse.asm` -- Header: `data/maps/maps.asm:184` -> `TILESET_HOUSE`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_AZALEA_TOWN`, `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:169` -> `map_const MOVE_DELETERS_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `BLACKTHORN_CITY` | 6 | -| 2 | 3 | 7 | `BLACKTHORN_CITY` | 6 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `MoveDeletersHouseBookshelf` -> `jumpstd DifficultBookshelfScript` | -| 1 | 1 | `BGEVENT_READ` | same | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOVEDELETERSHOUSE_SUPER_NERD` | `SPRITE_SUPER_NERD` | 2 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MoveDeleter` | -1 | - -**Scripts of interest** - -`MoveDeleter` -> `faceplayer / opentext / special MoveDeletion / waitbutton / -closetext / end`. The whole flow lives in `engine/events/move_deleter.asm`; the -script never writes `wScriptVar`. - ---- - -### MAP_BLACKTHORN_DRAGON_SPEECH_HOUSE - -Not visited by the walkthrough; included because it is city warp 2. - -- Script: `maps/BlackthornDragonSpeechHouse.asm` -- Header: `data/maps/maps.asm:180` -> `TILESET_HOUSE`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_AZALEA_TOWN`, `FALSE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:165` -> `map_const BLACKTHORN_DRAGON_SPEECH_HOUSE, 4, 4` - -**Warps**: 1 = (2, 7) -> `BLACKTHORN_CITY` 2; 2 = (3, 7) -> `BLACKTHORN_CITY` 2. - -**Object events**: `BLACKTHORNDRAGONSPEECHHOUSE_GRANNY` `SPRITE_GRANNY` at (2, 3), -`SPINRANDOM_SLOW`, `BlackthornDragonSpeechHouseGrannyScript` (CLAIR and LANCE -trained here); `BLACKTHORNDRAGONSPEECHHOUSE_EKANS` `SPRITE_EKANS` at (5, 5), -`SPRITEMOVEDATA_POKEMON`, `BlackthornDragonSpeechHouseDratiniScript` -(`cry DRATINI` - the sprite is EKANS but it is a Dratini). - ---- - -### MAP_BLACKTHORN_GYM_1F - -- Script: `maps/BlackthornGym1F.asm` (`BlackthornGym1F_MapEvents` = `53:4964`, - `BlackthornGym1FBouldersCallback` = `53:4005`, `BlackthornGymClairScript` = `53:4024`) -- Blocks: `maps/BlackthornGym1F.blk` -- Header: `data/maps/maps.asm:178` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:163` -> `map_const BLACKTHORN_GYM_1F, 5, 9` -- Callbacks: `callback MAPCALLBACK_TILES, BlackthornGym1FBouldersCallback` - -**Warps** - -| idx | x | y | destination map | dest warp | note | -|---|---|---|---|---|---| -| 1 | 4 | 17 | `BLACKTHORN_CITY` | 1 | door | -| 2 | 5 | 17 | `BLACKTHORN_CITY` | 1 | door | -| 3 | 1 | 7 | `BLACKTHORN_GYM_2F` | 1 | stairs | -| 4 | 7 | 9 | `BLACKTHORN_GYM_2F` | 2 | stairs | -| 5 | 2 | 6 | `BLACKTHORN_GYM_2F` | 3 | landing under 2F hole at (2, 5) | -| 6 | 7 | 7 | `BLACKTHORN_GYM_2F` | 4 | landing under 2F hole at (8, 7) | -| 7 | 7 | 6 | `BLACKTHORN_GYM_2F` | 5 | landing under 2F hole at (8, 3) | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `BlackthornGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `BlackthornGymStatue` | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `BLACKTHORNGYM1F_CLAIR` | `SPRITE_CLAIR` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymClairScript` | -1 | -| `BLACKTHORNGYM1F_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 6 | 6 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerCooltrainermMike` | -1 | -| `BLACKTHORNGYM1F_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 1 | 14 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerCooltrainermPaul` | -1 | -| `BLACKTHORNGYM1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 9 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 1 | `TrainerCooltrainerfLola` | -1 | -| `BLACKTHORNGYM1F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 15 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymGuideScript` | -1 | - -**Scripts of interest** - -- `BlackthornGym1FBouldersCallback` (`MAPCALLBACK_TILES`) - paints the fallen - boulders that came through the 2F holes: - - ``` - checkevent EVENT_BOULDER_IN_BLACKTHORN_GYM_1 -> changeblock 8, 2, $3b - checkevent EVENT_BOULDER_IN_BLACKTHORN_GYM_2 -> changeblock 2, 4, $3a - checkevent EVENT_BOULDER_IN_BLACKTHORN_GYM_3 -> changeblock 8, 6, $3b - ``` - - (`Script_changeblock` adds 4 to both operands and then divides down to a block - index, so these are block coordinates (4, 1), (1, 2) and (4, 3).) -- `BlackthornGymClairScript` (`53:4024`) - the single most important script in - this section. Control flow: - 1. `checkflag ENGINE_RISINGBADGE` -> `.AlreadyGotBadge` (`53:4074`). - 2. `checkevent EVENT_BEAT_CLAIR` -> `.FightDone` (`53:4063`). - 3. Otherwise: `ClairIntroText`, `winlosstext ClairWinText, 0`, - `loadtrainer CLAIR, CLAIR1`, `startbattle`, `reloadmapafterbattle`, - `setevent EVENT_BEAT_CLAIR`, `ClairText_GoToDragonsDen`, then a block of - bookkeeping: - `setevent EVENT_BEAT_COOLTRAINERM_PAUL`, `..._CODY`, `..._MIKE`, - `setevent EVENT_BEAT_COOLTRAINERF_FRAN`, `..._LOLA` - (the five gym trainers are force-marked beaten), - `clearevent EVENT_MAHOGANY_MART_OWNERS`, - `setevent EVENT_BLACKTHORN_CITY_GRAMPS_BLOCKS_DRAGONS_DEN`, - `clearevent EVENT_BLACKTHORN_CITY_GRAMPS_NOT_BLOCKING_DRAGONS_DEN` - (this pair is what opens Dragon's Den). **No badge and no TM are given here.** - 4. `.FightDone`: `checkitem DRAGON_FANG` -> `.HasDragonFang` - (`BlackthornGymClairText_Cheat`, "You did not get that at DRAGON'S DEN") ; - else `ClairText_WhatsTheMatter`. - 5. `.AlreadyGotBadge`: `checkevent EVENT_GOT_TM24_DRAGONBREATH` -> `.GotTM24` - (`BlackthornGymClairText_League`); else `verbosegiveitem TM_DRAGONBREATH`, - `setevent EVENT_GOT_TM24_DRAGONBREATH`. This is the fallback path for a - player whose bag was full in the Den. -- `BlackthornGymStatue` - `checkflag ENGINE_RISINGBADGE` -> `jumpstd GymStatue2Script` - with `gettrainername STRING_BUFFER_4, CLAIR, CLAIR1`; else `jumpstd GymStatue1Script`. -- `BlackthornGymGuideScript` - branches on `EVENT_BEAT_CLAIR`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_CLAIR` | `constants/event_flags.asm:713` | set by `BlackthornGymClairScript` | battle won; badge still pending | -| `ENGINE_RISINGBADGE` | `constants/engine_flags.asm:45` | **set in `maps/DragonsDenB1F.asm`, not here** | the actual 8th badge | -| `EVENT_GOT_TM24_DRAGONBREATH` | `constants/event_flags.asm:21` | Den script, or the `.AlreadyGotBadge` fallback here | | -| `EVENT_BOULDER_IN_BLACKTHORN_GYM_1..3` | `:1192-1194` | 2F stone table `disappear`; read by the 1F tiles callback | set -> boulder has fallen; 1F block repainted | -| `EVENT_BEAT_COOLTRAINERM_PAUL` / `_CODY` / `_MIKE` | `:859-861` | `trainer` rows and the Clair script's bulk `setevent` | | -| `EVENT_BEAT_COOLTRAINERF_FRAN` / `_LOLA` | `:879-880` | same | | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_DRAGONBREATH` (TM24) | Clair, only on the `.AlreadyGotBadge` re-talk path | `BlackthornGymClairScript.AlreadyGotBadge` | `EVENT_GOT_TM24_DRAGONBREATH` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `COOLTRAINERM`, `PAUL` | `CooltrainerMGroup` | COOLTRAINERM (3) | L34 `DRATINI` x3; `TRAINERTYPE_NORMAL` | `TrainerCooltrainermPaul` | none | -| `COOLTRAINERM`, `MIKE` | same | COOLTRAINERM (5) | L37 `DRAGONAIR`; `TRAINERTYPE_NORMAL` | `TrainerCooltrainermMike` | none | -| `COOLTRAINERF`, `LOLA` | `CooltrainerFGroup` | COOLTRAINERF (4) | L34 `DRATINI`, L36 `DRAGONAIR`; `TRAINERTYPE_NORMAL` | `TrainerCooltrainerfLola` | none | -| `CLAIR`, `CLAIR1` | `ClairGroup` (`data/trainers/parties.asm:67`) | CLAIR (1); `TRAINERTYPE_MOVES` | L37 `DRAGONAIR` (Thunder Wave / Surf / Slam / Dragonbreath), L37 `DRAGONAIR` (Thunder Wave / Thunderbolt / Slam / Dragonbreath), L37 `DRAGONAIR` (Thunder Wave / Ice Beam / Slam / Dragonbreath), L40 `KINGDRA` (Smokescreen / Surf / Hyper Beam / Dragonbreath) | `BlackthornGymClairScript` | none | - -Note the party **order**: the asm sends out three Dragonair first and Kingdra -last. The walkthrough lists Kingdra third. - -**Wild encounters** - -None (indoor). - ---- - -### MAP_BLACKTHORN_GYM_2F - -- Script: `maps/BlackthornGym2F.asm` (`BlackthornGym2F_MapEvents` = `53:4bc8`, - `BlackthornGym2FSetUpStoneTableCallback` = `53:49dd`) -- Blocks: `maps/BlackthornGym2F.blk` -- Header: `data/maps/maps.asm:179` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`, - `LANDMARK_BLACKTHORN_CITY`, `MUSIC_GYM`, `TRUE`, `PALETTE_DAY` -- Dimensions: `constants/map_constants.asm:164` -> `map_const BLACKTHORN_GYM_2F, 5, 9` -- Callbacks: `callback MAPCALLBACK_CMDQUEUE, BlackthornGym2FSetUpStoneTableCallback` - -**Warps** - -| idx | x | y | destination map | dest warp | note | -|---|---|---|---|---|---| -| 1 | 1 | 7 | `BLACKTHORN_GYM_1F` | 3 | stairs | -| 2 | 7 | 9 | `BLACKTHORN_GYM_1F` | 4 | stairs | -| 3 | 2 | 5 | `BLACKTHORN_GYM_1F` | 5 | hole | -| 4 | 8 | 7 | `BLACKTHORN_GYM_1F` | 6 | hole | -| 5 | 8 | 3 | `BLACKTHORN_GYM_1F` | 7 | hole | - -**Coord events / BG events** - -None. - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `BLACKTHORNGYM2F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 4 | 1 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerCooltrainermCody` | -1 | -| `BLACKTHORNGYM2F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 4 | 11 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerCooltrainerfFran` | -1 | -| `BLACKTHORNGYM2F_BOULDER1` | `SPRITE_BOULDER` | 8 | 2 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | `EVENT_BOULDER_IN_BLACKTHORN_GYM_1` | -| `BLACKTHORNGYM2F_BOULDER2` | `SPRITE_BOULDER` | 2 | 3 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | `EVENT_BOULDER_IN_BLACKTHORN_GYM_2` | -| `BLACKTHORNGYM2F_BOULDER3` | `SPRITE_BOULDER` | 6 | 16 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | `EVENT_BOULDER_IN_BLACKTHORN_GYM_3` | -| `BLACKTHORNGYM2F_BOULDER4` | `SPRITE_BOULDER` | 3 | 3 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | -1 | -| `BLACKTHORNGYM2F_BOULDER5` | `SPRITE_BOULDER` | 6 | 1 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | -1 | -| `BLACKTHORNGYM2F_BOULDER6` | `SPRITE_BOULDER` | 8 | 14 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | 0 | `BlackthornGymBoulder` | -1 | - -Three of the six boulders (4, 5, 6) carry **no** event flag and therefore never -disappear - they are the bridge-building boulders, not the hole-filling ones. - -**Scripts of interest** - -- `BlackthornGym2FSetUpStoneTableCallback` `.StoneTable`: - - ``` - stonetable 5, BLACKTHORNGYM2F_BOULDER1, .Boulder1 ; warp 5 = (8, 3) - stonetable 3, BLACKTHORNGYM2F_BOULDER2, .Boulder2 ; warp 3 = (2, 5) - stonetable 4, BLACKTHORNGYM2F_BOULDER3, .Boulder3 ; warp 4 = (8, 7) - db -1 - ``` - - Note the deliberately scrambled warp order: boulder 1 belongs in warp 5, - boulder 2 in warp 3, boulder 3 in warp 4. -- Each arm does `disappear BLACKTHORNGYM2F_BOULDERn` (setting the matching - `EVENT_BOULDER_IN_BLACKTHORN_GYM_n`) then `.Fall`: `pause 30`, `scall .FX` - (`playsound SFX_STRENGTH`, `earthquake 80`), `BlackthornGym2FBoulderFellText` - ("The boulder fell / through!"). -- `BlackthornGymBoulder` -> `jumpstd StrengthBoulderScript` for all six. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `COOLTRAINERM`, `CODY` | `CooltrainerMGroup` | COOLTRAINERM (4) | L34 `HORSEA`, L36 `SEADRA`; `TRAINERTYPE_NORMAL` | `TrainerCooltrainermCody` | none | -| `COOLTRAINERF`, `FRAN` | `CooltrainerFGroup` | COOLTRAINERF (3) | L37 `SEADRA`; `TRAINERTYPE_NORMAL` | `TrainerCooltrainerfFran` | none | - ---- - -### MAP_DRAGONS_DEN_1F - -- Script: `maps/DragonsDen1F.asm` (`DragonsDen1F_MapEvents` = `47:44c2`) -- Blocks: `maps/DragonsDen1F.blk` -- Header: `data/maps/maps.asm:150` -> `TILESET_CAVE`, `CAVE`, - `LANDMARK_DRAGONS_DEN`, `MUSIC_DRAGONS_DEN`, `TRUE`, `PALETTE_NITE`, - `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:137` -> `map_const DRAGONS_DEN_1F, 5, 9` -- Scene scripts, callbacks, coord events, bg events, object events: **all empty** - -**Warps** - -| idx | x | y | destination map | dest warp | note | -|---|---|---|---|---|---| -| 1 | 3 | 5 | `BLACKTHORN_CITY` | 8 | cave mouth | -| 2 | 3 | 3 | `DRAGONS_DEN_1F` | 4 | internal ladder, pairs with warp 4 | -| 3 | 5 | 15 | `DRAGONS_DEN_B1F` | 1 | the door down to the Den proper | -| 4 | 5 | 13 | `DRAGONS_DEN_1F` | 2 | internal ladder, pairs with warp 2 | - -The "bodyguard in front of it" the walkthrough mentions is on -`MAP_BLACKTHORN_CITY`, not here - `BLACKTHORNCITY_GRAMPS1` at (20, 2). - ---- - -### MAP_DRAGONS_DEN_B1F - -- Script: `maps/DragonsDenB1F.asm` (`DragonsDenB1F_MapEvents` = `47:49da`, - `DragonsDenB1FDragonFangScript` = `47:44fa`) -- Blocks: `maps/DragonsDenB1F.blk` -- Header: `data/maps/maps.asm:151` -> `TILESET_JOHTO`, `CAVE`, - `LANDMARK_DRAGONS_DEN`, `MUSIC_DRAGONS_DEN`, phone `TRUE`, `PALETTE_NITE`, - `FISHGROUP_DRATINI` -- Dimensions: `constants/map_constants.asm:138` -> `map_const DRAGONS_DEN_B1F, 20, 18` -- Attributes: `data/maps/attributes.asm:470`, no connections -- Callbacks: `callback MAPCALLBACK_NEWMAP, DragonsDenB1FCheckRivalCallback` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 20 | 3 | `DRAGONS_DEN_1F` | 3 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 18 | 24 | `BGEVENT_READ` | `DragonShrineSignpost` | -| 31 | 4 | `BGEVENT_ITEM` | `DragonsDenB1FHiddenRevive` -> `hiddenitem REVIVE, EVENT_DRAGONS_DEN_B1F_HIDDEN_REVIVE` | -| 21 | 17 | `BGEVENT_ITEM` | `DragonsDenB1FHiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_DRAGONS_DEN_B1F_HIDDEN_MAX_POTION` | -| 31 | 15 | `BGEVENT_ITEM` | `DragonsDenB1FHiddenMaxElixer` -> `hiddenitem MAX_ELIXER, EVENT_DRAGONS_DEN_B1F_HIDDEN_MAX_ELIXER` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DRAGONSDENB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 35 | 16 | `STILL` | `OBJECTTYPE_SCRIPT` | `DragonsDenB1FDragonFangScript` | `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG` | -| `DRAGONSDENB1F_CLAIR` | `SPRITE_CLAIR` | 35 | 22 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_DRAGONS_DEN_CLAIR` | -| `DRAGONSDENB1F_RIVAL` | `SPRITE_RIVAL` | 20 | 23 | `WANDER` (radius 2, 2) | `OBJECTTYPE_SCRIPT` | `DragonsDenB1FRivalScript` | `EVENT_RIVAL_DRAGONS_DEN` | - -Note the Dragon Fang object is `OBJECTTYPE_SCRIPT`, **not** `OBJECTTYPE_ITEMBALL` - -it runs a full script, so a bot cannot treat it as a plain pickup. - -**Scripts of interest** - -- `DragonsDenB1FDragonFangScript` (`47:44fa`) - the badge handoff: - 1. `giveitem DRAGON_FANG`; `iffalse .BagFullDragonFang` (`47:4558`, which prints - `Text_FoundDragonFang` + `Text_NoRoomForDragonFang` and ends **without** - setting anything - a full bag here loses the whole scene until you return). - 2. `disappear DRAGONSDENB1F_POKE_BALL1` (sets `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG`), - `Text_FoundDragonFang`, `playsound SFX_ITEM`, `itemnotify`. - 3. `readvar VAR_FACING`; `ifequal RIGHT, .next` -> `moveobject DRAGONSDENB1F_CLAIR, 34, 21` - (Clair spawns one cell over when the player grabbed the Fang facing right). - 4. `appear DRAGONSDENB1F_CLAIR` (clears `EVENT_DRAGONS_DEN_CLAIR`), - `applymovement DRAGONSDENB1F_CLAIR, MovementDragonsDen_ClairWalksToYou` - (four `step UP`), `turnobject PLAYER, DOWN`. - 5. `ClairText_GiveDragonbreathDragonDen`, - `DragonShrinePlayerReceivedRisingBadgeText`, `playsound SFX_GET_BADGE`, - **`setflag ENGINE_RISINGBADGE`**, `specialphonecall SPECIALCALL_MASTERBALL`, - `DragonShrineRisingBadgeExplanationText`, - `verbosegiveitem TM_DRAGONBREATH, 1`, `setevent EVENT_GOT_TM24_DRAGONBREATH`, - `ClairText_CollectedAllBadges` (the "go to NEW BARK TOWN then SURF east" - directions), then `applymovement ... MovementDragonsDen_ClairWalksAway` - (four `step DOWN`) and `disappear DRAGONSDENB1F_CLAIR`. -- `DragonsDenB1FCheckRivalCallback` - `checkevent EVENT_BEAT_RIVAL_IN_MT_MOON`, - then `readvar VAR_WEEKDAY` and appears the rival only on `TUESDAY` or - `THURSDAY`. Mt. Moon is post-Elite-Four, so the rival is **never** present - during this section; `DragonsDenB1FRivalScript` is a talk-only scene guarded by - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`. -- `DragonShrineSignpost` - Gold/Silver has no separate Dragon Shrine map; the - shrine is only this signpost at (18, 24). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG` | `constants/event_flags.asm:1097` | `disappear` in the Fang script | set -> Fang taken, scene done | -| `EVENT_DRAGONS_DEN_CLAIR` | `:1146` | set at new game (`std_scripts.asm`), cleared by `appear`, re-set by the closing `disappear` | Clair's cutscene object | -| `ENGINE_RISINGBADGE` | `constants/engine_flags.asm:45` | `setflag` here | 8th badge; also the Waterfall gate | -| `EVENT_GOT_TM24_DRAGONBREATH` | `constants/event_flags.asm:21` | `setevent` here | | -| `SPECIALCALL_MASTERBALL` | `constants/phone_constants.asm:52` | `specialphonecall` here; consumed by `ElmPhoneCallerScript` (`engine/phone/scripts/elm.asm:70` `.gift`) | queues Elm's call; the ball itself is `ElmGiveMasterBallScript` in `maps/ElmsLab.asm`, gated on `checkflag ENGINE_RISINGBADGE` | -| `EVENT_RIVAL_DRAGONS_DEN` | `:1128` | callback | rival hidden until post-Mt.-Moon Tue/Thu | -| `EVENT_DRAGONS_DEN_B1F_HIDDEN_REVIVE` / `_MAX_POTION` / `_MAX_ELIXER` | `:170-172` | hidden bg events | | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `DRAGON_FANG` | `giveitem` from the object at (35, 16) | `DragonsDenB1FDragonFangScript` | `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG` | -| `TM_DRAGONBREATH` (TM24) | `verbosegiveitem` from Clair right after | same | `EVENT_GOT_TM24_DRAGONBREATH` | -| `REVIVE` | hidden at (31, 4) | `DragonsDenB1FHiddenRevive` | `EVENT_DRAGONS_DEN_B1F_HIDDEN_REVIVE` | -| `MAX_POTION` | hidden at (21, 17) | `DragonsDenB1FHiddenMaxPotion` | `EVENT_DRAGONS_DEN_B1F_HIDDEN_MAX_POTION` | -| `MAX_ELIXER` | hidden at (31, 15) | `DragonsDenB1FHiddenMaxElixer` | `EVENT_DRAGONS_DEN_B1F_HIDDEN_MAX_ELIXER` | - -**Wild encounters** - -- Water: `data/wild/johto_water.asm:121` `def_water_wildmons DRAGONS_DEN_B1F`, - 4 percent: L15 `MAGIKARP`, L10 `MAGIKARP`, L10 `DRATINI`. There is no grass - table for this map; the walkthrough's "Magikarp / Dratini" list is Surf-only. -- Fishing group `FISHGROUP_DRATINI` (`data/maps/maps.asm:151`), - `data/wild/fish.asm:87`: Old = Magikarp L10 x3; Good = Magikarp L20 x3 + - `time_group 8` (Dratini L20); Super = Magikarp L40, `time_group 9` - (Dratini L40), Magikarp L40, `DRAGONAIR` L40. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Blackthorn Gym door at (18, 11) | `maps/BlackthornCity.asm` object `BLACKTHORNCITY_SUPER_NERD1` standing on (18, 12) with flag `EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM` | that event **set** (object hidden) | `maps/RadioTower5F.asm:111-112` sets `EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM` and clears `..._DOES_NOT_BLOCK_GYM` when the Radio Tower is cleared. Previous section's work; a bot arriving here without it is stuck at the gym door | -| Dragon's Den entrance at (20, 1) | `maps/BlackthornCity.asm` object `BLACKTHORNCITY_GRAMPS1` standing on (20, 2) with flag `EVENT_BLACKTHORN_CITY_GRAMPS_BLOCKS_DRAGONS_DEN` | that event **set** (object hidden) | `maps/BlackthornGym1F.asm:54-55` inside `BlackthornGymClairScript`, immediately after `setevent EVENT_BEAT_CLAIR` | -| Ice Path B1F -> Blackthorn side | `maps/IcePathB1F.asm` `IcePathB1FSetUpStoneTableCallback.StoneTable`; the four `SPRITEMOVEDATA_STRENGTH_BOULDER` objects | HM04 STRENGTH usable = `ENGINE_PLAINBADGE` (`engine/events/overworld.asm:941` `StrengthFunction.TryStrength` -> `CheckBadge`) plus a party member that knows the move | push boulder N onto warp N+2 (see the map block above). No script check gates the route - it is the `.blk` geometry plus the four boulders | -| Ice Path B3F Nevermeltice at (5, 7) | `maps/IcePathB3F.asm` object `ICEPATHB3F_ROCK` at (6, 6), `SPRITEMOVEDATA_SMASHABLE_ROCK`, `jumpstd SmashRockScript` | ROCK SMASH in the party; **no badge check** (`HasRockSmash` only calls `CheckPartyMove`) | smash it | -| Dragon's Den B1F, reaching the Dragon Fang | whirlpool block in `maps/DragonsDenB1F.blk`; `data/collision/field_move_blocks.asm:52` `WhirlpoolBlockPointers.johto` = facing block `$07` -> replacement `$36` | SURF (`ENGINE_FOGBADGE`, `engine/events/overworld.asm:340`) and WHIRLPOOL (`ENGINE_GLACIERBADGE`, `:1077` `WhirlpoolFunction.TryWhirlpool`) | the one `$07` block on this map is block (5, 10) = walk cells x 10-11, y 20-21 | -| Waterfall as a field move | `engine/events/overworld.asm:611` `WaterfallFunction` -> `:618 ld de, ENGINE_RISINGBADGE / farcall CheckBadge` | `ENGINE_RISINGBADGE` | granted by `DragonsDenB1FDragonFangScript`, i.e. HM07 picked up in the Ice Path is unusable for the whole of this section | -| Rising Badge itself | `maps/BlackthornGym1F.asm` `BlackthornGymClairScript` deliberately does **not** `setflag ENGINE_RISINGBADGE` | beating Clair is not sufficient | `maps/DragonsDenB1F.asm` `DragonsDenB1FDragonFangScript` | -| Master Ball from Elm | `maps/ElmsLab.asm:60` `checkflag ENGINE_RISINGBADGE / iftrue ElmGiveMasterBallScript` | `ENGINE_RISINGBADGE` | `specialphonecall SPECIALCALL_MASTERBALL` from the Den queues the phone call; the item is picked up in Elm's Lab (next section) | -| Dragon Fang bag space | `DragonsDenB1FDragonFangScript` `giveitem DRAGON_FANG / iffalse .BagFullDragonFang` | free bag slot | a full bag silently aborts the entire badge scene; the walkthrough's "deposit any extraneous items" advice is load bearing here | - -## 4. Bot checklist - -Coordinates are asm map coordinates (`object_event` / `warp_event` values). -"Pre" = precondition flag, "Post" = postcondition flag. - -1. `MAP_MAHOGANY_TOWN` - Fly, heal, walk east across the map connection to - `MAP_ROUTE_44`. Pre: `EVENT_CLEARED_RADIO_TOWER` (else the Blackthorn gym stays - blocked later). -2. `MAP_ROUTE_44` - battle `TrainerPsychicPhil` at (10, 9), sight 3. - Post: `EVENT_BEAT_PSYCHIC_PHIL`. -3. `MAP_ROUTE_44` - walk to (9, 5) and talk to `ROUTE44_FRUIT_TREE` - -> `BURNT_BERRY`. -4. `MAP_ROUTE_44` - walk to (30, 8), pick up `Route44MaxRevive` - (`MAX_REVIVE`, not Max Repel). Post: `EVENT_ROUTE_44_MAX_REVIVE`. -5. `MAP_ROUTE_44` - battle `TrainerFisherEdgar` at (19, 13), - `TrainerCooltrainerfCybil` at (31, 14), `TrainerCooltrainermAllen` at (41, 15). -6. `MAP_ROUTE_44` - pick up `Route44UltraBall` at (43, 2). - Post: `EVENT_ROUTE_44_ULTRA_BALL`. Optional: hidden `ELIXER` at (32, 9). -7. `MAP_ROUTE_44` - battle `TrainerPokemaniacZach` at (35, 2), - `TrainerFisherWilton1` at (42, 5) (register `PHONE_FISHER_WILTON`), - `TrainerBirdKeeperVance1` at (50, 7) (register `PHONE_BIRDKEEPER_VANCE`). -8. Party check before the cave: needs STRENGTH (`ENGINE_PLAINBADGE` already held) - and ROCK SMASH. All five Ice Path maps are `PALETTE_NITE` - (`data/maps/maps.asm:131-135`), not `PALETTE_DARK` - (`constants/map_data_constants.asm:38`, which is what - `DarkCaveBlackthornEntrance` uses at `data/maps/maps.asm:149`), so FLASH is - not required. -9. `MAP_ROUTE_44` (56, 7) -> warp 1 -> `MAP_ICE_PATH_1F` (4, 19). -10. `MAP_ICE_PATH_1F` - slide to (31, 7), take `IcePath1FHMWaterfall`. - Post: `EVENT_GOT_HM07_WATERFALL`. The move is unusable until step 30. -11. `MAP_ICE_PATH_1F` (37, 5) -> warp 3 -> `MAP_ICE_PATH_B1F` (3, 15). -12. `MAP_ICE_PATH_B1F` - use STRENGTH, push `ICEPATHB1F_BOULDER1` from (11, 7) - onto warp 3 at (11, 2). Post: `EVENT_BOULDER_IN_ICE_PATH_1` set, - `EVENT_BOULDER_IN_ICE_PATH_1A` cleared. -13. Push `ICEPATHB1F_BOULDER2` from (7, 8) onto warp 4 at (4, 7). - Post: `EVENT_BOULDER_IN_ICE_PATH_2` / `_2A`. -14. Push `ICEPATHB1F_BOULDER3` from (8, 9) onto warp 5 at (5, 12). - Post: `EVENT_BOULDER_IN_ICE_PATH_3` / `_3A`. -15. Push `ICEPATHB1F_BOULDER4` from (17, 7) onto warp 6 at (12, 13). - Post: `EVENT_BOULDER_IN_ICE_PATH_4` / `_4A`. - (STRENGTH must be re-activated after every map load - `ResetBikeFlags`.) -16. Drop through any hole, or take warp 2 at (17, 3) -> - `MAP_ICE_PATH_B2F_MAHOGANY_SIDE` (17, 1). -17. `MAP_ICE_PATH_B2F_MAHOGANY_SIDE` - pick up `FULL_HEAL` at (8, 9) and - `MAX_POTION` at (0, 2); optional hidden `CARBOS` at (0, 17). -18. Warp 2 at (9, 11) -> `MAP_ICE_PATH_B3F` (3, 5). -19. `MAP_ICE_PATH_B3F` - ROCK SMASH `ICEPATHB3F_ROCK` at (6, 6), take - `NEVERMELTICE` at (5, 7). Post: `EVENT_ICE_PATH_B3F_NEVERMELTICE`. -20. Warp 2 at (15, 5) -> `MAP_ICE_PATH_B2F_BLACKTHORN_SIDE` (3, 3). -21. Take `TM_REST` (TM44) at (8, 16). Post: - `EVENT_ICE_PATH_B2F_BLACKTHORN_SIDE_TM_REST`. Optional hidden `ICE_HEAL` at (2, 10). -22. Warp 1 at (3, 15) -> `MAP_ICE_PATH_B1F` (11, 27) (south region). -23. Take `IRON` at (5, 35). Post: `EVENT_ICE_PATH_B1F_IRON`. Optional hidden - `MAX_POTION` at (15, 31). -24. Warp 7 at (5, 25) -> `MAP_ICE_PATH_1F` (37, 13) (south region). -25. Take `PP_UP` at (32, 23). Post: `EVENT_ICE_PATH_1F_PP_UP`. -26. Warp 2 at (36, 27) -> `MAP_BLACKTHORN_CITY` (36, 9). - Post: `ENGINE_FLYPOINT_BLACKTHORN` (via `MAPCALLBACK_NEWMAP`). -27. `MAP_BLACKTHORN_CITY` (21, 29) -> Pokecenter, heal. (15, 29) -> Mart, - buy from `MartBlackthorn`. Optional (29, 23) Emy trade (needs a **female** - Dragonair), (9, 31) Move Deleter. -28. `MAP_BLACKTHORN_CITY` (18, 11) -> `MAP_BLACKTHORN_GYM_1F` (4, 17). - Pre: `EVENT_BLACKTHORN_CITY_SUPER_NERD_BLOCKS_GYM` set. -29. Gym: battle `TrainerCooltrainermPaul` (1, 14), take warp 3 at (1, 7) to 2F; - battle `TrainerCooltrainerfFran` (4, 11) and `TrainerCooltrainermCody` (4, 1); - push `BLACKTHORNGYM2F_BOULDER2` (2, 3) onto warp 3 (2, 5), - `BLACKTHORNGYM2F_BOULDER3` (6, 16) onto warp 4 (8, 7), - `BLACKTHORNGYM2F_BOULDER1` (8, 2) onto warp 5 (8, 3); use boulders 4, 5, 6 - ((3, 3), (6, 1), (8, 14)) as bridge pieces. Back on 1F battle - `TrainerCooltrainermMike` (6, 6) and `TrainerCooltrainerfLola` (9, 2). - All five are force-set by the Clair script anyway, so a bot may skip them. -30. Talk to `BLACKTHORNGYM1F_CLAIR` at (5, 3) -> `loadtrainer CLAIR, CLAIR1`. - Post: `EVENT_BEAT_CLAIR`, plus `EVENT_BLACKTHORN_CITY_GRAMPS_BLOCKS_DRAGONS_DEN` - set and `..._NOT_BLOCKING_DRAGONS_DEN` cleared. **No badge yet.** -31. Bag check: at least one free slot (see the `.BagFullDragonFang` trap). -32. `MAP_BLACKTHORN_CITY` (20, 1) -> `MAP_DRAGONS_DEN_1F` (3, 5). - Ladder (3, 3) -> (5, 13), then (5, 15) -> `MAP_DRAGONS_DEN_B1F` (20, 3). -33. `MAP_DRAGONS_DEN_B1F` - SURF, dissolve the whirlpool at walk cells - (10-11, 20-21), surf east to (35, 16) and take the Dragon Fang. - Post: `EVENT_DRAGONS_DEN_B1F_DRAGON_FANG`, `ENGINE_RISINGBADGE`, - `EVENT_GOT_TM24_DRAGONBREATH`, `SPECIALCALL_MASTERBALL` queued. - Optional hidden items at (31, 4), (21, 17), (31, 15). -34. Exit via warp 1 at (20, 3). WATERFALL is now usable. The Elm phone call - fires outside; the Master Ball pickup in `MAP_ELMS_LAB` is the next section. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers, dimensions, warps/coord/bg/object event tables for every map above | `src/import/RomExtractorGen2.lua` (`warps`, `coordEvents`, `bgEvents`, `objects` at lines 787-862, consumed at 973-975) | implemented (data-driven from the ROM, so no per-map porting is needed) | -| Map script execution (`checkevent`, `setevent`, `giveitem`, `verbosegiveitem`, `loadtrainer`, `startbattle`, `applymovement`, `moveobject`, `changeblock`, `earthquake`, `writecmdqueue`, `trade`, `specialphonecall`) | `src/script/gen2/Vm.lua` (490, 806, 1002, 1044, 1292, 1364, 1445) | implemented | -| Ice Path B1F stone table (boulder -> hole -> fall) | `src/world/gen2/CmdQueue.lua:212-222` (`ICE_PATH_B1F`, warps 2-5 zero-based against objects 2-5, events 1805-1808) | implemented; covered by `tests/drivers/gold_icepath_boulder.lua` and `tests/drivers/gold_map_callbacks.lua:167` | -| Blackthorn Gym 2F stone table | `src/world/gen2/CmdQueue.lua:227-230` (warps 5/3/4 -> objects 4/5/6, matching the scrambled asm order) | implemented; no dedicated driver | -| Blackthorn Gym 1F `MAPCALLBACK_TILES` fallen-boulder `changeblock` | `src/script/gen2/Vm.lua:1002` | implemented (generic `changeblock`) | -| STRENGTH badge gate + `ResetBikeFlags` clearing STRENGTH on every map load | `src/world/gen2/FieldMoves.lua:108` (`STRENGTH = "PLAIN"`), `src/world/gen2/World.lua:5610-5613` | implemented | -| WHIRLPOOL badge gate + block replacement | `src/world/gen2/FieldMoves.lua:109,247,540-556,624` | implemented | -| WATERFALL badge gate (`ENGINE_RISINGBADGE`) | `src/world/gen2/FieldMoves.lua:110,253-266,528-538` | implemented | -| ROCK SMASH (`HasRockSmash` inversion) | `src/script/gen2/CallAsm.lua:344-350` | implemented | -| **Ice tile sliding (`STEP_ICE`)** | `src/world/gen2/Permissions.lua:128,142` defines `ICE = {0x23, 0x2b}` but the only caller is `src/world/gen2/FieldMoves.lua:169`; there is no `STEP_ICE` arm in `World:movePlayer` (`src/world/gen2/World.lua:5876`) and no `CheckStandingOnIce` equivalent anywhere in `src/` | **missing** - this is the single biggest gap for this section, because every Ice Path floor is an ice-slide maze. A bot can push boulders but cannot traverse the mazes as the cart does | -| `ENGINE_FLYPOINT_BLACKTHORN` / Fly to Blackthorn | `src/world/gen2/FieldMoves.lua:354` (`LANDMARK_BLACKTHORN_CITY`, `SPAWN_BLACKTHORN`, flag 74) | implemented | -| Emy's NPC trade (`NPC_TRADE_EMY`, female Dragonair) | `src/core/gen2/NpcTrade.lua` (gender check at :91-93), `src/script/gen2/Vm.lua:1292` `trade` opcode | implemented | -| Move Deleter (`special MoveDeletion`) | `src/script/gen2/Specials.lua:637` `H.MoveDeletion`, `src/ui/gen2/MoveDeleter.lua` | implemented | -| Blackthorn Mart (`pokemart MARTTYPE_STANDARD, MART_BLACKTHORN`) | `src/ui/gen2/MartMenu.lua` + extractor mart tables | implemented (generic) | -| Vance / Wilton phone registration and rematch | `src/core/gen2/Phone.lua:210-215` (ids 32 and 33), rematch flags at `:451-452` | implemented | -| `specialphonecall SPECIALCALL_MASTERBALL` -> Elm's call | `src/script/gen2/Vm.lua:1364-1371`, `src/core/gen2/Phone.lua:402-403` (`[8] = SPECIALCALL_MASTERBALL`, condition `outside`, `ElmPhoneCallerScript`) | implemented | -| Rising Badge on the trainer card / badge list | `src/ui/gen2/TrainerCard.lua:64,74`, `src/world/gen2/FieldMoves.lua:118` | implemented | -| Clair battle, gym leader music / party | generic battle path `src/battle/gen2/Battle.lua` + extracted `data/trainers/parties.asm` | implemented (generic); no Blackthorn-specific driver exists under `tests/drivers/gold_*.lua` | -| End-to-end scripted run of this section | - | **missing** - the only relevant driver is `tests/drivers/gold_icepath_boulder.lua`, which teleports directly to `ICE_PATH_B1F` (9, 2) and tests one boulder | - -## 6. Unresolved / verify by hand - -- **"Max Repel" on Route 44.** The walkthrough says "then the item east of that - trainer (Max Repel)". The asm item ball at (30, 8) is - `Route44MaxRevive: itemball MAX_REVIVE`. There is no Max Repel object on - `MAP_ROUTE_44`; Max Repel is a Blackthorn Mart stock item - (`data/items/marts.asm:224`). Treat the walkthrough as wrong. -- **Route 44 item list.** The section header lists only "Burnt Berry, Ultra Ball" - but the map also has the Max Revive ball and a hidden Elixer at (32, 9). -- **Psychic Phil's Kadabra level.** Walkthrough says Level 24; `data/trainers/parties.asm` - PSYCHIC_T (8) says `db 26, KADABRA`. The Natu is L24 in both. -- **"Protein" in the Ice Path.** The walkthrough's final Ice Path paragraph says - "Go up even more steps to get the Protein, then slide down at the very left to - grab a PP Up." No `PROTEIN` appears in any of the five Ice Path map files, and - the walkthrough's own item list for the Ice Path does not include it. The only - vitamins in the Ice Path are `IRON` (B1F, (5, 35)) and the hidden `CARBOS` - (B2F Mahogany Side, (0, 17)). Unresolved. -- **Ice Path item list omissions.** The asm additionally has `MAX_POTION` (item - ball, B2F Mahogany Side (0, 2)), hidden `MAX_POTION` (B1F (15, 31)), hidden - `CARBOS` (B2F Mahogany (0, 17)) and hidden `ICE_HEAL` (B2F Blackthorn (2, 10)). -- **Ice maze direction strings.** Every "right, down, right, down" style route in - the walkthrough is a property of the `.blk` block layout and the ice-tile - collision values, not of any table in the map asm. None of them were verified; - a bot must derive them from `maps/IcePath*.blk` plus `CheckIceTile` - (`engine/overworld/player_movement.asm:274`). -- **Boulder push counts.** Likewise, "move the bottom boulder right, then 6 - squares down..." for both the Ice Path B1F and Blackthorn Gym 2F puzzles is - block-geometry, not script data. What *is* pinned down is the required - destination warp for each boulder (see the two stone tables above); the paths - between were not verified. -- **Dragon's Den whirlpool.** The walkthrough says "Make sure you have ... Surf - and Waterfall". The Den needs SURF and **WHIRLPOOL**; there is no waterfall - block on `MAP_DRAGONS_DEN_B1F` and Waterfall is not even usable until the badge - this trip awards. The single whirlpool block (`$07` in `TILESET_JOHTO`) is at - block (5, 10) of `maps/DragonsDenB1F.blk`; whether that is the only crossing - the route needs was not verified beyond the block scan. -- **Clair's party order.** Walkthrough lists Dragonair, Dragonair, Kingdra, - Dragonair. `ClairGroup` in `data/trainers/parties.asm:67` is Dragonair, - Dragonair, Dragonair, Kingdra. -- **Fisher Edgar's Remoraid levels.** Walkthrough says two Level 25 Remoraid; - the asm agrees, but note both carry `LOCK_ON / PSYBEAM / AURORA_BEAM / - BUBBLEBEAM` rather than default moves. -- **"use the warps to return to the entrance".** The 1F <-> 2F warp pairs - (1F 3/4 <-> 2F 1/2, 1F 5/6/7 <-> 2F 3/4/5) are transcribed above, but which - combination the walkthrough means by "use the warps" is ambiguous. -- **Money rewards.** The G values in the walkthrough (832G, 1000G, 1200G, ...) - come from `base money x level`, computed at runtime in - `engine/battle/`; no static table was located, so none were verified. -- **Section numbering.** The file is `section-13-*` but its own heading reads - `---- 19 > Ice Path and Blackthorn City Gym ----`. Assumed to be the FAQ's - internal chapter number, not a different section. diff --git a/docs/gold-walkthrough/asm-walk/section-14-ho-oh-gold-and-lugia-silver.md b/docs/gold-walkthrough/asm-walk/section-14-ho-oh-gold-and-lugia-silver.md deleted file mode 100644 index 6db982be..00000000 --- a/docs/gold-walkthrough/asm-walk/section-14-ho-oh-gold-and-lugia-silver.md +++ /dev/null @@ -1,1090 +0,0 @@ -# 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. diff --git a/docs/gold-walkthrough/asm-walk/section-15-mount-mortar-and-dark-cave.md b/docs/gold-walkthrough/asm-walk/section-15-mount-mortar-and-dark-cave.md deleted file mode 100644 index a95ff62a..00000000 --- a/docs/gold-walkthrough/asm-walk/section-15-mount-mortar-and-dark-cave.md +++ /dev/null @@ -1,762 +0,0 @@ -# Section 15 - Mount Mortar and Dark Cave - -Source: `../section-15-mount-mortar-and-dark-cave.txt` -Maps covered: `MAP_MOUNT_MORTAR_1F_OUTSIDE`, `MAP_MOUNT_MORTAR_1F_INSIDE`, -`MAP_MOUNT_MORTAR_2F_INSIDE`, `MAP_MOUNT_MORTAR_B1F`, -`MAP_DARK_CAVE_BLACKTHORN_ENTRANCE`, `MAP_DARK_CAVE_VIOLET_ENTRANCE`. -Touched in passing (owned by neighbouring sections): `MAP_MAHOGANY_TOWN`, -`MAP_ROUTE_42`, `MAP_BLACKTHORN_CITY`, `MAP_ROUTE_45`, `MAP_ROUTE_31`, -`MAP_ROUTE_46`. - -Badges / key milestones in this section: **no badge**. The milestones are -`EVENT_BEAT_BLACKBELT_KIYO` + `EVENT_GOT_TYROGUE_FROM_KIYO` (a free level 10 -`TYROGUE`, the only one in the game) and `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE`. -Everything else here is optional loot. Nothing in this section gates any later -section. - -A note on coordinates: every table below is transcribed verbatim from the map's -`_MapEvents` block in the named `maps/*.asm` file. `warp_event`, `coord_event`, -`bg_event` and `object_event` all share one coordinate space (the -`object_event` macro in `macros/scripts/maps.asm` applies the +4 border offset -itself), so the numbers are directly comparable. A map declared -`map_const NAME, W, H` spans x in `0 .. 2W-1` and y in `0 .. 2H-1`. - -A note on the `event flag` column of `object_event`: -`engine/overworld/map_objects_2.asm` `CheckObjectFlag` **hides** the object when -the flag is set, and `-1` means always visible. So for a Poke Ball, "flag set = -already taken = gone". - -A note on darkness: all six maps in this section are `PALETTE_DARK` in -`data/maps/maps.asm`. `engine/tilesets/timeofday_pals.asm` -`ReplaceTimeOfDayPals` (`23:43e9`) sends `PALETTE_DARK` down `.NeedsFlash`, -which writes `DARKNESS_PALSET` unless `STATUSFLAGS_FLASH_F` is set in -`wStatusFlags`. That bit is set by `BlindingFlash` (`engine/events/field_moves.asm`) -and cleared by `ResetFlashIfOutOfCave` (`home/flag.asm`, `00:2f1d`) on any map -whose `wEnvironment` is `ROUTE` or `TOWN`. **A bot must re-cast Flash every time -it re-enters, including after every Fly.** - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_MAHOGANY_TOWN` | `data/maps/maps.asm:74`, `data/maps/spawn_points.asm` `spawn MAHOGANY_TOWN, 15, 14` | Fly (`ENGINE_FLYPOINT_MAHOGANY`) | west map connection -> `ROUTE_42` | heal, buy Max Repels / Escape Ropes, drop to 5 party members so Tyrogue fits | -| 2 | `MAP_ROUTE_42` | `maps/Route42.asm` | east connection from Mahogany, or warps 1/2 at (0,8)/(0,9) from the Ecruteak gate | warp 3 (10,5) west entrance, warp 4 (28,9) middle entrance, warp 5 (46,7) east entrance -> `MOUNT_MORTAR_1F_OUTSIDE` warps 1/2/3 | "three cave entrances to Mt. Mortar" | -| 3 | `MAP_MOUNT_MORTAR_1F_OUTSIDE` | `maps/MountMortar1FOutside.asm` | warps 1/2/3 at (3,33)/(17,33)/(37,33) | warp 4 (17,5) -> 2F after Waterfall; warp 7 (17,29) -> B1F | all three Route 42 doors land on this one map; the Surf lake and the waterfall live here | -| 4 | `MAP_MOUNT_MORTAR_1F_INSIDE` | `maps/MountMortar1FInside.asm` | 1F Outside warps 5/6 (11,21)/(29,21) or 8/9 (7,13)/(33,13) | warp 5 (3,19) -> B1F; warp 6 (9,9) -> 2F | the "giant room" with the Strength boulder, Escape Rope and Hyper Potion | -| 5 | `MAP_MOUNT_MORTAR_2F_INSIDE` | `maps/MountMortar2FInside.asm` | warp 1 (17,33) from 1F Outside warp 4 (top of the waterfall) | warp 2 (3,5) -> `MOUNT_MORTAR_1F_INSIDE` warp 6 | Rare Candy, Max Potion, TM40, Dragon Scale, Elixer, Escape Rope, hidden Full Restore | -| 6 | `MAP_MOUNT_MORTAR_B1F` | `maps/MountMortarB1F.asm` | warp 1 (3,3) from 1F Inside warp 5 | Escape Rope, or Strength the boulder at (9,10) and take warp 2 (19,29) -> 1F Outside warp 7 | **Blackbelt Kiyo, then the free Tyrogue** | -| 7 | `MAP_BLACKTHORN_CITY` | `data/maps/maps.asm:187`, `data/maps/spawn_points.asm` `spawn BLACKTHORN_CITY, 21, 30` | Fly (`ENGINE_FLYPOINT_BLACKTHORN`) | south map connection (`data/maps/attributes.asm:161` `connection south, Route45, ROUTE_45, 0`) | heal, then head south | -| 8 | `MAP_ROUTE_45` | `maps/Route45.asm` | north connection from Blackthorn | warp 1 (2,5) -> `DARK_CAVE_BLACKTHORN_ENTRANCE` warp 1 | "enter the Dark Cave to the left before there are even any jumps" | -| 9 | `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE` | `maps/DarkCaveBlackthornEntrance.asm` | warp 1 (23,3) | warp 2 (3,25) -> `DARK_CAVE_VIOLET_ENTRANCE` warp 2 | Blackglasses NPC, Revive, TM13 Snore, Wobbuffet | -| 10 | `MAP_DARK_CAVE_VIOLET_ENTRANCE` | `maps/DarkCaveVioletEntrance.asm` | warp 2 (17,1) | warp 1 (3,15) -> `ROUTE_31` warp 3 | "surf down, rock smash the rock, grab the Potion, head out onto Route 31" | -| 11 | `MAP_ROUTE_31` | `maps/Route31.asm` | warp 3 (34,5) | Fly back to Blackthorn | walkthrough exit | - -Spill into the next section: the walkthrough's video links name Blackthorn Gym, -Route 45/46, Tohjo Falls, Routes 26/27, Victory Road and the Elite Four, but its -**prose** stops at "head back south to actually take Route 45". `MAP_ROUTE_45` -proper, `MAP_ROUTE_46`, `MAP_BLACKTHORN_GYM` and everything past them belong to -the next section; only the Route 45 warp that reaches Dark Cave is documented -here. - -`MAP_DARK_CAVE_VIOLET_ENTRANCE` is also reachable from `ROUTE_46` warp 3 at -(14,5) (-> Dark Cave warp 3 at (35,33)) and from `ROUTE_31` warp 3; the earlier -sections that visit Route 31 already touch the north end of this map. This -section only walks it top-to-bottom-left, from the Blackthorn side out to -Route 31. - ---- - -## 2. Maps - -### MAP_MOUNT_MORTAR_1F_OUTSIDE - -- Script: `maps/MountMortar1FOutside.asm` -- Blocks: `maps/MountMortar1FOutside.blk` -- Header: `data/maps/maps.asm:127` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:114` `map_const MOUNT_MORTAR_1F_OUTSIDE, 20, 18` - (x 0..39, y 0..35), map id 49, group 3 -- Attributes: `data/maps/attributes.asm:446`, border block `$09`, **no connections** -- Map events block: `MountMortar1FOutside_MapEvents` = `46:5db9` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 33 | `ROUTE_42` | 3 | -| 2 | 17 | 33 | `ROUTE_42` | 4 | -| 3 | 37 | 33 | `ROUTE_42` | 5 | -| 4 | 17 | 5 | `MOUNT_MORTAR_2F_INSIDE` | 1 | -| 5 | 11 | 21 | `MOUNT_MORTAR_1F_INSIDE` | 1 | -| 6 | 29 | 21 | `MOUNT_MORTAR_1F_INSIDE` | 2 | -| 7 | 17 | 29 | `MOUNT_MORTAR_B1F` | 2 | -| 8 | 7 | 13 | `MOUNT_MORTAR_1F_INSIDE` | 3 | -| 9 | 33 | 13 | `MOUNT_MORTAR_1F_INSIDE` | 4 | - -**Coord events** (`def_coord_events`) - -`def_coord_events` is **empty**. No scripted trip-wires anywhere in Mt. Mortar. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 25 | 22 | `BGEVENT_ITEM` | `MountMortar1FOutsideHiddenHyperPotion` -> `hiddenitem HYPER_POTION, EVENT_MOUNT_MORTAR_1F_OUTSIDE_HIDDEN_HYPER_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMORTAR1FOUTSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 13 | 15 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FOutsideEther` (`itemball ETHER`) | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_ETHER` | -| `MOUNTMORTAR1FOUTSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 31 | 18 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FOutsideGuardSpec` (`itemball GUARD_SPEC`) | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE` | - -Note the flag/item mismatch on the second ball: the item is `GUARD_SPEC` but the -flag is named `..._REVIVE`. That is verbatim from the disassembly - the label -name is cosmetic, the flag bit is what matters. - -**Scripts of interest** - -- `MountMortar1FOutsideEther` / `MountMortar1FOutsideGuardSpec` - bare - `itemball ITEM` rows (two bytes, no bytecode). Picked up by the shared - `FindItemInBallScript` path, not by a per-map script. -- `MountMortar1FOutsideHiddenHyperPotion` - `hiddenitem` data behind a - `BGEVENT_ITEM` row; A-pressed on (25,22) or found by the ITEMFINDER. - -**Wild encounters** - -`data/wild/johto_grass.asm:686` `def_grass_wildmons MOUNT_MORTAR_1F_OUTSIDE`, -encounter rate `6 percent` for all three of morn/day/nite, and **all three -slot lists are identical**: - -| slot | lvl | species | -|---|---|---| -| 1 | 13 | ZUBAT | -| 2 | 15 | ZUBAT | -| 3 | 14 | MACHOP | -| 4 | 14 | RATTATA | -| 5 | 14 | GEODUDE | -| 6 | 16 | RATTATA | -| 7 | 15 | MARILL | - -`data/wild/johto_water.asm:58` `def_water_wildmons MOUNT_MORTAR_1F_OUTSIDE`, -rate `4 percent`: 20 GOLDEEN / 15 GOLDEEN / 20 SEAKING. - -Fishing: `FISHGROUP_LAKE`, `data/wild/fish.asm:57` - Old rod MAGIKARP 10 / -GOLDEEN 10; Good rod MAGIKARP 20 / GOLDEEN 20 / `time_group 4`; Super rod -GOLDEEN 40 / `time_group 5` / MAGIKARP 40 / SEAKING 40. - -No headbutt or rock-smash table (`data/wild/treemon_maps.asm` does not list this -map). - ---- - -### MAP_MOUNT_MORTAR_1F_INSIDE - -- Script: `maps/MountMortar1FInside.asm` -- Blocks: `maps/MountMortar1FInside.blk` -- Header: `data/maps/maps.asm:128` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:115` `map_const MOUNT_MORTAR_1F_INSIDE, 20, 27` - (x 0..39, y 0..53), map id 50, group 3 -- Attributes: `data/maps/attributes.asm:447`, border block `$09`, **no connections** -- Map events block: `MountMortar1FInside_MapEvents` = `46:5e19` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 47 | `MOUNT_MORTAR_1F_OUTSIDE` | 5 | -| 2 | 29 | 47 | `MOUNT_MORTAR_1F_OUTSIDE` | 6 | -| 3 | 5 | 39 | `MOUNT_MORTAR_1F_OUTSIDE` | 8 | -| 4 | 33 | 41 | `MOUNT_MORTAR_1F_OUTSIDE` | 9 | -| 5 | 3 | 19 | `MOUNT_MORTAR_B1F` | 1 | -| 6 | 9 | 9 | `MOUNT_MORTAR_2F_INSIDE` | 2 | - -**Coord events** - empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 31 | 9 | `BGEVENT_ITEM` | `MountMortar1FInsideHiddenMaxRepel` -> `hiddenitem MAX_REPEL, EVENT_MOUNT_MORTAR_1F_INSIDE_HIDDEN_MAX_REPEL` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMORTAR1FINSIDE_BOULDER` | `SPRITE_BOULDER` | 21 | 43 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `MountMortar1FBoulder` (`jumpstd StrengthBoulderScript`) | `-1` | -| `MOUNTMORTAR1FINSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 33 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideEscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_ESCAPE_ROPE` | -| `MOUNTMORTAR1FINSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 16 | 10 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideMaxRevive` (`itemball MAX_REVIVE`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_MAX_REVIVE` | -| `MOUNTMORTAR1FINSIDE_POKE_BALL3` | `SPRITE_POKE_BALL` | 12 | 21 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideHyperPotion` (`itemball HYPER_POTION`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_HYPER_POTION` | - -**Scripts of interest** - -- `MountMortar1FBoulder` - one line: `jumpstd StrengthBoulderScript`. - `engine/events/std_scripts.asm:196` `StrengthBoulderScript: farsjump AskStrengthScript` - (`03:4d4e`). See section 3 for the gate. - -**Wild encounters** - -`data/wild/johto_grass.asm:714` `def_grass_wildmons MOUNT_MORTAR_1F_INSIDE`, -rate `6 percent` morn/day/nite, all three lists identical: - -| slot | lvl | species | -|---|---|---| -| 1 | 13 | GEODUDE | -| 2 | 13 | MACHOP | -| 3 | 15 | GEODUDE | -| 4 | 14 | RATTATA | -| 5 | 15 | MACHOP | -| 6 | 14 | ZUBAT | -| 7 | 14 | ZUBAT | - -**There is no `def_water_wildmons MOUNT_MORTAR_1F_INSIDE`** in -`data/wild/johto_water.asm` - only 1F Outside, 2F Inside and B1F have water -tables. Fishing group is still `FISHGROUP_LAKE`. - ---- - -### MAP_MOUNT_MORTAR_2F_INSIDE - -- Script: `maps/MountMortar2FInside.asm` -- Blocks: `maps/MountMortar2FInside.blk` -- Header: `data/maps/maps.asm:129` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:116` `map_const MOUNT_MORTAR_2F_INSIDE, 20, 18` - (x 0..39, y 0..35), map id 51, group 3 -- Attributes: `data/maps/attributes.asm:448`, border block `$09`, **no connections** -- Map events block: `MountMortar2FInside_MapEvents` = `46:5e87` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 33 | `MOUNT_MORTAR_1F_OUTSIDE` | 4 | -| 2 | 3 | 5 | `MOUNT_MORTAR_1F_INSIDE` | 6 | - -**Coord events** - empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 19 | 6 | `BGEVENT_ITEM` | `MountMortar2FInsideHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_MOUNT_MORTAR_2F_INSIDE_HIDDEN_FULL_RESTORE` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMORTAR2FINSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 31 | 23 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideMaxPotion` (`itemball MAX_POTION`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_MAX_POTION` | -| `MOUNTMORTAR2FINSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideRareCandy` (`itemball RARE_CANDY`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_RARE_CANDY` | -| `MOUNTMORTAR2FINSIDE_POKE_BALL3` | `SPRITE_POKE_BALL` | 19 | 17 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideTMDefenseCurl` (`itemball TM_DEFENSE_CURL`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_TM_DEFENSE_CURL` | -| `MOUNTMORTAR2FINSIDE_POKE_BALL4` | `SPRITE_POKE_BALL` | 14 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideDragonScale` (`itemball DRAGON_SCALE`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_DRAGON_SCALE` | -| `MOUNTMORTAR2FINSIDE_POKE_BALL5` | `SPRITE_POKE_BALL` | 8 | 9 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideElixer` (`itemball ELIXER`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_ELIXER` | -| `MOUNTMORTAR2FINSIDE_POKE_BALL6` | `SPRITE_POKE_BALL` | 28 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideEscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_ESCAPE_ROPE` | - -`TM_DEFENSE_CURL` is **TM40** by the `add_tm` ordering in -`constants/item_constants.asm:261`. The walkthrough calls it "TM40 Aerial Ace", -which is the HGSS remake's TM40 - see section 6. - -**Wild encounters** - -`data/wild/johto_grass.asm:742` `def_grass_wildmons MOUNT_MORTAR_2F_INSIDE`, -rate `6 percent` morn/day/nite, all three lists identical - this is the floor -the walkthrough's "Raticate / Machoke / Graveler" list is drawn from: - -| slot | lvl | species | -|---|---|---| -| 1 | 31 | GRAVELER | -| 2 | 32 | MACHOKE | -| 3 | 31 | GEODUDE | -| 4 | 30 | RATICATE | -| 5 | 28 | MACHOP | -| 6 | 30 | GOLBAT | -| 7 | 30 | GOLBAT | - -`data/wild/johto_water.asm:65` `def_water_wildmons MOUNT_MORTAR_2F_INSIDE`, -rate `2 percent`: 20 GOLDEEN / 25 GOLDEEN / 25 SEAKING. - ---- - -### MAP_MOUNT_MORTAR_B1F - -- Script: `maps/MountMortarB1F.asm` -- Blocks: `maps/MountMortarB1F.blk` -- Header: `data/maps/maps.asm:130` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:117` `map_const MOUNT_MORTAR_B1F, 20, 18` - (x 0..39, y 0..35), map id 52, group 3 -- Attributes: `data/maps/attributes.asm:449`, border block `$09`, **no connections** -- Map events block: `MountMortarB1F_MapEvents` = `46:60f0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `MOUNT_MORTAR_1F_INSIDE` | 5 | -| 2 | 19 | 29 | `MOUNT_MORTAR_1F_OUTSIDE` | 7 | - -**Coord events** - empty. Kiyo is a talk-to trigger, not a trip-wire. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 6 | `BGEVENT_ITEM` | `MountMortarB1FHiddenMaxRevive` -> `hiddenitem MAX_REVIVE, EVENT_MOUNT_MORTAR_B1F_HIDDEN_MAX_REVIVE` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMORTARB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 31 | 17 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortarB1FHyperPotion` (`itemball HYPER_POTION`) | `EVENT_MOUNT_MORTAR_B1F_HYPER_POTION` | -| `MOUNTMORTARB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 4 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortarB1FFullHeal` (`itemball FULL_HEAL`) | `EVENT_MOUNT_MORTAR_B1F_FULL_HEAL` | -| `MOUNTMORTARB1F_BOULDER` | `SPRITE_BOULDER` | 9 | 10 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `MountMortarB1FBoulder` (`jumpstd StrengthBoulderScript`) | `-1` | -| `MOUNTMORTARB1F_KIYO` | `SPRITE_BLACK_BELT` | 13 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_BROWN` | `MountMortarB1FKiyoScript` | `-1` | - -**Scripts of interest** - -`MountMortarB1FKiyoScript` (`46:5eec`) - the whole point of this section. Note -it is `OBJECTTYPE_SCRIPT`, **not** `OBJECTTYPE_TRAINER`: Kiyo has no `trainer` -header, no sight range, and never walks up to you. He must be talked to. - -``` -faceplayer -opentext -checkevent EVENT_GOT_TYROGUE_FROM_KIYO ; iftrue -> .GotTyrogue (46:5f27) -checkevent EVENT_BEAT_BLACKBELT_KIYO ; iftrue -> .BeatKiyo (46:5f0d) -writetext MountMortarB1FKiyoIntroText -waitbutton -closetext -winlosstext MountMortarB1FKiyoWinText, 0 -loadtrainer BLACKBELT_T, KIYO -startbattle -reloadmapafterbattle -setevent EVENT_BEAT_BLACKBELT_KIYO -opentext -.BeatKiyo: -writetext MountMortarB1FTyrogueRewardText -promptbutton -waitsfx -readvar VAR_PARTYCOUNT -ifequal PARTY_LENGTH, .NoRoom ; 46:5f2d -writetext MountMortarB1FReceiveMonText -playsound SFX_CAUGHT_MON -waitsfx -givepoke TYROGUE, 10 -setevent EVENT_GOT_TYROGUE_FROM_KIYO -.GotTyrogue: -writetext MountMortarB1FKiyoGotTyrogueText -waitbutton -closetext -end -``` - -Bot-relevant control flow: - -- The battle is unconditional on first talk (no `checkevent` guard before - `loadtrainer`), and the win-flag write is **after** `startbattle`, so a - blackout means the whole script re-runs from the top. -- `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH` is the "you have no room" - branch. `PARTY_LENGTH` is 6. **If the party is full, `EVENT_BEAT_BLACKBELT_KIYO` - is already set but `EVENT_GOT_TYROGUE_FROM_KIYO` is not**, so re-talking - re-enters at `.BeatKiyo` and offers the Tyrogue again. This is why the - walkthrough tells you to free a slot in advance - but it is recoverable, not - missable. -- `givepoke TYROGUE, 10` - level 10, no held item, no nickname prompt in this - form of the opcode. - -`MountMortarB1FBoulder` - `jumpstd StrengthBoulderScript`, same as 1F Inside. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BLACKBELT_KIYO` | `constants/event_flags.asm:684` | set by `MountMortarB1FKiyoScript` after `startbattle` | Kiyo will not fight again | -| `EVENT_GOT_TYROGUE_FROM_KIYO` | `constants/event_flags.asm:106` | set by `MountMortarB1FKiyoScript` after `givepoke` | the Tyrogue is banked; the script becomes pure flavour | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `KIYO` (`constants/trainer_constants.asm:500`) | `BLACKBELT_T` | 6th entry of `BlackbeltGroup` | `data/trainers/parties.asm:2455` `; BLACKBELT_T (6)` `db "KIYO@", TRAINERTYPE_NORMAL` / `db 34, HITMONLEE` / `db 34, HITMONCHAN` | `MountMortarB1FKiyoScript` | none - no `trainer` header, so no phone number, no rematch | - -`TRAINERTYPE_NORMAL` means **no custom moves and no held items**: both mons use -their level-34 learnset defaults. The walkthrough's "816G" prize and the -1012/1020 EXP figures were not verified against `data/trainers/attributes.asm` - -see section 6. - -**Wild encounters** - -`data/wild/johto_grass.asm:770` `def_grass_wildmons MOUNT_MORTAR_B1F`, rate -`6 percent` morn/day/nite, all three lists identical: - -| slot | lvl | species | -|---|---|---| -| 1 | 15 | ZUBAT | -| 2 | 17 | ZUBAT | -| 3 | 16 | RATTATA | -| 4 | 16 | MACHOP | -| 5 | 16 | GEODUDE | -| 6 | 16 | RATICATE | -| 7 | 16 | RATICATE | - -`data/wild/johto_water.asm:72` `def_water_wildmons MOUNT_MORTAR_B1F`, rate -`2 percent`: 20 GOLDEEN / 15 GOLDEEN / 20 SEAKING. - ---- - -### MAP_DARK_CAVE_BLACKTHORN_ENTRANCE - -- Script: `maps/DarkCaveBlackthornEntrance.asm` -- Blocks: `maps/DarkCaveBlackthornEntrance.blk` -- Header: `data/maps/maps.asm:149` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_DARK_CAVE`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:136` `map_const DARK_CAVE_BLACKTHORN_ENTRANCE, 15, 18` - (x 0..29, y 0..35), map id 71, group 3 -- Attributes: `data/maps/attributes.asm:468`, border block `$09`, **no connections** -- Map events block: `DarkCaveBlackthornEntrance_MapEvents` = `47:4489` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 3 | `ROUTE_45` | 1 | -| 2 | 3 | 25 | `DARK_CAVE_VIOLET_ENTRANCE` | 2 | - -**Coord events** - empty. - -**BG events** - `def_bg_events` is **empty**. No signs, no hidden items. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DARKCAVEBLACKTHORNENTRANCE_PHARMACIST` | `SPRITE_PHARMACIST` | 7 | 3 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `DarkCaveBlackthornEntrancePharmacistScript` | `-1` | -| `DARKCAVEBLACKTHORNENTRANCE_POKE_BALL1` | `SPRITE_POKE_BALL` | 21 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveBlackthornEntranceRevive` (`itemball REVIVE`) | `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_REVIVE` | -| `DARKCAVEBLACKTHORNENTRANCE_POKE_BALL2` | `SPRITE_POKE_BALL` | 7 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveBlackthornEntranceTMSnore` (`itemball TM_SNORE`) | `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_TM_SNORE` | - -`TM_SNORE` is **TM13** by the `add_tm` ordering in -`constants/item_constants.asm:233`, which matches the walkthrough's "TM 13". - -**Scripts of interest** - -`DarkCaveBlackthornEntrancePharmacistScript` (`47:436c`): - -``` -faceplayer -opentext -checkevent EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE ; iftrue -> .GotBlackglasses (47:4381) -writetext DarkCaveBlackthornEntrancePharmacistText1 -promptbutton -verbosegiveitem BLACKGLASSES -iffalse .PackFull ; 47:4385 -setevent EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE -.GotBlackglasses: -writetext DarkCaveBlackthornEntrancePharmacistText2 -waitbutton -.PackFull: -closetext -end -``` - -Bot-relevant: `verbosegiveitem` returns 0 when the pack is full, and the -`setevent` is **after** the give, so a full item pocket leaves the flag clear -and the NPC re-offers. Not missable. The NPC spins -(`SPRITEMOVEDATA_SPINRANDOM_SLOW`), so the bot must stand adjacent and press A; -`faceplayer` handles the turn. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE` | `constants/event_flags.asm:123` | `DarkCaveBlackthornEntrancePharmacistScript` | BLACKGLASSES banked; NPC becomes flavour text | -| `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_REVIVE` | `constants/event_flags.asm:1090` | object visibility mask | set = ball already taken | -| `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_TM_SNORE` | `constants/event_flags.asm:1091` | object visibility mask | set = TM13 already taken | - -**Wild encounters** - -`data/wild/johto_grass.asm:1545` `def_grass_wildmons DARK_CAVE_BLACKTHORN_ENTRANCE`, -rate `4 percent` morn/day/nite, all three lists identical - this is the -walkthrough's Wobbuffet spot: - -| slot | lvl | species | -|---|---|---| -| 1 | 23 | GEODUDE | -| 2 | 23 | ZUBAT | -| 3 | 25 | GRAVELER | -| 4 | 20 | WOBBUFFET | -| 5 | 25 | WOBBUFFET | -| 6 | 23 | GOLBAT | -| 7 | 23 | GOLBAT | - -`data/wild/johto_water.asm:114` `def_water_wildmons DARK_CAVE_BLACKTHORN_ENTRANCE`, -rate `2 percent`: 15 MAGIKARP / 10 MAGIKARP / 5 MAGIKARP. - -No `treemon_map` row for this map - **rock smash here yields nothing**, and -there are no smashable rock objects on it either. - ---- - -### MAP_DARK_CAVE_VIOLET_ENTRANCE - -- Script: `maps/DarkCaveVioletEntrance.asm` -- Blocks: `maps/DarkCaveVioletEntrance.blk` -- Header: `data/maps/maps.asm:148` -> `TILESET_DARK_CAVE`, `CAVE`, - `LANDMARK_DARK_CAVE`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_DARK`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:135` `map_const DARK_CAVE_VIOLET_ENTRANCE, 20, 18` - (x 0..39, y 0..35), map id 70, group 3 -- Attributes: `data/maps/attributes.asm:467`, border block `$09`, **no connections** -- Map events block: `DarkCaveVioletEntrance_MapEvents` = `47:42f5` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 15 | `ROUTE_31` | 3 | -| 2 | 17 | 1 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | 2 | -| 3 | 35 | 33 | `ROUTE_46` | 3 | - -**Coord events** - empty. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 26 | 3 | `BGEVENT_ITEM` | `DarkCaveVioletEntranceHiddenElixer` -> `hiddenitem ELIXER, EVENT_DARK_CAVE_VIOLET_ENTRANCE_HIDDEN_ELIXER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DARKCAVEVIOLETENTRANCE_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 8 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntrancePotion` (`itemball POTION`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_POTION` | -| `DARKCAVEVIOLETENTRANCE_ROCK1` | `SPRITE_ROCK` | 16 | 14 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` (`jumpstd SmashRockScript`) | `-1` | -| `DARKCAVEVIOLETENTRANCE_ROCK2` | `SPRITE_ROCK` | 27 | 6 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` | -| `DARKCAVEVIOLETENTRANCE_ROCK3` | `SPRITE_ROCK` | 7 | 14 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` | -| `DARKCAVEVIOLETENTRANCE_ROCK4` | `SPRITE_ROCK` | 36 | 31 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` | -| `DARKCAVEVIOLETENTRANCE_POKE_BALL2` | `SPRITE_POKE_BALL` | 36 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntranceFullHeal` (`itemball FULL_HEAL`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_FULL_HEAL` | -| `DARKCAVEVIOLETENTRANCE_POKE_BALL3` | `SPRITE_POKE_BALL` | 35 | 9 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntranceHyperPotion` (`itemball HYPER_POTION`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_HYPER_POTION` | - -Note the four rocks all share one script label; the smash `disappear LAST_TALKED` -in `RockSmashScript` acts on `hLastTalked`, so they are independent objects -despite the shared pointer. Rocks have **no event flag** (`-1`) - they come back -on every map reload. - -**Scripts of interest** - -`DarkCaveVioletEntranceRock` - `jumpstd SmashRockScript` -(`engine/events/std_scripts.asm:199` -> `farsjump AskRockSmashScript`, `03:4f60`). -`AskRockSmashScript` calls `HasRockSmash` (`03:4f7f`), which is **inverted** -(`wScriptVar = 1` when the party does NOT know ROCK SMASH); on 0 it prompts -yes/no and falls into `RockSmashScript` (`03:4f35`): -`playsound SFX_STRENGTH` / `earthquake 84` / `applymovementlasttalked MovementData_RockSmash` -/ `disappear LAST_TALKED` / `callasm RockMonEncounter` / `randomwildmon` / -`startbattle`. - -**Wild encounters** - -`data/wild/johto_grass.asm:1517` `def_grass_wildmons DARK_CAVE_VIOLET_ENTRANCE`, -rate `4 percent` morn/day/nite, all three lists identical: - -| slot | lvl | species | -|---|---|---| -| 1 | 3 | GEODUDE | -| 2 | 2 | ZUBAT | -| 3 | 2 | GEODUDE | -| 4 | 4 | GEODUDE | -| 5 | 3 | ZUBAT | -| 6 | 4 | ZUBAT | -| 7 | 4 | DUNSPARCE | - -`data/wild/johto_water.asm:107` `def_water_wildmons DARK_CAVE_VIOLET_ENTRANCE`, -rate `2 percent`: 15 MAGIKARP / 10 MAGIKARP / 5 MAGIKARP. - -**Rock smash encounters** - this map IS in `data/wild/treemon_maps.asm:46` -`RockMonMaps`: `treemon_map DARK_CAVE_VIOLET_ENTRANCE, TREEMON_SET_ROCK`. -`RockMonEncounter` (`2e:63a1`) rolls `RandomRange 10 < 4` (a 40% chance) and then -`SelectTreeMon` over `data/wild/treemons.asm:91` `TreeMonSet_Rock`: -90% KRABBY lvl 15, 10% SHUCKLE lvl 15. **This is the only Shuckle source in -Johto reachable in this section**, and the walkthrough does not mention it. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Darkness on all six maps | `engine/tilesets/timeofday_pals.asm` `ReplaceTimeOfDayPals` (`23:43e9`) `.NeedsFlash` -> `DARKNESS_PALSET`, because `data/maps/maps.asm` lines 127-130 / 148-149 all say `PALETTE_DARK` | FLASH cast, i.e. `STATUSFLAGS_FLASH_F` set in `wStatusFlags` | `engine/events/overworld.asm` `FlashFunction.CheckUseFlash` (`03:48f1`): `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET`. The bit is cleared by `home/flag.asm` `ResetFlashIfOutOfCave` (`00:2f1d`) on any `ROUTE`/`TOWN` map, so **re-cast after every exit and every Fly** | -| Mt. Mortar middle entrance (`ROUTE_42` warp 4 at (28,9)) | terrain: the tile is across water on Route 42 | SURF | `engine/events/overworld.asm` `SurfFunction` (`03:493b`), `.TrySurf` arm / `TrySurfOW` (`03:4a06`): `ENGINE_FOGBADGE` + a party member knowing SURF | -| The lake and the waterfall between 1F Outside and 2F | terrain on `MountMortar1FOutside.blk`; the only route to warp 4 at (17,5) | SURF, then WATERFALL | `WaterfallFunction.TryWaterfall` (`03:4af6`) / `TryWaterfallOW` (`03:4b5f`): `ENGINE_RISINGBADGE`, then `CheckMapCanWaterfall` - **player must be facing UP and the tile ABOVE (`wTileUp`) must be a waterfall tile** | -| `MOUNTMORTAR1FINSIDE_BOULDER` at (21,43) | `MountMortar1FBoulder` -> `jumpstd StrengthBoulderScript` -> `AskStrengthScript` (`03:4d4e`) | STRENGTH toggled on | `TryStrengthOW` (`03:4d7b`): party knows STRENGTH **and** `ENGINE_PLAINBADGE` **and** `BIKEFLAGS_STRENGTH_ACTIVE_F` set. `StrengthFunction.TryStrength` (`03:4cf1`) only checks the badge. Note `ResetBikeFlags` clears the active bit on **every map load** - Strength must be re-activated per floor | -| `MOUNTMORTARB1F_BOULDER` at (9,10) | `MountMortarB1FBoulder`, same std script | STRENGTH | as above. This is the walkthrough's "move the rock with HM Strength to get out"; the alternative is an Escape Rope | -| Kiyo's Tyrogue | `MountMortarB1FKiyoScript` `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .NoRoom` | 5 or fewer party members | free a slot and re-talk; `EVENT_BEAT_BLACKBELT_KIYO` stays set so the battle is not repeated | -| Dark Cave water crossings (both entrances) | terrain | SURF | `ENGINE_FOGBADGE` as above | -| `DARKCAVEVIOLETENTRANCE_ROCK1..4` | `DarkCaveVioletEntranceRock` -> `jumpstd SmashRockScript` -> `AskRockSmashScript` (`03:4f60`) | a party member knowing ROCK SMASH (TM08) | `HasRockSmash` (`03:4f7f`) - **no badge check at all** for Rock Smash | -| Fly between Mahogany and Blackthorn | `engine/events/overworld.asm:545` (the `.TryFly` arm of the Fly jumptable) `ld de, ENGINE_STORMBADGE` | `ENGINE_STORMBADGE` + the destination's `ENGINE_FLYPOINT_*` (`constants/engine_flags.asm:87`, `:89`) | both flypoints are set by visiting the towns in earlier sections | - -Badge constants used above: `ENGINE_ZEPHYRBADGE` (`constants/engine_flags.asm:38`), -`ENGINE_PLAINBADGE` (`:40`), `ENGINE_FOGBADGE` (`:41`), `ENGINE_STORMBADGE` -(`:43`), `ENGINE_RISINGBADGE` (`:45`). All are checked through -`engine/events/overworld.asm` `CheckBadge` (line 50), which prints -"Badge required" and returns carry. - -**Nothing in this section gates anything else.** Both Mt. Mortar and Dark Cave -are entirely optional; a bot that skips them loses only items, the Tyrogue and -the Blackglasses. - ---- - -## 4. Bot checklist - -Preconditions for the whole section: `ENGINE_ZEPHYRBADGE`, `ENGINE_FOGBADGE`, -`ENGINE_PLAINBADGE`, `ENGINE_RISINGBADGE`, `ENGINE_STORMBADGE`; party members -knowing FLASH, SURF, STRENGTH, WATERFALL (and ROCK SMASH for step 26); party -count <= 5 before step 20. - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `MAHOGANY_TOWN` | spawn (15,14) | Fly in, heal, buy MAX_REPEL / ESCAPE_ROPE | `ENGINE_FLYPOINT_MAHOGANY` | party healthy, bag stocked | -| 2 | `MAHOGANY_TOWN` | west map connection | walk west | - | on `ROUTE_42` | -| 3 | `ROUTE_42` | warp 3 at (10,5) | step onto warp (west entrance) | - | on `MOUNT_MORTAR_1F_OUTSIDE` at (3,33) | -| 4 | `MOUNT_MORTAR_1F_OUTSIDE` | - | use FLASH from the PACK/party menu | `ENGINE_ZEPHYRBADGE`, map palset is `DARKNESS_PALSET` | `STATUSFLAGS_FLASH_F` set | -| 5 | `MOUNT_MORTAR_1F_OUTSIDE` | - | use MAX_REPEL | bag has one | encounters suppressed | -| 6 | `MOUNT_MORTAR_1F_OUTSIDE` | object at (13,15) | walk onto / A-press the Poke Ball | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_ETHER` clear | ETHER in bag, flag set | -| 7 | `MOUNT_MORTAR_1F_OUTSIDE` | object at (31,18) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE` clear | GUARD_SPEC in bag, flag set | -| 8 | `MOUNT_MORTAR_1F_OUTSIDE` | bg event (25,22) | face the tile, press A | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_HIDDEN_HYPER_POTION` clear | HYPER_POTION in bag, flag set | -| 9 | `MOUNT_MORTAR_1F_OUTSIDE` | warp 5 at (11,21) | step on warp | - | on `MOUNT_MORTAR_1F_INSIDE` at (11,47) | -| 10 | `MOUNT_MORTAR_1F_INSIDE` | - | re-cast FLASH only if it was lost (it is not - cave to cave keeps the bit) | - | - | -| 11 | `MOUNT_MORTAR_1F_INSIDE` | object at (33,22) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_ESCAPE_ROPE` clear | ESCAPE_ROPE in bag | -| 12 | `MOUNT_MORTAR_1F_INSIDE` | object at (12,21) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_HYPER_POTION` clear | HYPER_POTION in bag | -| 13 | `MOUNT_MORTAR_1F_INSIDE` | boulder at (21,43) | activate STRENGTH from the party menu, then push | `ENGINE_PLAINBADGE`, party knows STRENGTH | `BIKEFLAGS_STRENGTH_ACTIVE_F` set, boulder movable | -| 14 | `MOUNT_MORTAR_1F_INSIDE` | warp 1 at (11,47) | back out | - | on `MOUNT_MORTAR_1F_OUTSIDE` at (11,21) | -| 15 | `MOUNT_MORTAR_1F_OUTSIDE` | the lake | walk into water / A-press facing water -> SURF | `ENGINE_FOGBADGE` | `wPlayerState = PLAYER_SURF` | -| 16 | `MOUNT_MORTAR_1F_OUTSIDE` | the waterfall tile | face **UP** at the waterfall, press A -> WATERFALL | `ENGINE_RISINGBADGE`, `CheckMapCanWaterfall` passes | climbed | -| 17 | `MOUNT_MORTAR_1F_OUTSIDE` | warp 4 at (17,5) | step on warp | - | on `MOUNT_MORTAR_2F_INSIDE` at (17,33) | -| 18 | `MOUNT_MORTAR_2F_INSIDE` | objects at (31,23), (2,24), (19,17), (14,5), (8,9), (28,5) | pick up all six Poke Balls | matching `EVENT_MOUNT_MORTAR_2F_INSIDE_*` clear | MAX_POTION, RARE_CANDY, TM_DEFENSE_CURL (TM40), DRAGON_SCALE, ELIXER, ESCAPE_ROPE | -| 19 | `MOUNT_MORTAR_2F_INSIDE` | bg event (19,6) | face, press A | `EVENT_MOUNT_MORTAR_2F_INSIDE_HIDDEN_FULL_RESTORE` clear | FULL_RESTORE in bag | -| 20 | `MOUNT_MORTAR_2F_INSIDE` | warp 2 at (3,5) | step on warp | - | on `MOUNT_MORTAR_1F_INSIDE` at (9,9) | -| 21 | `MOUNT_MORTAR_1F_INSIDE` | object at (16,10) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_MAX_REVIVE` clear | MAX_REVIVE in bag | -| 22 | `MOUNT_MORTAR_1F_INSIDE` | bg event (31,9) | face, press A | `EVENT_MOUNT_MORTAR_1F_INSIDE_HIDDEN_MAX_REPEL` clear | MAX_REPEL in bag | -| 23 | `MOUNT_MORTAR_1F_INSIDE` | warp 5 at (3,19) | step on warp | - | on `MOUNT_MORTAR_B1F` at (3,3) | -| 24 | `MOUNT_MORTAR_B1F` | bg (4,6), objects (4,16) and (31,17) | pick up hidden MAX_REVIVE, FULL_HEAL, HYPER_POTION | matching `EVENT_MOUNT_MORTAR_B1F_*` clear | three items | -| 25 | `MOUNT_MORTAR_B1F` | `MOUNTMORTARB1F_KIYO` at (13,4) | stand adjacent, press A | party count <= 5, `EVENT_GOT_TYROGUE_FROM_KIYO` clear | battle `BLACKBELT_T`/`KIYO` (L34 HITMONLEE, L34 HITMONCHAN) -> `EVENT_BEAT_BLACKBELT_KIYO` | -| 26 | `MOUNT_MORTAR_B1F` | Kiyo (same object, script continues) | press A through the reward text | party count <= 5 | `givepoke TYROGUE, 10`, `EVENT_GOT_TYROGUE_FROM_KIYO` set | -| 27 | `MOUNT_MORTAR_B1F` | - | use ESCAPE_ROPE (or STRENGTH the boulder at (9,10) then warp 2 at (19,29)) | ESCAPE_ROPE in bag | out of the cave | -| 28 | anywhere outdoors | `SPAWN_BLACKTHORN` | Fly to Blackthorn, heal | `ENGINE_STORMBADGE`, `ENGINE_FLYPOINT_BLACKTHORN` | at Blackthorn spawn (21,30); **`STATUSFLAGS_FLASH_F` is now cleared** | -| 29 | `BLACKTHORN_CITY` | south map connection | walk south | - | on `ROUTE_45` | -| 30 | `ROUTE_45` | warp 1 at (2,5) | step on warp | - | on `DARK_CAVE_BLACKTHORN_ENTRANCE` at (23,3) | -| 31 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | - | use FLASH again | `ENGINE_ZEPHYRBADGE` | `STATUSFLAGS_FLASH_F` set | -| 32 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | water in the middle of the map | SURF | `ENGINE_FOGBADGE` | crossing possible | -| 33 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | `DARKCAVEBLACKTHORNENTRANCE_PHARMACIST` at (7,3) | stand adjacent, press A | `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE` clear, item pocket has room | BLACKGLASSES, flag set | -| 34 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | objects at (7,22) and (21,24) | pick up both Poke Balls | matching events clear | TM_SNORE (TM13), REVIVE | -| 35 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | warp 2 at (3,25) | step on warp | - | on `DARK_CAVE_VIOLET_ENTRANCE` at (17,1) | -| 36 | `DARK_CAVE_VIOLET_ENTRANCE` | water below (17,1) | SURF down | `ENGINE_FOGBADGE` | - | -| 37 | `DARK_CAVE_VIOLET_ENTRANCE` | rock at (16,14) or (7,14) | face rock, press A, answer yes | party knows ROCK_SMASH | rock gone; 40% chance of a KRABBY/SHUCKLE L15 battle | -| 38 | `DARK_CAVE_VIOLET_ENTRANCE` | object at (6,8) | pick up Poke Ball | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_POTION` clear | POTION in bag | -| 39 | `DARK_CAVE_VIOLET_ENTRANCE` | warp 1 at (3,15) | step on warp | - | on `ROUTE_31` at (34,5); **FLASH bit cleared** | -| 40 | `ROUTE_31` | - | Fly back to Blackthorn, heal | - | section complete | - -Optional extras this route skips, with their coordinates if a completionist bot -wants them: `DARK_CAVE_VIOLET_ENTRANCE` objects at (36,22) FULL_HEAL and (35,9) -HYPER_POTION plus the hidden ELIXER bg at (26,3) - all three sit on the -north-east lobe of the map that the earlier Route 31 section already reaches. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers / dimensions for all six maps | `tools/rom_manifest_gold.json` (e.g. `MOUNT_MORTAR_1F_OUTSIDE` 20x18 group 3 map 49, `DARK_CAVE_BLACKTHORN_ENTRANCE` 15x18 group 3 map 71), read by `src/import/RomExtractorGen2.lua` | implemented (extracted from ROM, no hand-ported map file needed) | -| Warp / coord / bg / object event tables | `src/import/RomExtractorGen2.lua` (`warps`, `coordEvents`, `bgEvents`, `objects` around lines 787-862) -> `src/world/gen2/Map.lua` | implemented | -| `PALETTE_DARK` -> `DARKNESS_PALSET` and the FLASH override | `src/world/gen2/Palettes.lua`, asserted by `tests/gen2_palettes_test.lua:118-129` (`dark cave` / `dark cave with flash`) | implemented | -| `ResetFlashIfOutOfCave` (FLASH dies on ROUTE/TOWN) | `src/world/gen2/World.lua:5622-5625` | implemented | -| FLASH / SURF / WATERFALL / STRENGTH badge gates | `src/world/gen2/FieldMoves.lua:105-110` (`FLASH=ZEPHYR`, `SURF=FOG`, `STRENGTH=PLAIN`, `WATERFALL=RISING`), `FieldMoves.flashFromMenu` / `surfFromMenu` / `waterfallFromMenu` / `strengthFromMenu` | implemented | -| `CheckMapCanWaterfall` (facing UP, `wTileUp`) and the climb loop | `src/world/gen2/FieldMoves.lua:253-266`, `src/world/gen2/World.lua:4030-4046` `runWaterfall` / `waterfallStep`, `World:tryWaterfallOW` (4217) | implemented | -| Strength boulder push (`StrengthBoulderScript` -> `AskStrengthScript`) | `src/world/gen2/World.lua:5268-5274` (the boulder arm of `World:interact`), `World:tryStrengthOW`, driver `tests/drivers/gold_icepath_boulder.lua` | implemented | -| `BIKEFLAGS_STRENGTH_ACTIVE_F` cleared on map load | `src/world/gen2/World.lua:5611-5613` | implemented | -| Hidden items (`BGEVENT_ITEM` / `hiddenitem`) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5290-5296` | implemented | -| `verbosegiveitem BLACKGLASSES` (the Dark Cave pharmacist) | `src/script/gen2/Vm.lua:490-510`, hook `World.lua:831` `giveItem` | implemented | -| `givepoke TYROGUE, 10` (Kiyo's reward) | `src/script/gen2/Vm.lua:439-445`, hook `World.lua:812` `givePoke` | implemented | -| `readvar VAR_PARTYCOUNT` (Kiyo's "no room" branch) | `src/world/gen2/World.lua:112` `VAR_PARTYCOUNT = 0x01`, read at `World.lua:1225` | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` | `src/script/gen2/Vm.lua:806`, `:817`, `:886`, `:918`; `src/world/gen2/Trainers.lua`; driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| Tyrogue's three-way stat evolution (what the reward mon becomes) | `src/core/gen2/Evolution.lua:71`, asserted by `tests/gen2_evolution_test.lua:347-357` | implemented | -| Repel / Max Repel step counter | `src/world/gen2/World.lua:3382` `World:useRepel`, `src/ui/gen2/PackMenu.lua:199-206` | implemented | -| **Picking up a Poke Ball (`OBJECTTYPE_ITEMBALL`)** | extracted into `obj.itemball` by `src/import/RomExtractorGen2.lua:2968-2969`, but `World:interact` (`src/world/gen2/World.lua:5256-5310`) has arms for trainer, boulder, `scriptKey`, `BGEVENT_READ` and `BGEVENT_ITEM` **and no itemball arm**; `CallAsm.lua:551` stubs `TryReceiveItem` out | **missing** - all 15 Poke Balls in this section are unobtainable in the port today | -| **ROCK SMASH as a field move** | `src/script/gen2/CallAsm.lua:346-350` implements `HasRockSmash`, but line 526 stubs `RockMonEncounter` with the note "ROCK SMASH has no field-move path yet; RockMonMaps is unported"; `World:interact`'s tile-event chain (`World.lua:5301-5308`) lists cut / whirlpool / waterfall / headbutt / surf and **not** rock smash, and nothing in `src/` references `SPRITEMOVEDATA_SMASHABLE_ROCK` | **missing** - the four Dark Cave Violet rocks cannot be broken, and the Krabby/Shuckle table is unreachable | -| **ESCAPE_ROPE / DIG as a field item** | `src/world/gen2/World.lua:3293-3302` `World:useFieldItem` handles ITEMFINDER, BICYCLE, SACRED_ASH, repels, trophy boxes and rods only. (Gen 1's path exists at `src/ui/BagMenu.lua:231-240`, but that is the Red/Blue engine, not the Gen 2 one.) | **missing** - the walkthrough's "use one of the Escape Ropes to get out" has no port path; the Strength-boulder exit at B1F (9,10) does work | -| Fly to Mahogany / Blackthorn | `src/world/gen2/FieldMoves.lua:394-440` `hasVisitedSpawn` / `flyPoints`, `flyFromMenu` (504) | implemented | -| Wild encounter tables (grass + water + fishing) for these maps | extracted; `src/world/gen2/FieldMoves.lua:162-177` `canEncounterWildMon` / `encounterTable` | implemented (not separately verified against these six maps) | - -No driver in `tests/drivers/gold_*.lua` visits Mt. Mortar or Dark Cave. - ---- - -## 6. Unresolved / verify by hand - -1. **The walkthrough's Mt. Mortar item list is the HGSS list, not the GS list.** - It names Carbos, Escape Rope x2, Full Restore, Hyper Potion x2, Max Ether, - Max Potion, Max Revive, PP Up, Rare Candy, Dragon Scale and "TM40 Aerial - Ace". What is actually in `maps/MountMortar*.asm` is: ETHER, GUARD_SPEC, - hidden HYPER_POTION (1F Outside); ESCAPE_ROPE, MAX_REVIVE, HYPER_POTION, - hidden MAX_REPEL (1F Inside); MAX_POTION, RARE_CANDY, TM_DEFENSE_CURL, - DRAGON_SCALE, ELIXER, ESCAPE_ROPE, hidden FULL_RESTORE (2F Inside); - HYPER_POTION, FULL_HEAL, hidden MAX_REVIVE (B1F). **No CARBOS, no PP_UP, no - MAX_ETHER, no IRON anywhere in Mt. Mortar.** TM40 is the right *number* but - the move is DEFENSE_CURL (`constants/item_constants.asm:261`); Aerial Ace is - a Gen 3 move that does not exist in this disassembly. -2. **"grab the free Iron on the cliff" (1F Inside, after coming down from 2F).** - There is no IRON object on any Mt. Mortar map. The nearest items to warp 6 at - (9,9) are the MAX_REVIVE ball at (16,10) and the hidden MAX_REPEL bg event at - (31,9). Treat "Iron" as an HGSS-only pickup. -3. **"surf clockwise around and reach a Hyper Potion and a Max Ether."** There is - no MAX_ETHER in Mt. Mortar. If the intended floor is B1F (the ladder from the - central entrance) the pair is HYPER_POTION (31,17) + FULL_HEAL (4,16); if 2F, - the closest analogue is ELIXER (8,9). Which one the author meant could not be - determined from the asm. -4. **Kiyo's prize money and EXP yields.** The walkthrough gives 816G and - 1012/1020 EXP. `data/trainers/parties.asm` only carries the class, name, type - byte and party; the money formula lives in the battle engine's - `data/trainers/attributes.asm` base-money table times level, which was not - opened for this document. The party itself is verified: L34 HITMONLEE, L34 - HITMONCHAN, `TRAINERTYPE_NORMAL` (default moves, no held items). -5. **"a giant room where you can zig zag around ... and get two items, to include - a Hyper Potion and Escape Rope."** `MOUNT_MORTAR_1F_INSIDE` actually holds - *three* balls (ESCAPE_ROPE (33,22), MAX_REVIVE (16,10), HYPER_POTION (12,21)) - plus a hidden MAX_REPEL - but MAX_REVIVE and the hidden item sit on the - section of the map only reachable from 2F, which is presumably why the - walkthrough counts two. -6. **"The west entrance and east entrance lead to each other."** All three Route - 42 doors land on the same map, `MOUNT_MORTAR_1F_OUTSIDE`; the west/east link - runs through `MOUNT_MORTAR_1F_INSIDE` via warp pairs 5/6 (at y=21) and 8/9 - (at y=13). The blk files were not decoded, so the exact walkable path between - any two of these warps is not verified here - only the warp graph is. -7. **Dark Cave Pokemon list.** The walkthrough lists Zubat, Golbat, Geodude, - Graveler, Magikarp and Wobbuffet - that is the `DARK_CAVE_BLACKTHORN_ENTRANCE` - table exactly. It omits DUNSPARCE, which is slot 7 of - `DARK_CAVE_VIOLET_ENTRANCE` and is on the map the route exits through. -8. **Dark Cave item list.** The walkthrough lists Blackglasses, Full Heal, Hyper - Potion, Revive and TM13. The Full Heal (36,22) and Hyper Potion (35,9) are on - `DARK_CAVE_VIOLET_ENTRANCE`'s north-east lobe, which this route does not pass - through; the hidden ELIXER at (26,3) is not mentioned at all. The POTION at - (6,8) is named in the prose but missing from the list. -9. **The Shuckle/Krabby rock-smash table** (`data/wild/treemon_maps.asm:46`, - `data/wild/treemons.asm:91`) is real and reachable at - `DARK_CAVE_VIOLET_ENTRANCE`, but the walkthrough never mentions it. Worth - flagging to a bot author as a cheap Shuckle source. -10. **`MOUNT_MORTAR_1F_OUTSIDE` object 2 flag naming.** The item is - `GUARD_SPEC` but the event flag is `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE` - (`constants/event_flags.asm:1053`). Verbatim from the disassembly; not a - transcription error here, but it will look like one to anyone grepping by - item name. -11. **Mahogany Town and Blackthorn City interiors** (Poke Center, Mart, gym) were - not opened for this document - only their `data/maps/maps.asm` header rows - and `data/maps/spawn_points.asm` entries. Those maps belong to neighbouring - sections. diff --git a/docs/gold-walkthrough/asm-walk/section-16-routes-45-46-26-and-27.md b/docs/gold-walkthrough/asm-walk/section-16-routes-45-46-26-and-27.md deleted file mode 100644 index 8af16be0..00000000 --- a/docs/gold-walkthrough/asm-walk/section-16-routes-45-46-26-and-27.md +++ /dev/null @@ -1,1078 +0,0 @@ -# Section 16 - Routes 45, 46, 26 and 27 - -Source: `../section-16-routes-45-46-26-and-27.txt` -Maps covered: `MAP_ROUTE_45`, `MAP_ROUTE_46`, `MAP_ROUTE_27`, `MAP_TOHJO_FALLS`, -`MAP_ROUTE_27_SANDSTORM_HOUSE`, `MAP_ROUTE_26`, `MAP_ROUTE_26_HEAL_HOUSE`, -`MAP_DAY_OF_WEEK_SIBLINGS_HOUSE`, `MAP_VICTORY_ROAD_GATE` (gate only) -Badges / key milestones in this section: no badge. The milestones are the -MASTER BALL and EVERSTONE from Prof. Elm, TM37 SANDSTORM, TM22 SOLARBEAM, the -Tohjo Falls MOON STONE, and the eight-badge check at the Victory Road gate that -opens the Pokemon League. - -Coordinate convention reminder: `warp_event` / `bg_event` / `object_event` rows -are in map **cells** (16 px walk grid). `map_const NAME, W, H` in -`constants/map_constants.asm` is in **blocks** (32 px), so a map is `W*2` by -`H*2` cells. Route 45 is `10, 45` blocks = 20 x 90 cells, which is why object -rows there run to y = 82. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_45` | `maps/Route45.asm` | south connection from `BLACKTHORN_CITY` (`data/maps/attributes.asm`: `connection north, BlackthornCity, BLACKTHORN_CITY, 0`) | west connection to `ROUTE_46` (offset 36); optional warp 1 to `DARK_CAVE_BLACKTHORN_ENTRANCE` | Descend the mountain road, three forks, six trainers, four item balls | -| 2 | `MAP_ROUTE_46` | `maps/Route46.asm` | east connection from `ROUTE_45` (offset -36) | south connection to `ROUTE_29` (offset -10) or warps 1/2 to `ROUTE_29_ROUTE_46_GATE`; warp 3 to `DARK_CAVE_VIOLET_ENTRANCE` | Camper Ted, Picnicker Erin, Hiker Bailey, Dire Hit, two fruit trees | -| 3 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | FLY | warp 1 to `ELMS_LAB`, warp 2 to `PLAYERS_HOUSE_1F`; east connection to `ROUTE_27` | Elm hands the MASTER BALL (needs RISINGBADGE) and the EVERSTONE (needs Togepi shown). Map tables for New Bark Town belong to section 00; only the two Elm scripts are transcribed below. | -| 4 | `MAP_ROUTE_27` | `maps/Route27.asm` | west connection from `NEW_BARK_TOWN` (offset 0) - the crossing itself is water, so SURF | warp 2 / warp 3 into `TOHJO_FALLS`; warp 1 into `ROUTE_27_SANDSTORM_HOUSE`; east connection to `ROUTE_26` (offset -45) | "First step into KANTO" coord event, Rare Candy, then the eastern trainer gauntlet | -| 5 | `MAP_TOHJO_FALLS` | `maps/TohjoFalls.asm` | `Route27` warp 2 (26, 5) -> Tohjo warp 1 | Tohjo warp 2 (25, 15) -> `Route27` warp 3 | Cross the falls; MOON STONE item ball the walkthrough does not mention | -| 6 | `MAP_ROUTE_27` | `maps/Route27.asm` | Tohjo warp 2 | east connection to `ROUTE_26` | Megan ambush on exit, Sandstorm house, Blake/Brian/Gilbert, whirlpool island (Jose + TM22), Reena | -| 7 | `MAP_ROUTE_27_SANDSTORM_HOUSE` | `maps/Route27SandstormHouse.asm` | `Route27` warp 1 (33, 7) | warps 1/2 back to `Route27` warp 1 | Happiness check -> TM37 SANDSTORM | -| 8 | `MAP_ROUTE_26` | `maps/Route26.asm` | west connection from `ROUTE_27` (offset 45) | warp 1 (7, 5) to `VICTORY_ROAD_GATE` warp 3 | Scott, Richard, heal house, Joyce, Ice Berry, Gaven, Jake, Max Elixer, Beth | -| 9 | `MAP_ROUTE_26_HEAL_HOUSE` | `maps/Route26HealHouse.asm` | `Route26` warp 2 (15, 57) | warps 1/2 back to `Route26` warp 2 | Free full heal, repeatable | -| 10 | `MAP_DAY_OF_WEEK_SIBLINGS_HOUSE` | `maps/DayOfWeekSiblingsHouse.asm` | `Route26` warp 3 (5, 71) | warps 1/2 back to `Route26` warp 3 | Monica's journal (the day-of-week sibling roster) | -| 11 | `MAP_VICTORY_ROAD_GATE` | `maps/VictoryRoadGate.asm` | `Route26` warp 1 | warps 5/6 to `VICTORY_ROAD` | Eight-badge check. Victory Road itself belongs to section 17; only the gate's badge check is transcribed here because it is this section's terminal gate. | - -Spill-over note: `DARK_CAVE_BLACKTHORN_ENTRANCE` / `DARK_CAVE_VIOLET_ENTRANCE` -are the "left path" the walkthrough points at from Routes 45 and 46; they are -section 15's maps. Their return warps are `DarkCaveBlackthornEntrance.asm` -`warp_event 23, 3, ROUTE_45, 1` and `DarkCaveVioletEntrance.asm` -`warp_event 35, 33, ROUTE_46, 3`. - ---- - -## 2. Maps - -### MAP_ROUTE_45 - -- Script: `maps/Route45.asm` -- Blocks: `maps/Route45.blk` -- Header: `data/maps/maps.asm:185` -> `map Route45, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_45, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_DRATINI_2` -- Dimensions: `constants/map_constants.asm:170` -> `map_const ROUTE_45, 10, 45` (20 x 90 cells) -- Connections (`data/maps/attributes.asm:243`): `connection north, BlackthornCity, BLACKTHORN_CITY, 0`; `connection west, Route46, ROUTE_46, 36` -- Symbols: `4d:4e75 Route45_MapScripts`, `4d:54ad Route45_MapEvents` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 5 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | 1 | - -**Coord events** (`def_coord_events`) - -None. Route 45 has no trip-wires; every scripted beat is an A press. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 10 | 4 | `BGEVENT_READ` | `Route45Sign` ("ROUTE 45 / MOUNTAIN RD. AHEAD") | -| 13 | 80 | `BGEVENT_ITEM` | `Route45HiddenPpUp` -> `hiddenitem PP_UP, EVENT_ROUTE_45_HIDDEN_PP_UP` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE45_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 10 | 16 | `STANDING_RIGHT` | `TRAINER` (sight 1) | `TrainerHikerErik` | -1 | -| `ROUTE45_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 15 | 64 | `STANDING_RIGHT` | `TRAINER` (sight 2) | `TrainerHikerMichael` | -1 | -| `ROUTE45_POKEFAN_M3` | `SPRITE_POKEFAN_M` | 5 | 28 | `STANDING_LEFT` | `TRAINER` (sight 3) | `TrainerHikerParry` | -1 | -| `ROUTE45_POKEFAN_M4` | `SPRITE_POKEFAN_M` | 9 | 64 | `STANDING_LEFT` | `TRAINER` (sight 1) | `TrainerHikerTimothy` | -1 | -| `ROUTE45_BLACK_BELT` | `SPRITE_BLACK_BELT` | 11 | 50 | `SPINRANDOM_FAST` | `TRAINER` (sight 2) | `TrainerBlackbeltKenji` | -1 | -| `ROUTE45_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 17 | 18 | `STANDING_LEFT` | `TRAINER` (sight 1) | `TrainerCooltrainermRyan` | -1 | -| `ROUTE45_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 4 | 36 | `STANDING_RIGHT` | `TRAINER` (sight 3) | `TrainerCooltrainerfKelly` | -1 | -| `ROUTE45_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 16 | 82 | `STILL` | `SCRIPT` | `Route45FruitTree` (`fruittree FRUITTREE_ROUTE_45`) | -1 | -| `ROUTE45_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 51 | `STILL` | `ITEMBALL` | `Route45XSpecial` (`itemball X_SPECIAL`) | `EVENT_ROUTE_45_X_SPECIAL` | -| `ROUTE45_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 66 | `STILL` | `ITEMBALL` | `Route45Revive` (`itemball REVIVE`) | `EVENT_ROUTE_45_REVIVE` | -| `ROUTE45_POKE_BALL3` | `SPRITE_POKE_BALL` | 4 | 21 | `STILL` | `ITEMBALL` | `Route45Elixer` (`itemball ELIXER`) | `EVENT_ROUTE_45_ELIXER` | -| `ROUTE45_POKE_BALL4` | `SPRITE_POKE_BALL` | 8 | 33 | `STILL` | `ITEMBALL` | `Route45MaxPotion` (`itemball MAX_POTION`) | `EVENT_ROUTE_45_MAX_POTION` | - -**Scripts of interest** - -- `TrainerBlackbeltKenji` (`4d:4e77`). Header is - `trainer BLACKBELT_T, KENJI3, EVENT_BEAT_BLACKBELT_KENJI, BlackbeltKenjiSeenText, BlackbeltKenjiBeatenText, 0, .Script`. - Post-battle `.Script`: `endifjustbattled` / `opentext` / - `checkevent EVENT_KENJI_READY_FOR_REMATCH` (rematch branch) / - `checkcellnum PHONE_BLACKBELT_KENJI` (already registered -> jump to the - "number accepted" std) / `checkevent EVENT_KENJI_ASKED_FOR_PHONE_NUMBER`. - First pass sets `EVENT_KENJI_ASKED_FOR_PHONE_NUMBER` and calls - `askforphonenumber PHONE_BLACKBELT_KENJI`; the return value is compared - against `PHONE_CONTACTS_FULL` and `PHONE_CONTACT_REFUSED`. This is the - walkthrough's "talk to him again and he will give you his phone number" - - the number is offered on the **second** A press, not the first. - `.WantsBattle` picks the rematch party by - `checkevent EVENT_RESTORED_POWER_TO_KANTO` -> `KENJI2`, else - `checkevent EVENT_BEAT_ELITE_FOUR` -> `KENJI1`, else `KENJI3`; then - `loadtrainer` / `startbattle` / `reloadmapafterbattle` / - `clearevent EVENT_KENJI_READY_FOR_REMATCH`. -- `TrainerHikerParry` (`4d:4f2d`) is the same shape with - `PHONE_HIKER_PARRY`, `EVENT_PARRY_ASKED_FOR_PHONE_NUMBER`, - `EVENT_PARRY_READY_FOR_REMATCH`, and `PARRY3` as the first-meeting party - (`PARRY1` after the Elite Four, `PARRY2` after Kanto's power is restored). - Note `gettrainername STRING_BUFFER_3, HIKER, PARRY1` - the displayed name is - read off `PARRY1` even though the fight is `PARRY3`. -- `TrainerHikerErik`, `TrainerHikerMichael`, `TrainerHikerTimothy`, - `TrainerCooltrainermRyan`, `TrainerCooltrainerfKelly`: plain - `endifjustbattled` / `opentext` / `writetext ...AfterBattleText` / - `waitbutton` / `closetext` / `end`. No flags beyond the `trainer` header's - own beat flag. -- `Route45FruitTree` = `fruittree FRUITTREE_ROUTE_45`. Index 12 in - `constants/script_constants.asm:218`; `data/items/fruit_trees.asm` row 12 is - `MYSTERYBERRY` - the walkthrough's "Mystery Berry". -- `Route45HiddenPpUp` = `hiddenitem PP_UP, EVENT_ROUTE_45_HIDDEN_PP_UP`, at - cell (13, 80). Not mentioned by the walkthrough at all. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_HIKER_ERIK` | `constants/event_flags.asm:824` | `trainer` header | set on win, hides the "!" re-challenge | -| `EVENT_BEAT_HIKER_MICHAEL` | `:825` | `trainer` header | as above | -| `EVENT_BEAT_HIKER_PARRY` | `:826` | `trainer` header | as above | -| `EVENT_BEAT_HIKER_TIMOTHY` | `:827` | `trainer` header | as above | -| `EVENT_BEAT_BLACKBELT_KENJI` | `:686` | `trainer` header | as above | -| `EVENT_BEAT_COOLTRAINERM_RYAN` | `:864` | `trainer` header | as above | -| `EVENT_BEAT_COOLTRAINERF_KELLY` | `:883` | `trainer` header | as above | -| `EVENT_KENJI_ASKED_FOR_PHONE_NUMBER` | `constants/event_flags.asm` | `TrainerBlackbeltKenji.Script` | gates the "ask again" wording | -| `EVENT_KENJI_READY_FOR_REMATCH` | `constants/event_flags.asm` | phone script sets, map script clears | rematch pending | -| `EVENT_PARRY_ASKED_FOR_PHONE_NUMBER` / `EVENT_PARRY_READY_FOR_REMATCH` | same | `TrainerHikerParry.Script` | same pair for Parry | -| `EVENT_RESTORED_POWER_TO_KANTO` | `:204` | read by both rematch scripts | selects the hardest rematch party | -| `EVENT_BEAT_ELITE_FOUR` | `:77` | read by both rematch scripts | selects the middle rematch party | -| `EVENT_ROUTE_45_X_SPECIAL` | `:1113` | item ball object | one-time pickup | -| `EVENT_ROUTE_45_REVIVE` | `:1114` | item ball object | one-time pickup | -| `EVENT_ROUTE_45_ELIXER` | `:1115` | item ball object | one-time pickup | -| `EVENT_ROUTE_45_MAX_POTION` | `:1116` | item ball object | one-time pickup | -| `EVENT_ROUTE_45_HIDDEN_PP_UP` | `:185` | `hiddenitem` | one-time hidden pickup | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `X_SPECIAL` | item ball at (6, 51) | `Route45XSpecial` | `EVENT_ROUTE_45_X_SPECIAL` | -| `REVIVE` | item ball at (6, 66) | `Route45Revive` | `EVENT_ROUTE_45_REVIVE` | -| `ELIXER` | item ball at (4, 21) | `Route45Elixer` | `EVENT_ROUTE_45_ELIXER` | -| `MAX_POTION` | item ball at (8, 33) | `Route45MaxPotion` | `EVENT_ROUTE_45_MAX_POTION` | -| `MYSTERYBERRY` | fruit tree at (16, 82) | `Route45FruitTree` / `FRUITTREE_ROUTE_45` | daily fruit-tree flag, not an `EVENT_*` | -| `PP_UP` | hidden, bg event (13, 80) | `Route45HiddenPpUp` | `EVENT_ROUTE_45_HIDDEN_PP_UP` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `HIKER, ERIK` | `HIKER` (`$2c`) | 7 | `HikerGroup` "ERIK": 24 MACHOP, 27 GRAVELER, 27 MACHOP | `TrainerHikerErik` | no | -| `HIKER, MICHAEL` | `HIKER` | 8 | "MICHAEL": 25 GEODUDE, 25 GRAVELER, 25 GOLEM | `TrainerHikerMichael` | no | -| `HIKER, PARRY3` | `HIKER` | 20 | "PARRY": 29 ONIX | `TrainerHikerParry` | `PHONE_HIKER_PARRY`; `PARRY1` = 34 ONIX, `PARRY2` = 38 ONIX | -| `HIKER, TIMOTHY` | `HIKER` | 10 | "TIMOTHY" (`TRAINERTYPE_MOVES`): 27 DIGLETT (MAGNITUDE/DIG/SAND_ATTACK/SLASH), 27 DUGTRIO (same) | `TrainerHikerTimothy` | no | -| `BLACKBELT_T, KENJI3` | `BLACKBELT_T` (`$32`) | 8 | `BlackbeltGroup` "KENJI": 28 MACHOKE | `TrainerBlackbeltKenji` | `PHONE_BLACKBELT_KENJI` | -| `COOLTRAINERM, RYAN` | `COOLTRAINERM` (`$1b`) | 8 | "RYAN" (`TRAINERTYPE_MOVES`): 25 PIDGEOT (SAND_ATTACK/QUICK_ATTACK/WHIRLWIND/WING_ATTACK), 27 ELECTABUZZ (THUNDERPUNCH/LIGHT_SCREEN/SWIFT/SCREECH) | `TrainerCooltrainermRyan` | no | -| `COOLTRAINERF, KELLY` | `COOLTRAINERF` (`$1c`) | 7 | "KELLY": 27 MARILL, 24 WARTORTLE, 24 WARTORTLE | `TrainerCooltrainerfKelly` | no | - -**Wild encounters** - -`data/wild/johto_grass.asm:2236` `def_grass_wildmons ROUTE_45`, rates -`10 percent` morn/day/nite (identical slot lists across all three times): - -- Gold: 23 GEODUDE, 23 GRAVELER, 24 GLIGAR, 20 TEDDIURSA, 25 GRAVELER, - 27 GRAVELER, 27 GRAVELER. -- Silver: 23 GEODUDE, 23 GRAVELER, 24 GRAVELER, 20 PHANPY, 25 GRAVELER, - 27 SKARMORY, 27 SKARMORY. - -`data/wild/johto_water.asm:204` `def_water_wildmons ROUTE_45`, 2 percent: -20 MAGIKARP, 15 MAGIKARP, 5 MAGIKARP. - -Fishing group `FISHGROUP_DRATINI_2` (`data/wild/fish.asm` `FishGroups` row 9). -Headbutt: `data/wild/treemon_maps.asm:26` -> `TREEMON_SET_CANYON` -(`data/wild/treemons.asm:73`: SPEAROW/AIPOM at level 10). - -Roaming beast: `data/wild/roammon_maps.asm:33` `roam_map ROUTE_45, ROUTE_44, ROUTE_46` -- Route 45 is a roam map, so Entei/Raikou can appear in its grass. - ---- - -### MAP_ROUTE_46 - -- Script: `maps/Route46.asm` -- Blocks: `maps/Route46.blk` -- Header: `data/maps/maps.asm:186` -> `map Route46, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_46, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:171` -> `map_const ROUTE_46, 10, 18` (20 x 36 cells) -- Connections (`data/maps/attributes.asm:247`): `connection south, Route29, ROUTE_29, -10`; `connection east, Route45, ROUTE_45, -36` -- Symbols: `4d:555e Route46_MapScripts`, `4d:57c7 Route46_MapEvents` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 33 | `ROUTE_29_ROUTE_46_GATE` | 1 | -| 2 | 8 | 33 | `ROUTE_29_ROUTE_46_GATE` | 2 | -| 3 | 14 | 5 | `DARK_CAVE_VIOLET_ENTRANCE` | 3 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 27 | `BGEVENT_READ` | `Route46Sign` ("ROUTE 46 / MOUNTAIN RD. AHEAD") | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE46_POKEFAN_M` | `SPRITE_POKEFAN_M` | 12 | 18 | `STANDING_LEFT` | `TRAINER` (sight 2) | `TrainerHikerBailey` | -1 | -| `ROUTE46_YOUNGSTER` | `SPRITE_YOUNGSTER` | 3 | 13 | `STANDING_RIGHT` | `TRAINER` (sight 4) | `TrainerCamperTed` | -1 | -| `ROUTE46_LASS` | `SPRITE_LASS` | 1 | 15 | `STANDING_RIGHT` | `TRAINER` (sight 4) | `TrainerPicnickerErin1` | -1 | -| `ROUTE46_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 7 | 5 | `STILL` | `SCRIPT` | `Route46FruitTree1` (`fruittree FRUITTREE_ROUTE_46_1`) | -1 | -| `ROUTE46_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 8 | 6 | `STILL` | `SCRIPT` | `Route46FruitTree2` (`fruittree FRUITTREE_ROUTE_46_2`) | -1 | -| `ROUTE46_POKE_BALL` | `SPRITE_POKE_BALL` | 0 | 12 | `STILL` | `ITEMBALL` | `Route46DireHit` (`itemball DIRE_HIT`) | `EVENT_ROUTE_46_DIRE_HIT` | - -**Scripts of interest** - -- `TrainerPicnickerErin1` (`4d:5574`), header - `trainer PICNICKER, ERIN1, EVENT_BEAT_PICNICKER_ERIN, ...`. Same phone shape - as Kenji/Parry: `checkcellnum PHONE_PICNICKER_ERIN`, - `EVENT_ERIN_ASKED_FOR_PHONE_NUMBER`, `askforphonenumber PHONE_PICNICKER_ERIN`, - `EVENT_ERIN_READY_FOR_REMATCH`; rematch parties `ERIN2` (after Elite Four) - and `ERIN3` (after Kanto power restored). -- `TrainerCamperTed` (`4d:5560`) and `TrainerHikerBailey` (`4d:5602`) are plain - after-battle text scripts. -- `Route46FruitTree1` = `FRUITTREE_ROUTE_46_1` = index 4 = - `BERRY`; `Route46FruitTree2` = `FRUITTREE_ROUTE_46_2` = index 10 = - `PRZCUREBERRY` (`data/items/fruit_trees.asm`). Those are the walkthrough's - "Berry and PrzCure Berry". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_HIKER_BAILEY` | `constants/event_flags.asm:828` | `trainer` header | set on win | -| `EVENT_BEAT_CAMPER_TED` | `:538` | `trainer` header | set on win | -| `EVENT_BEAT_PICNICKER_ERIN` | `:645` | `trainer` header | set on win | -| `EVENT_ERIN_ASKED_FOR_PHONE_NUMBER` / `EVENT_ERIN_READY_FOR_REMATCH` | `constants/event_flags.asm` | `TrainerPicnickerErin1.Script` | phone / rematch pair | -| `EVENT_ROUTE_46_DIRE_HIT` | `:1117` | item ball object | one-time pickup | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `DIRE_HIT` | item ball at (0, 12) | `Route46DireHit` | `EVENT_ROUTE_46_DIRE_HIT` | -| `BERRY` | fruit tree at (7, 5) | `Route46FruitTree1` | daily fruit-tree flag | -| `PRZCUREBERRY` | fruit tree at (8, 6) | `Route46FruitTree2` | daily fruit-tree flag | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `HIKER, BAILEY` | `HIKER` | 11 | `HikerGroup` "BAILEY": five 13 GEODUDE | `TrainerHikerBailey` | no | -| `CAMPER, TED` | `CAMPER` (`$36`) | 11 | `CamperGroup` "TED": 17 MANKEY | `TrainerCamperTed` | no | -| `PICNICKER, ERIN1` | `PICNICKER` (`$35`) | 10 | `PicnickerGroup` "ERIN": 16 PONYTA, 16 PONYTA | `TrainerPicnickerErin1` | `PHONE_PICNICKER_ERIN` | - -**Wild encounters** - -`data/wild/johto_grass.asm:2291` `def_grass_wildmons ROUTE_46`, `10 percent` -morn/day/nite, same for Gold and Silver: - -- morn/day: 3 GEODUDE, 2 SPEAROW, 2 RATTATA, 2 GEODUDE, 3 SPEAROW, - 3 JIGGLYPUFF, 5 JIGGLYPUFF. -- nite: 3 GEODUDE, 3 RATTATA, 2 RATTATA, 2 GEODUDE, 4 GEODUDE, 3 JIGGLYPUFF, - 5 JIGGLYPUFF. - -No water table. Fishing group `FISHGROUP_SHORE`. Headbutt: -`data/wild/treemon_maps.asm:27` -> `TREEMON_SET_CANYON`. -Roaming beast: `data/wild/roammon_maps.asm:34` `roam_map ROUTE_46, ROUTE_45, ROUTE_29`. - ---- - -### MAP_NEW_BARK_TOWN (Elm's Lab beats only) - -The New Bark Town / Elm's Lab map tables belong to section 00. What this -section needs is the two `ProfElmScript` branches the walkthrough triggers. - -- `maps/ElmsLab.asm:50` `ProfElmScript`: - `faceplayer` / `opentext` / `checkevent EVENT_GOT_SS_TICKET_FROM_ELM` - (-> `ElmCheckMasterBall`) / `checkevent EVENT_BEAT_ELITE_FOUR` - (-> `ElmGiveTicketScript`). -- `ElmCheckMasterBall`: `checkevent EVENT_GOT_MASTER_BALL_FROM_ELM` / - `checkflag ENGINE_RISINGBADGE` -> `ElmGiveMasterBallScript`. **This is the - actual gate on the Master Ball: the eighth Johto badge, not "eight badges" - counted.** `constants/engine_flags.asm:45` is `ENGINE_RISINGBADGE`. -- `ElmGiveMasterBallScript` (`60:4268`): `verbosegiveitem MASTER_BALL` / - `iffalse .notdone` / `setevent EVENT_GOT_MASTER_BALL_FROM_ELM`. A full bag - means the flag is never set, so a bot must have a free slot. -- `ElmCheckEverstone`: `checkevent EVENT_GOT_EVERSTONE_FROM_ELM` / - `checkevent EVENT_SHOWED_TOGEPI_TO_ELM` -> `ElmGiveEverstoneScript` / - `checkevent EVENT_TOLD_ELM_ABOUT_TOGEPI_OVER_THE_PHONE`, then - `setval TOGEPI` + `special FindPartyMonThatSpeciesYourTrainerID`, and the - same for `TOGETIC`. That special is why the walkthrough tells you to - **withdraw Togepi into the party first** - the check is a party scan, and it - additionally requires the mon to be your own OT. -- `ShowElmTogepiScript` sets `EVENT_SHOWED_TOGEPI_TO_ELM` and falls into - `ElmGiveEverstoneScript` (`60:424f`): `verbosegiveitem EVERSTONE` / - `iffalse ElmScript_NoRoomForEverstone` / `setevent EVENT_GOT_EVERSTONE_FROM_ELM`. -- Mom's savings: `maps/PlayersHouse1F.asm:85` `MomScript`, `.BankOfMom` at - line 115 runs `special BankOfMom`. - ---- - -### MAP_ROUTE_27 - -- Script: `maps/Route27.asm` -- Blocks: `maps/Route27.blk` -- Header: `data/maps/maps.asm:473` -> `map Route27, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_27, MUSIC_ROUTE_26, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:438` -> `map_const ROUTE_27, 40, 9` (80 x 18 cells) -- Connections (`data/maps/attributes.asm:170`): `connection west, NewBarkTown, NEW_BARK_TOWN, 0`; `connection east, Route26, ROUTE_26, -45` -- Scene var: `data/maps/scenes.asm:27` `scene_var ROUTE_27, wRoute27SceneID` -- Symbols: `4a:46ba Route27_MapScripts`, `4a:4cac Route27_MapEvents` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 33 | 7 | `ROUTE_27_SANDSTORM_HOUSE` | 1 | -| 2 | 26 | 5 | `TOHJO_FALLS` | 1 | -| 3 | 36 | 5 | `TOHJO_FALLS` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE27_FIRST_STEP_INTO_KANTO` (0) | 18 | 10 | `FirstStepIntoKantoLeftScene` | fisher turns LEFT, `showemote EMOTE_SHOCK`, `applymovement ROUTE27_FISHER, Route27FisherStepLeftTwiceMovement` (two `step LEFT`) | -| `SCENE_ROUTE27_FIRST_STEP_INTO_KANTO` (0) | 19 | 10 | `FirstStepIntoKantoRightScene` | same but one `step LEFT` | - -Both fall into `FirstStepIntoKantoScene_Continue`: `turnobject PLAYER, RIGHT`, -`writetext Route27FisherHeyText`, `writetext Route27FisherText` ("You've taken -your first step into KANTO"), then `setscene SCENE_ROUTE27_NOOP` (1) so it never -fires again. The scene constants are generated by the `def_scene_scripts` / -`scene_script` macros in `macros/scripts/maps.asm`; the sym table confirms -`SCENE_ROUTE27_FIRST_STEP_INTO_KANTO = 00` and `SCENE_ROUTE27_NOOP = 01`. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 25 | 7 | `BGEVENT_READ` | `TohjoFallsSign` ("TOHJO FALLS / The Link Between KANTO and JOHTO") | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE27_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 49 | 7 | `STANDING_UP` | `TRAINER` (sight 3) | `TrainerCooltrainermBlake` | -1 | -| `ROUTE27_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 58 | 6 | `STANDING_LEFT` | `TRAINER` (sight 5) | `TrainerCooltrainermBrian` | -1 | -| `ROUTE27_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 72 | 11 | `STANDING_UP` | `TRAINER` (sight 5) | `TrainerCooltrainerfReena` | -1 | -| `ROUTE27_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 37 | 6 | `SPINCLOCKWISE` | `TRAINER` (sight 2) | `TrainerCooltrainerfMegan` | -1 | -| `ROUTE27_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 64 | 7 | `STANDING_LEFT` | `TRAINER` (sight 5) | `TrainerPsychicGilbert` | -1 | -| `ROUTE27_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 58 | 13 | `STANDING_RIGHT` | `TRAINER` (sight 3) | `TrainerBirdKeeperJose2` | -1 | -| `ROUTE27_POKE_BALL1` | `SPRITE_POKE_BALL` | 60 | 12 | `STILL` | `ITEMBALL` | `Route27TMSolarbeam` (`itemball TM_SOLARBEAM`) | `EVENT_ROUTE_27_TM_SOLARBEAM` | -| `ROUTE27_POKE_BALL2` | `SPRITE_POKE_BALL` | 53 | 12 | `STILL` | `ITEMBALL` | `Route27RareCandy` (`itemball RARE_CANDY`) | `EVENT_ROUTE_27_RARE_CANDY` | -| `ROUTE27_FISHER` | `SPRITE_FISHER` | 21 | 10 | `SPINRANDOM_SLOW` | `SCRIPT` (sight 3) | `Route27FisherScript` (`jumptextfaceplayer Route27FisherText`) | -1 | - -**Scripts of interest** - -- `FirstStepIntoKantoLeftScene` (`4a:46c6`) / `FirstStepIntoKantoRightScene` - (`4a:46d4`) - the only forced cutscene on this route, described above. It is - a coord event on the two cells (18, 10) and (19, 10), so a bot that lands on - Route 27 by surfing will cross it. -- `TrainerBirdKeeperJose2` (`4a:4706`), `trainer BIRD_KEEPER, JOSE2, EVENT_BEAT_BIRD_KEEPER_JOSE2, ...`, - phone contact `PHONE_BIRDKEEPER_JOSE`, `EVENT_JOSE_ASKED_FOR_PHONE_NUMBER`, - `EVENT_JOSE_READY_FOR_REMATCH`; rematch parties `JOSE1` (after Elite Four) - and `JOSE3` (after Kanto power restored). -- `TrainerCooltrainerfReena` (`4a:47bc`), `trainer COOLTRAINERF, REENA1, ...`, - phone contact `PHONE_COOLTRAINERF_REENA`; rematch parties `REENA2` / `REENA3`. -- `TrainerCooltrainermBlake` (`4a:4794`), `TrainerCooltrainermBrian` - (`4a:47a8`), `TrainerPsychicGilbert` (`4a:46f2`), - `TrainerCooltrainerfMegan` (`4a:484a`) - plain after-battle text. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `SCENE_ROUTE27_FIRST_STEP_INTO_KANTO` = 0 | generated in `maps/Route27.asm` (`def_scene_scripts`) | coord events; cleared by `setscene SCENE_ROUTE27_NOOP` | the Kanto welcome fires exactly once | -| `SCENE_ROUTE27_NOOP` = 1 | same | `FirstStepIntoKantoScene_Continue` | scene after the cutscene | -| `EVENT_BEAT_COOLTRAINERM_BLAKE` | `constants/event_flags.asm:867` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERM_BRIAN` | `:868` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERF_REENA` | `:886` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERF_MEGAN` | `:887` | `trainer` header | set on win | -| `EVENT_BEAT_PSYCHIC_GILBERT` | `:572` | `trainer` header | set on win | -| `EVENT_BEAT_BIRD_KEEPER_JOSE2` | `:507` | `trainer` header | set on win | -| `EVENT_ROUTE_27_TM_SOLARBEAM` | `:1100` | item ball object | one-time pickup | -| `EVENT_ROUTE_27_RARE_CANDY` | `:1101` | item ball object | one-time pickup | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SOLARBEAM` (TM22) | item ball at (60, 12), on the island past the whirlpool | `Route27TMSolarbeam` | `EVENT_ROUTE_27_TM_SOLARBEAM` | -| `RARE_CANDY` | item ball at (53, 12), in the southern water | `Route27RareCandy` | `EVENT_ROUTE_27_RARE_CANDY` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `COOLTRAINERF, MEGAN` | `COOLTRAINERF` | 11 | "MEGAN" (`TRAINERTYPE_MOVES`): 32 BULBASAUR (GROWL/LEECH_SEED/POISONPOWDER/RAZOR_LEAF), 32 IVYSAUR (same), 32 VENUSAUR (BODY_SLAM/SLEEP_POWDER/RAZOR_LEAF/SWEET_SCENT) | `TrainerCooltrainerfMegan` | no | -| `COOLTRAINERM, BLAKE` | `COOLTRAINERM` | 11 | "BLAKE" (`TRAINERTYPE_MOVES`): 33 MAGNETON (THUNDERBOLT/SUPERSONIC/SWIFT/SCREECH), 31 QUAGSIRE (WATER_GUN/SLAM/AMNESIA/EARTHQUAKE), 31 EXEGGCUTE (LEECH_SEED/CONFUSION/SLEEP_POWDER/SOLARBEAM) | `TrainerCooltrainermBlake` | no | -| `COOLTRAINERM, BRIAN` | `COOLTRAINERM` | 12 | "BRIAN" (`TRAINERTYPE_MOVES`): 35 SANDSLASH (SAND_ATTACK/POISON_STING/SLASH/SWIFT) | `TrainerCooltrainermBrian` | no | -| `PSYCHIC_T, GILBERT` | `PSYCHIC_T` (`$34`) | 10 | "GILBERT": 30 STARMIE, 30 EXEGGCUTE, 34 GIRAFARIG | `TrainerPsychicGilbert` | no | -| `BIRD_KEEPER, JOSE2` | `BIRD_KEEPER` (`$18`) | 14 | "JOSE": 35 FARFETCH_D | `TrainerBirdKeeperJose2` | `PHONE_BIRDKEEPER_JOSE` | -| `COOLTRAINERF, REENA1` | `COOLTRAINERF` | 10 | "REENA": 31 STARMIE, 33 NIDOQUEEN, 31 STARMIE | `TrainerCooltrainerfReena` | `PHONE_COOLTRAINERF_REENA` | - -Note the party order for Reena is STARMIE / NIDOQUEEN / STARMIE in the asm; the -walkthrough lists it STARMIE / STARMIE / NIDOQUEEN. - -**Wild encounters** - -`data/wild/kanto_grass.asm:1172` `def_grass_wildmons ROUTE_27`, `10 percent`: - -- Gold morn: 28 DODUO, 28 RATICATE, 30 DODUO, 28 QUAGSIRE, 32 PONYTA, - 30 SANDSLASH, 30 SANDSLASH. -- Gold day: 28 DODUO, 28 RATICATE, 30 DODUO, 30 RATICATE, 32 PONYTA, - 30 SANDSLASH, 30 SANDSLASH. -- Gold nite: 28 QUAGSIRE, 28 RATICATE, 30 QUAGSIRE, 30 RATICATE, 32 PONYTA, - 30 SANDSLASH, 30 SANDSLASH. -- Silver swaps DODRIO/ARBOK in for some of those; see the `ELIF DEF(_SILVER)` - block at the same label. - -`data/wild/kanto_water.asm:96` `def_water_wildmons ROUTE_27`, 6 percent: -20 TENTACOOL, 15 TENTACOOL, 20 TENTACRUEL. - -Fishing group `FISHGROUP_OCEAN`. Headbutt: -`data/wild/treemon_maps.asm:8` -> `TREEMON_SET_FOREST` (Gold: CATERPIE / -METAPOD / EXEGGCUTE / BUTTERFREE common, PINECO in the rare table). - -The walkthrough's Route 27 list omits Doduo's Gold-only DODRIO slots and does -not mention that ARBOK is Silver-only. - ---- - -### MAP_TOHJO_FALLS - -- Script: `maps/TohjoFalls.asm` -- Blocks: `maps/TohjoFalls.blk` -- Header: `data/maps/maps.asm:152` -> `map TohjoFalls, TILESET_CAVE, CAVE, LANDMARK_TOHJO_FALLS, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` - (`TRUE` in the phone slot = no phone calls inside) -- Dimensions: `constants/map_constants.asm:139` -> `map_const TOHJO_FALLS, 15, 9` (30 x 18 cells) -- Connections: none (`data/maps/attributes.asm:471` is a bare `map_attributes` row) -- Symbols: `47:4a20 TohjoFalls_MapScripts`, `47:4a24 TohjoFalls_MapEvents` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 15 | `ROUTE_27` | 2 | -| 2 | 25 | 15 | `ROUTE_27` | 3 | - -**Coord events** / **BG events** - -Both empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `TOHJOFALLS_POKE_BALL` | `SPRITE_POKE_BALL` | 2 | 6 | `STILL` | `ITEMBALL` | `TohjoFallsMoonStone` (`itemball MOON_STONE`) | `EVENT_TOHJO_FALLS_MOON_STONE` | - -**Scripts of interest** - -`TohjoFallsMoonStone` (`47:4a22`) is the only script on the map. There is no -`def_scene_scripts` body and no callbacks - the whole map is terrain. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_TOHJO_FALLS_MOON_STONE` | `constants/event_flags.asm:1098` | item ball object | one-time pickup | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MOON_STONE` | item ball at (2, 6) - across the water, west end | `TohjoFallsMoonStone` | `EVENT_TOHJO_FALLS_MOON_STONE` | - -**Trainers** - -None. - -**Wild encounters** - -`data/wild/kanto_grass.asm:199` `def_grass_wildmons TOHJO_FALLS`, encounter -rate 4 percent in Gold / 6 percent in Silver, identical morn/day/nite: -22 ZUBAT, 22 RATICATE, 22 GOLBAT, 21 SLOWPOKE, 20 RATTATA, 23 SLOWPOKE, -23 SLOWPOKE. - -`data/wild/kanto_water.asm:103` `def_water_wildmons TOHJO_FALLS`, 4 percent: -20 GOLDEEN, 20 SLOWPOKE, 20 SEAKING. - -Fishing group `FISHGROUP_LAKE`. No treemon map entry (it is a cave). - -The walkthrough's list (Rattata / Slowpoke / Seaking) is a subset - ZUBAT, -GOLBAT, RATICATE and GOLDEEN are also here. - ---- - -### MAP_ROUTE_27_SANDSTORM_HOUSE - -- Script: `maps/Route27SandstormHouse.asm` -- Blocks: none (indoor, uses the shared house layout) -- Header: `data/maps/maps.asm:483` -> `map Route27SandstormHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_27, MUSIC_AZALEA_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:448` -> `map_const ROUTE_27_SANDSTORM_HOUSE, 4, 4` (8 x 8 cells) -- Symbols: `60:6352 SandstormHouseWoman` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_27` | 1 | -| 2 | 3 | 7 | `ROUTE_27` | 1 | - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `SandstormHouseBookshelf` (`jumpstd MagazineBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `SandstormHouseBookshelf` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE27SANDSTORMHOUSE_GRANNY` | `SPRITE_GRANNY` | 2 | 4 | `STANDING_DOWN` | `SCRIPT` | `SandstormHouseWoman` | -1 | - -**Scripts of interest** - -`SandstormHouseWoman` (`60:6352`): - -``` -faceplayer / opentext -checkevent EVENT_GOT_TM37_SANDSTORM ; iftrue .AlreadyGotItem -special GetFirstPokemonHappiness -writetext SandstormHouseWomanText1 / promptbutton -ifgreater 150 - 1, .Loyal ; i.e. happiness >= 150 -sjump .Disloyal -.Loyal: writetext SandstormHouseWomanLoyalText / promptbutton - verbosegiveitem TM_SANDSTORM / iffalse .Done - setevent EVENT_GOT_TM37_SANDSTORM -``` - -The precise gate is **happiness of the first non-egg party member >= 150** -(`ifgreater 150 - 1`), not "one of your Pokemon is happy" as the walkthrough -puts it - only the first party slot is read, by -`special GetFirstPokemonHappiness`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_TM37_SANDSTORM` | `constants/event_flags.asm:126` | `SandstormHouseWoman` | one-time; also short-circuits straight to the TM description text | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_SANDSTORM` (TM37) | talk to the granny with a >= 150 happiness lead mon | `SandstormHouseWoman.Loyal` | `EVENT_GOT_TM37_SANDSTORM` | - ---- - -### MAP_ROUTE_26 - -- Script: `maps/Route26.asm` -- Blocks: `maps/Route26.blk` -- Header: `data/maps/maps.asm:472` -> `map Route26, TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_26, MUSIC_ROUTE_26, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:437` -> `map_const ROUTE_26, 10, 54` (20 x 108 cells) -- Connections (`data/maps/attributes.asm:167`): `connection west, Route27, ROUTE_27, 45` (west only - the north end is the Victory Road gate warp, not a connection) -- Symbols: `4a:4000 Route26_MapScripts`, `4a:4638 Route26_MapEvents` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 5 | `VICTORY_ROAD_GATE` | 3 | -| 2 | 15 | 57 | `ROUTE_26_HEAL_HOUSE` | 1 | -| 3 | 5 | 71 | `DAY_OF_WEEK_SIBLINGS_HOUSE` | 1 | - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 8 | 6 | `BGEVENT_READ` | `Route26Sign` ("ROUTE 26 / #MON LEAGUE RECEPTION GATE") | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE26_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 11 | 16 | `STANDING_RIGHT` | `TRAINER` (sight 2) | `TrainerCooltrainermJake` | -1 | -| `ROUTE26_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 9 | 38 | `STANDING_LEFT` | `TRAINER` (sight 5) | `TrainerCooltrainermGaven3` | -1 | -| `ROUTE26_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 10 | 56 | `SPINRANDOM_FAST` | `TRAINER` (sight 3) | `TrainerCooltrainerfJoyce` | -1 | -| `ROUTE26_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 5 | 8 | `STANDING_RIGHT` | `TRAINER` (sight 4) | `TrainerCooltrainerfBeth1` | -1 | -| `ROUTE26_YOUNGSTER` | `SPRITE_YOUNGSTER` | 13 | 79 | `STANDING_RIGHT` | `TRAINER` (sight 4) | `TrainerPsychicRichard` | -1 | -| `ROUTE26_FISHER` | `SPRITE_FISHER` | 10 | 92 | `STANDING_DOWN` | `TRAINER` (sight 3) | `TrainerFisherScott` | -1 | -| `ROUTE26_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 14 | 54 | `STILL` | `SCRIPT` | `Route26FruitTree` (`fruittree FRUITTREE_ROUTE_26`) | -1 | -| `ROUTE26_POKE_BALL` | `SPRITE_POKE_BALL` | 9 | 15 | `STILL` | `ITEMBALL` | `Route26MaxElixer` (`itemball MAX_ELIXER`) | `EVENT_ROUTE_26_MAX_ELIXER` | - -The y coordinates run south (high) to north (low), which matches the -walkthrough's order exactly: Scott (92) -> Richard (79) -> Day-of-Week house -(71) -> heal house (57) -> Joyce (56) -> Ice Berry tree (54) -> Gaven (38) -> -Jake (16) -> Max Elixer (15) -> Beth (8) -> gate warp (5). - -**Scripts of interest** - -- `TrainerCooltrainermGaven3` (`4a:4016`), header - `trainer COOLTRAINERM, GAVEN3, EVENT_BEAT_COOLTRAINERM_GAVEN, ...`. Phone - contact `PHONE_COOLTRAINERM_GAVEN`; rematch parties `GAVEN1` (after Elite - Four) and `GAVEN2` (after Kanto power restored). His after-battle text is the - one that names Victory Road as the way to the League. -- `TrainerCooltrainerfBeth1` (`4a:40b8`), phone `PHONE_COOLTRAINERF_BETH`; - rematch parties `BETH2` / `BETH3`. -- `TrainerCooltrainermJake` (`4a:4002`), `TrainerCooltrainerfJoyce` - (`4a:40a4`), `TrainerPsychicRichard` (`4a:4146`), `TrainerFisherScott` - (`4a:415a`) - plain after-battle text. -- `Route26FruitTree` = `FRUITTREE_ROUTE_26` = index 14 = `ICE_BERRY`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_FISHER_SCOTT` | `constants/event_flags.asm:605` | `trainer` header | set on win | -| `EVENT_BEAT_PSYCHIC_RICHARD` | `:571` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERF_JOYCE` | `:884` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERM_GAVEN` | `:866` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERM_JAKE` | `:865` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERF_BETH` | `:885` | `trainer` header | set on win | -| `EVENT_GAVEN_ASKED_FOR_PHONE_NUMBER` / `EVENT_GAVEN_READY_FOR_REMATCH` | `constants/event_flags.asm` | `TrainerCooltrainermGaven3.Script` | phone / rematch pair | -| `EVENT_BETH_ASKED_FOR_PHONE_NUMBER` / `EVENT_BETH_READY_FOR_REMATCH` | `constants/event_flags.asm` | `TrainerCooltrainerfBeth1.Script` | phone / rematch pair | -| `EVENT_ROUTE_26_MAX_ELIXER` | `:1099` | item ball object | one-time pickup | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_ELIXER` | item ball at (9, 15) | `Route26MaxElixer` | `EVENT_ROUTE_26_MAX_ELIXER` | -| `ICE_BERRY` | fruit tree at (14, 54) | `Route26FruitTree` / `FRUITTREE_ROUTE_26` | daily fruit-tree flag | - -There is exactly **one** Max Elixer on Route 26. The walkthrough mentions it -twice ("grab the Max Elixir" then "you can also pick up an Max Elixir if you -take the jumps back to heal") - both refer to the same ball at (9, 15). - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `FISHER, SCOTT` | `FISHER` (`$25`) | 21 | `FisherGroup` "SCOTT": 30 QWILFISH, 30 QWILFISH, 34 SEAKING | `TrainerFisherScott` | no | -| `PSYCHIC_T, RICHARD` | `PSYCHIC_T` | 9 | "RICHARD": 36 ESPEON | `TrainerPsychicRichard` | no | -| `COOLTRAINERF, JOYCE` | `COOLTRAINERF` | 8 | "JOYCE" (`TRAINERTYPE_MOVES`): 36 PIKACHU (QUICK_ATTACK/DOUBLE_TEAM/THUNDERBOLT/THUNDER), 32 BLASTOISE (BITE/CURSE/SURF/RAIN_DANCE) | `TrainerCooltrainerfJoyce` | no | -| `COOLTRAINERM, GAVEN3` | `COOLTRAINERM` | 10 | "GAVEN" (`TRAINERTYPE_MOVES`): 32 VICTREEBEL (WRAP/TOXIC/ACID/RAZOR_LEAF), 32 **KINGLER** (BUBBLEBEAM/STOMP/GUILLOTINE/PROTECT), 32 FLAREON (SAND_ATTACK/QUICK_ATTACK/BITE/FIRE_SPIN) | `TrainerCooltrainermGaven3` | `PHONE_COOLTRAINERM_GAVEN` | -| `COOLTRAINERM, JAKE` | `COOLTRAINERM` | 9 | "JAKE" (`TRAINERTYPE_MOVES`): 33 PARASECT (LEECH_LIFE/SPORE/SLASH/SWORDS_DANCE), 35 GOLDUCK (CONFUSION/SCREECH/PSYCH_UP/FURY_SWIPES) | `TrainerCooltrainermJake` | no | -| `COOLTRAINERF, BETH1` | `COOLTRAINERF` | 9 | "BETH" (`TRAINERTYPE_MOVES`): 36 RAPIDASH (STOMP/FIRE_SPIN/FURY_ATTACK/AGILITY) | `TrainerCooltrainerfBeth1` | `PHONE_COOLTRAINERF_BETH` | - -**Wild encounters** - -`data/wild/kanto_grass.asm:1117` `def_grass_wildmons ROUTE_26`, `10 percent`: - -- Gold morn: 28 DODUO, 28 SANDSLASH, 32 PONYTA, 30 DODUO, 30 DODRIO, - 30 RATICATE, 30 QUAGSIRE. -- Gold day: 28 DODUO, 28 SANDSLASH, 32 PONYTA, 30 DODUO, 30 RATICATE, - 30 DODRIO, 30 DODRIO. -- Gold nite: 28 RATICATE, 28 SANDSLASH, 32 PONYTA, 30 RATICATE, 30 QUAGSIRE, - 32 QUAGSIRE, 32 QUAGSIRE. -- Silver replaces SANDSLASH with RATICATE and DODRIO with ARBOK; see the - `ELIF DEF(_SILVER)` block at the same label. - -`data/wild/kanto_water.asm:89` `def_water_wildmons ROUTE_26`, 6 percent: -30 TENTACOOL, 25 TENTACOOL, 30 TENTACRUEL. - -Fishing group `FISHGROUP_OCEAN`. Headbutt: -`data/wild/treemon_maps.asm:7` -> `TREEMON_SET_FOREST`. - -Note SANDSLASH is Gold-only on Route 26; the walkthrough lists ARBOK, which is -the Silver slot. - ---- - -### MAP_ROUTE_26_HEAL_HOUSE - -- Script: `maps/Route26HealHouse.asm` -- Header: `data/maps/maps.asm:481` -> `map Route26HealHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_26, MUSIC_AZALEA_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:446` -> `map_const ROUTE_26_HEAL_HOUSE, 4, 4` -- Symbols: `60:60e6 Route26HealHouseTeacherScript` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_26` | 2 | -| 2 | 3 | 7 | `ROUTE_26` | 2 | - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `Route26HealHouseBookshelf` (`jumpstd PictureBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `Route26HealHouseBookshelf` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE26HEALHOUSE_TEACHER` | `SPRITE_TEACHER` | 2 | 3 | `STANDING_DOWN` | `SCRIPT` | `Route26HealHouseTeacherScript` | -1 | - -**Scripts of interest** - -`Route26HealHouseTeacherScript` (`60:60e6`): -`faceplayer` / `opentext` / `writetext Route26HealHouseRestAWhileText` / -`waitbutton` / `closetext` / `special FadeOutToBlack` / -`special ReloadSpritesNoPalettes` / `playmusic MUSIC_HEAL` / -`special HealParty` / `pause 60` / `special FadeInFromBlack` / -`special RestartMapMusic` / `writetext Route26HealHouseKeepAtItText` / `end`. - -**No flag guards this at all** - it is unconditional and repeatable, which is -what makes it a free Pokemon Center for a bot grinding Route 26. - ---- - -### MAP_DAY_OF_WEEK_SIBLINGS_HOUSE - -- Script: `maps/DayOfWeekSiblingsHouse.asm` -- Header: `data/maps/maps.asm:482` -> `map DayOfWeekSiblingsHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_26, MUSIC_AZALEA_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:447` -> `map_const DAY_OF_WEEK_SIBLINGS_HOUSE, 4, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_26` | 3 | -| 2 | 3 | 7 | `ROUTE_26` | 3 | - -**Coord events** / **BG events** - -Both empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DAYOFWEEKSIBLINGSHOUSE_POKEDEX` | `SPRITE_POKEDEX` | 3 | 3 | `STILL` | `SCRIPT` | `DayOfWeekSiblingsHousePokedexScript` | -1 | - -**Scripts of interest** - -`DayOfWeekSiblingsHousePokedexScript` is two nested `yesorno` prompts over -`DayOfWeekSiblingsHousePokedexText1/2/3`. Text 3 is Monica's roster, and it -matches the walkthrough exactly: Monday MONICA / ROUTE 40, Tuesday TUSCANY / -ROUTE 29, Wednesday WESLEY / LAKE OF RAGE, Thursday ARTHUR / ROUTE 36, and so -on. Pure text - no flags, no items. - ---- - -### MAP_VICTORY_ROAD_GATE (badge check only) - -Victory Road itself is section 17. Transcribed here because the gate is this -section's terminal blocker. - -- Script: `maps/VictoryRoadGate.asm` -- Header: `data/maps/maps.asm:467` -> `map VictoryRoadGate, TILESET_GATE, GATE, LANDMARK_ROUTE_26, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:433` -> `map_const VICTORY_ROAD_GATE, 10, 9` -- Symbols: `5f:4fdc VictoryRoadGateBadgeCheckScript`, `5f:4ff8 _VictoryRoadGateBadgeCheckScript.AllEightBadges` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 7 | `ROUTE_22` | 1 | -| 2 | 18 | 7 | `ROUTE_22` | 1 | -| 3 | 9 | 17 | `ROUTE_26` | 1 | -| 4 | 10 | 17 | `ROUTE_26` | 1 | -| 5 | 9 | 0 | `VICTORY_ROAD` | 1 | -| 6 | 10 | 0 | `VICTORY_ROAD` | 1 | -| 7 | 1 | 7 | `ROUTE_28` | 2 | -| 8 | 2 | 7 | `ROUTE_28` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_VICTORYROADGATE_BADGE_CHECK` (0) | 10 | 11 | `VictoryRoadGateBadgeCheckScript` | `turnobject PLAYER, LEFT` then the shared badge check | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VICTORYROADGATE_OFFICER` | `SPRITE_OFFICER` | 8 | 11 | `STANDING_RIGHT` | `SCRIPT` | `VictoryRoadGateOfficerScript` | -1 | -| `VICTORYROADGATE_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 7 | 5 | `STANDING_RIGHT` | `SCRIPT` | `VictoryRoadGateLeftBlackBeltScript` | `EVENT_OPENED_MT_SILVER` | -| `VICTORYROADGATE_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 12 | 5 | `STANDING_LEFT` | `SCRIPT` | `VictoryRoadGateRightBlackBeltScript` | `EVENT_FOUGHT_SNORLAX` | - -**Scripts of interest** - -`_VictoryRoadGateBadgeCheckScript`: - -``` -opentext / writetext VictoryRoadGateOfficerText / promptbutton -readvar VAR_BADGES -ifgreater NUM_JOHTO_BADGES - 1, .AllEightBadges ; badges > 7 -writetext VictoryRoadGateNotEnoughBadgesText / waitbutton / closetext -applymovement PLAYER, VictoryRoadGateStepDownMovement ; one step DOWN -end -.AllEightBadges: -writetext VictoryRoadGateEightBadgesText / waitbutton / closetext -setscene SCENE_VICTORYROADGATE_NOOP -end -``` - -`readvar VAR_BADGES` is a **popcount of the Johto badge byte**, not a badge -index. `setscene SCENE_VICTORYROADGATE_NOOP` (1) permanently disarms the coord -event once you pass. The two black belts are hidden until -`EVENT_OPENED_MT_SILVER` / `EVENT_FOUGHT_SNORLAX` -(`constants/event_flags.asm:1265`, `:1266`), so on a first pass neither is -present. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| New Bark Town -> Route 27 crossing | `engine/events/overworld.asm` `TrySurfOW` (`ld de, ENGINE_FOGBADGE` / `ld d, SURF` / `CheckPartyMove`) | SURF in the party **and** FOGBADGE | Beat Morty (section 07) | -| Route 27 east-half water (Blake onward, Rare Candy) | same `TrySurfOW` | SURF + FOGBADGE | as above | -| Tohjo Falls falls | `engine/events/overworld.asm` `TryWaterfallOW` (`ld d, WATERFALL` / `CheckPartyMove` then `ld de, ENGINE_RISINGBADGE` / `CheckEngineFlag`) plus `CheckMapCanWaterfall` (must face UP and the tile above must pass `CheckWaterfallTile`, `home/map_objects.asm:185`, i.e. `COLL_WATERFALL` $33 or `COLL_CURRENT_DOWN` $3b) | WATERFALL + RISINGBADGE | Beat Clair (section 13) | -| Route 27 whirlpool (island with TM22 + Jose) | `engine/events/overworld.asm` `TryWhirlpoolOW` (`ld d, WHIRLPOOL` / `CheckPartyMove`, then `ld de, ENGINE_GLACIERBADGE` / `CheckEngineFlag`, then `TryWhirlpoolMenu`) | WHIRLPOOL + GLACIERBADGE | Beat Pryce (section 11) | -| Master Ball from Elm | `maps/ElmsLab.asm` `ElmCheckMasterBall` -> `checkflag ENGINE_RISINGBADGE` | RISINGBADGE set, `EVENT_GOT_MASTER_BALL_FROM_ELM` clear, and a free bag slot (`verbosegiveitem` `iffalse`) | Beat Clair | -| Everstone from Elm | `maps/ElmsLab.asm` `ElmCheckEverstone` -> `EVENT_SHOWED_TOGEPI_TO_ELM`, otherwise `setval TOGEPI` / `special FindPartyMonThatSpeciesYourTrainerID` (and `TOGETIC`), gated on `EVENT_TOLD_ELM_ABOUT_TOGEPI_OVER_THE_PHONE` | Togepi or Togetic **in the party**, hatched by you (OT check) | Withdraw it from the PC before talking | -| TM37 SANDSTORM | `maps/Route27SandstormHouse.asm` `SandstormHouseWoman` -> `special GetFirstPokemonHappiness` / `ifgreater 150 - 1` | first non-egg party member's happiness >= 150 | Walk/level/haircut the lead mon | -| Victory Road Gate | `maps/VictoryRoadGate.asm` `_VictoryRoadGateBadgeCheckScript` -> `readvar VAR_BADGES` / `ifgreater NUM_JOHTO_BADGES - 1` | all eight Johto badges | Fails soft: you get pushed one step DOWN by `VictoryRoadGateStepDownMovement`, the gate is not warp-locked | -| Route 45 / 46 Dark Cave side trips | plain warps, no check | - | none; the Dark Cave interior gates are section 15's | - -Nothing on Routes 45, 46 or 26 is gated by an `EVENT_*` or a badge. Route 27 is -the only map in this section where a field move blocks forward progress. - ---- - -## 4. Bot checklist - -Preconditions for the whole section: eight Johto badges, SURF + WATERFALL + -WHIRLPOOL in the party, at least a few free bag slots. - -Route 45 (entered from Blackthorn's south edge): - -1. `MAP_ROUTE_45` - walk south from the north connection. Optional: warp 1 at - (2, 5) into Dark Cave Blackthorn Entrance. -2. Item ball `ROUTE45_POKE_BALL3` at (4, 21) -> ELIXER. Post: `EVENT_ROUTE_45_ELIXER`. -3. Trainer `ROUTE45_POKEFAN_M3` at (5, 28), sight 3 -> Hiker Parry (`PARRY3`, - 29 ONIX). Post: `EVENT_BEAT_HIKER_PARRY`. Talk again twice for - `PHONE_HIKER_PARRY`. -4. Trainer `ROUTE45_POKEFAN_M1` at (10, 16), sight 1 -> Hiker Erik. - Post: `EVENT_BEAT_HIKER_ERIK`. -5. Trainer `ROUTE45_COOLTRAINER_M` at (17, 18), sight 1 -> Cooltrainer Ryan. - Post: `EVENT_BEAT_COOLTRAINERM_RYAN`. -6. Item ball `ROUTE45_POKE_BALL4` at (8, 33) -> MAX_POTION. - Post: `EVENT_ROUTE_45_MAX_POTION`. -7. Trainer `ROUTE45_COOLTRAINER_F` at (4, 36), sight 3 -> Cooltrainer Kelly. - Post: `EVENT_BEAT_COOLTRAINERF_KELLY`. -8. Trainer `ROUTE45_BLACK_BELT` at (11, 50), sight 2, spins -> Blackbelt Kenji. - Post: `EVENT_BEAT_BLACKBELT_KENJI`. Second talk sets - `EVENT_KENJI_ASKED_FOR_PHONE_NUMBER` and offers `PHONE_BLACKBELT_KENJI`. -9. Item ball `ROUTE45_POKE_BALL1` at (6, 51) -> X_SPECIAL. - Post: `EVENT_ROUTE_45_X_SPECIAL`. -10. Trainer `ROUTE45_POKEFAN_M4` at (9, 64), sight 1 -> Hiker Timothy. - Post: `EVENT_BEAT_HIKER_TIMOTHY`. -11. Trainer `ROUTE45_POKEFAN_M2` at (15, 64), sight 2 -> Hiker Michael. - Post: `EVENT_BEAT_HIKER_MICHAEL`. (The walkthrough never names him.) -12. Item ball `ROUTE45_POKE_BALL2` at (6, 66) -> REVIVE. - Post: `EVENT_ROUTE_45_REVIVE`. -13. Hidden item: face the bg event at (13, 80), press A -> PP_UP. - Post: `EVENT_ROUTE_45_HIDDEN_PP_UP`. -14. Fruit tree `ROUTE45_FRUIT_TREE` at (16, 82) -> MYSTERYBERRY (daily). -15. Cross the west connection into `MAP_ROUTE_46` (block offset 36). - -Route 46: - -16. Trainer `ROUTE46_POKEFAN_M` at (12, 18), sight 2 -> Hiker Bailey (five - 13 GEODUDE). Post: `EVENT_BEAT_HIKER_BAILEY`. -17. Trainer `ROUTE46_YOUNGSTER` at (3, 13), sight 4 -> Camper Ted. - Post: `EVENT_BEAT_CAMPER_TED`. -18. Item ball `ROUTE46_POKE_BALL` at (0, 12) -> DIRE_HIT. - Post: `EVENT_ROUTE_46_DIRE_HIT`. -19. Trainer `ROUTE46_LASS` at (1, 15), sight 4 -> Picnicker Erin. - Post: `EVENT_BEAT_PICNICKER_ERIN`. Second talk offers `PHONE_PICNICKER_ERIN`. -20. Fruit trees at (7, 5) -> BERRY and (8, 6) -> PRZCUREBERRY (daily). -21. Optional warp 3 at (14, 5) -> Dark Cave Violet Entrance. -22. Exit south via warps 1/2 at (7, 33) / (8, 33) into - `MAP_ROUTE_29_ROUTE_46_GATE`, or the south connection to Route 29. - -New Bark Town: - -23. FLY to `MAP_NEW_BARK_TOWN`; warp 1 at (6, 3) -> `MAP_ELMS_LAB`. -24. Talk to `ELMSLAB_ELM` at (5, 2). Pre: `ENGINE_RISINGBADGE` set, - `EVENT_GOT_MASTER_BALL_FROM_ELM` clear, free bag slot. - Post: `EVENT_GOT_MASTER_BALL_FROM_ELM`, MASTER_BALL in bag. -25. Withdraw Togepi/Togetic into the party at a PC, return to Elm, talk again. - Post: `EVENT_SHOWED_TOGEPI_TO_ELM`, `EVENT_GOT_EVERSTONE_FROM_ELM`, - EVERSTONE in bag. -26. Optional: `MAP_PLAYERS_HOUSE_1F` warp 2 at (13, 5), talk to Mom -> - `MomScript.BankOfMom` -> `special BankOfMom` to withdraw savings. -27. Leave New Bark Town east; the connection to `MAP_ROUTE_27` is water. Use - SURF (FOGBADGE required). - -Route 27, first pass: - -28. Walking onto cell (18, 10) or (19, 10) fires the coord event - `FirstStepIntoKantoLeftScene` / `...RightScene`. It is unskippable; wait it - out. Post: map scene = `SCENE_ROUTE27_NOOP`. -29. Optional: item ball `ROUTE27_POKE_BALL2` at (53, 12) -> RARE_CANDY, in the - southern water. Post: `EVENT_ROUTE_27_RARE_CANDY`. (See section 6 - the - walkthrough puts this before Tohjo Falls; the coordinate is well east of - the west cave mouth.) -30. Warp 2 at (26, 5) -> `MAP_TOHJO_FALLS` warp 1. - -Tohjo Falls: - -31. Surf west, use WATERFALL facing UP where the tile above is `COLL_WATERFALL`. - Requires RISINGBADGE. -32. Item ball `TOHJOFALLS_POKE_BALL` at (2, 6) -> MOON_STONE. - Post: `EVENT_TOHJO_FALLS_MOON_STONE`. -33. Exit via warp 2 at (25, 15) -> `MAP_ROUTE_27` warp 3 (36, 5). - -Route 27, second pass: - -34. Trainer `ROUTE27_COOLTRAINER_F2` at (37, 6), sight 2, spins -> Cooltrainer - Megan. Post: `EVENT_BEAT_COOLTRAINERF_MEGAN`. -35. Warp 1 at (33, 7) -> `MAP_ROUTE_27_SANDSTORM_HOUSE`. Talk to - `ROUTE27SANDSTORMHOUSE_GRANNY` at (2, 4). Pre: lead party mon happiness - >= 150 and a free bag slot. Post: `EVENT_GOT_TM37_SANDSTORM`, TM37 in bag. -36. Surf east. Trainer `ROUTE27_COOLTRAINER_M1` at (49, 7), sight 3 -> - Cooltrainer Blake. Post: `EVENT_BEAT_COOLTRAINERM_BLAKE`. -37. Trainer `ROUTE27_COOLTRAINER_M2` at (58, 6), sight 5 -> Cooltrainer Brian. - Post: `EVENT_BEAT_COOLTRAINERM_BRIAN`. -38. Trainer `ROUTE27_YOUNGSTER1` at (64, 7), sight 5 -> Psychic Gilbert. - Post: `EVENT_BEAT_PSYCHIC_GILBERT`. -39. Surf south, use WHIRLPOOL on the whirlpool block (GLACIERBADGE required). -40. Trainer `ROUTE27_YOUNGSTER2` at (58, 13), sight 3 -> Bird Keeper Jose. - Post: `EVENT_BEAT_BIRD_KEEPER_JOSE2`. Second talk offers - `PHONE_BIRDKEEPER_JOSE`. -41. Item ball `ROUTE27_POKE_BALL1` at (60, 12) -> TM_SOLARBEAM. - Post: `EVENT_ROUTE_27_TM_SOLARBEAM`. -42. Trainer `ROUTE27_COOLTRAINER_F1` at (72, 11), sight 5 -> Cooltrainer Reena. - Post: `EVENT_BEAT_COOLTRAINERF_REENA`. Second talk offers - `PHONE_COOLTRAINERF_REENA`. -43. East connection into `MAP_ROUTE_26` (block offset -45). - -Route 26 (walk north, y decreasing): - -44. Trainer `ROUTE26_FISHER` at (10, 92), sight 3 -> Fisher Scott. - Post: `EVENT_BEAT_FISHER_SCOTT`. -45. Trainer `ROUTE26_YOUNGSTER` at (13, 79), sight 4 -> Psychic Richard. - Post: `EVENT_BEAT_PSYCHIC_RICHARD`. -46. Optional: warp 3 at (5, 71) -> `MAP_DAY_OF_WEEK_SIBLINGS_HOUSE`, read the - `SPRITE_POKEDEX` object at (3, 3) for Monica's roster. No flags. -47. Warp 2 at (15, 57) -> `MAP_ROUTE_26_HEAL_HOUSE`, talk to - `ROUTE26HEALHOUSE_TEACHER` at (2, 3) for a free full heal. Repeatable, no - flag. -48. Trainer `ROUTE26_COOLTRAINER_F1` at (10, 56), sight 3, spins -> Cooltrainer - Joyce. Post: `EVENT_BEAT_COOLTRAINERF_JOYCE`. -49. Fruit tree `ROUTE26_FRUIT_TREE` at (14, 54) -> ICE_BERRY (daily). -50. Trainer `ROUTE26_COOLTRAINER_M2` at (9, 38), sight 5 -> Cooltrainer Gaven. - Post: `EVENT_BEAT_COOLTRAINERM_GAVEN`. Second talk offers - `PHONE_COOLTRAINERM_GAVEN`. -51. Trainer `ROUTE26_COOLTRAINER_M1` at (11, 16), sight 2 -> Cooltrainer Jake. - Post: `EVENT_BEAT_COOLTRAINERM_JAKE`. -52. Item ball `ROUTE26_POKE_BALL` at (9, 15) -> MAX_ELIXER. - Post: `EVENT_ROUTE_26_MAX_ELIXER`. -53. Trainer `ROUTE26_COOLTRAINER_F2` at (5, 8), sight 4 -> Cooltrainer Beth. - Post: `EVENT_BEAT_COOLTRAINERF_BETH`. Second talk offers - `PHONE_COOLTRAINERF_BETH`. -54. Warp 1 at (7, 5) -> `MAP_VICTORY_ROAD_GATE` warp 3. -55. In the gate, step onto (10, 11). Pre: `VAR_BADGES` >= 8. On pass the map - scene becomes `SCENE_VICTORYROADGATE_NOOP` and warps 5/6 at (9, 0) / - (10, 0) lead into `MAP_VICTORY_ROAD` (section 17). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map blocks, warps, coord events, bg events, object events for every map here | `src/import/RomExtractorGen2.lua:787-980` (`warps` / `coordEvents` / `bgEvents` / `objects` readers), `src/world/gen2/Map.lua`, `src/world/gen2/World.lua:5013` (coord events), `:5257` (`World:interact`) | implemented, data-driven - nothing per-map is hand-written, so these routes come for free once the ROM is imported | -| Map connections (Blackthorn -> R45 -> R46, New Bark -> R27 -> R26) | `src/world/gen2/World.lua:407-430` | implemented | -| Overworld trainer struct, eyesight, battle, beat flag | `src/world/gen2/Trainers.lua`, `src/import/RomExtractorGen2.lua:2963-2968` (reads `OBJECTTYPE_TRAINER` headers), driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| Trainer parties incl. `TRAINERTYPE_MOVES` rows (Timothy, Ryan, Blake, Brian, Joyce, Gaven, Jake, Beth, Megan) | `src/world/gen2/Trainers.lua` party build over the extracted trainer table | implemented | -| Phone registration + rematch flags for Kenji, Parry, Erin, Jose, Reena, Gaven, Beth | `src/core/gen2/Phone.lua:427-455` (`Phone.REMATCH_EVENTS`, contacts 11/12/13/14/34/35/36 present) | implemented | -| `askforphonenumber` / `checkcellnum` opcodes | `src/script/gen2/Opcodes.lua:156`, `:47` | implemented | -| Fruit trees (Mystery Berry, Berry, PrzCure Berry, Ice Berry) | `src/script/gen2/Opcodes.lua:160` (`fruittree`), `src/core/gen2/Apricorns.lua:351-460` (item lookup, picked flag, daily reset) | implemented | -| Hidden item PP UP at Route 45 (13, 80) | `src/world/gen2/HiddenItems.lua`, consumed at `src/world/gen2/World.lua:5285` | implemented | -| **Item ball pickup** (all nine `OBJECTTYPE_ITEMBALL` objects in this section) | extractor stores `obj.itemball` (`src/import/RomExtractorGen2.lua:2968`) but nothing in `src/world/` reads it - `World:interact` only dispatches on `npc.def.trainer`, strength boulders, `npc.def.scriptKey` and `HiddenItems` | **missing** - walking up to a Poke Ball object and pressing A does nothing. This blocks ELIXER, MAX_POTION, X_SPECIAL, REVIVE, DIRE_HIT, RARE_CANDY, TM_SOLARBEAM, MOON_STONE, MAX_ELIXER | -| SURF gate (FOGBADGE) for the New Bark -> Route 27 crossing | `src/world/gen2/FieldMoves.lua:103-110` (`FieldMoves.BADGE.SURF = "FOG"`), driver `tests/drivers/gold_water_moves.lua` | implemented | -| WHIRLPOOL gate (GLACIERBADGE) + block replacement for the Route 27 island | `src/world/gen2/FieldMoves.lua:219-251`, `World:tryWhirlpoolOW`, driver `tests/drivers/gold_water_moves.lua` | implemented | -| WATERFALL climb (RISINGBADGE) in Tohjo Falls | `src/world/gen2/FieldMoves.lua:258-266`, `src/world/gen2/World.lua:4037-4046` (`runWaterfall` / `waterfallStep`), `:4217` (`tryWaterfallOW`), `src/world/gen2/Permissions.lua:136` (`COLL_WATERFALL` $33 + `COLL_CURRENT_DOWN` $3b) | implemented; **no driver** covers it (`gold_water_moves.lua` is SURF + WHIRLPOOL only) | -| Route 27 "first step into Kanto" coord event + scene | `src/world/gen2/World.lua:5013` (coord event scan), `:1161-1183` (`World:scene`), `src/script/gen2/Opcodes.lua:25` (`setscene`), `:122-123` (`showemote`, `turnobject`), `:110` (`applymovement`) | implemented | -| Pokegear region flip to Kanto after that scene | `src/ui/gen2/Pokegear.lua:1070`, `:1658` (region follows the player's landmark, `cp KANTO_LANDMARK`) | implemented | -| Sandstorm house happiness check | `src/script/gen2/Specials.lua:1506` (`GetFirstPokemonHappiness`), `src/script/gen2/Opcodes.lua:163` (`verbosegiveitem`), `src/core/gen2/Happiness.lua` | implemented | -| Route 26 heal house | `src/script/gen2/Specials.lua:450` (`HealParty`), `:999` (`FadeOutToBlack`), `:1025` (`ReloadSpritesNoPalettes`), `:1059` (`RestartMapMusic`) | implemented | -| Elm Master Ball / Everstone (`checkflag ENGINE_RISINGBADGE`, `FindPartyMonThatSpeciesYourTrainerID`) | `src/script/gen2/Specials.lua:1134`, `src/script/gen2/Opcodes.lua:163` | implemented | -| Mom's savings (`special BankOfMom`) | `src/ui/gen2/BankOfMom.lua` | implemented | -| Victory Road gate badge check (`readvar VAR_BADGES`) | `src/world/gen2/World.lua:117` (`VAR_BADGES = 0x07`), `:1240` (popcount read) | implemented | -| Roaming beasts on Routes 45/46 | `src/core/gen2/Roamers.lua`, driver `tests/drivers/gold_roamers.lua` | implemented | -| Wild encounter tables (grass morn/day/nite, water, fish, treemons) | `src/battle/gen2/Encounter.lua` over the extracted encounter tables | implemented | - ---- - -## 6. Unresolved / verify by hand - -1. **"Camper Quentin" does not exist in pokegold.** The walkthrough puts a - Camper Quentin on Route 45 with 27 FEAROW / 30 PRIMEAPE / 30 TAUROS. There - is no `QUENTIN` constant anywhere in the checkout - (`grep -rn QUENTIN /Users/bryanbassett/Documents/development/pokegold` is - empty), no camper object on `maps/Route45.asm`, and the only TAUROS line in - `data/trainers/parties.asm` is a level 35 one in `CooltrainerMGroup` "SEAN". - The Route 45 trainer the walkthrough is probably confusing this with is - Hiker Michael (25 GEODUDE / 25 GRAVELER / 25 GOLEM), whom it never mentions. -2. **"A Revive and a Nugget" on Route 45.** There is no NUGGET on Route 45. - The four item balls are X_SPECIAL, REVIVE, ELIXER and MAX_POTION, and the - only hidden item is `hiddenitem PP_UP, EVENT_ROUTE_45_HIDDEN_PP_UP` at - (13, 80). Treat "Nugget" as an error and pick up the PP UP instead. -3. **Route 27 Rare Candy placement.** The walkthrough says to surf "southeast - of the cave entrance" *before* entering Tohjo Falls. The item ball is at - cell (53, 12), which is east of the *eastern* Tohjo exit (36, 5) and far - east of the western entrance (26, 5). Whether the southern water is - continuous from the landing point (~x 18-21) all the way to x 53 was not - verified - it would need reading `maps/Route27.blk` against the - `TILESET_JOHTO` collision table. A bot should simply grab it on the way to - Blake rather than trusting the stated ordering. -4. **Cooltrainer Gaven's second mon.** The walkthrough says Krabby; the asm - says `db 32, KINGLER` in `CooltrainerMGroup` entry 10. -5. **Cooltrainer Reena's party order.** Walkthrough: STARMIE, STARMIE, - NIDOQUEEN. Asm (`CooltrainerFGroup` entry 10): 31 STARMIE, 33 NIDOQUEEN, - 31 STARMIE. -6. **Hiker Erik's party order.** Walkthrough: Machop, Machop, Graveler. Asm - (`HikerGroup` entry 7): 24 MACHOP, 27 GRAVELER, 27 MACHOP. -7. **Wild lists are incomplete in the walkthrough.** Route 46 also has - JIGGLYPUFF (3 and 5); Tohjo Falls also has ZUBAT, GOLBAT, RATICATE and - GOLDEEN; Route 26/27's ARBOK entries are Silver-only while SANDSLASH and - DODRIO are the Gold slots. The walkthrough's per-route lists appear to be a - Gold/Silver merge. -8. **Tohjo Falls MOON STONE is never mentioned.** `TohjoFallsMoonStone` at - (2, 6) is the only object on the map. -9. **"Head down the other waterfall".** There is no downward WATERFALL move in - the engine. Descending is a `COLL_CURRENT_DOWN` ($3b) tile that carries the - surfing player; `CheckWaterfallTile` (`home/map_objects.asm:185`) accepts - both `COLL_WATERFALL` and `COLL_CURRENT_DOWN` precisely so the climb loop - keeps running. Whether the port's `World:waterfallStep` handles the descend - direction as well as the climb was not verified. -10. **Kenji's phone number timing.** The walkthrough says "talk to him again - and he will give you his phone number", which matches - but the asm needs - *two* post-battle talks: the first sets - `EVENT_KENJI_ASKED_FOR_PHONE_NUMBER` and runs `AskNumber1MScript`, and - `askforphonenumber` only runs after that `scall` returns. Bots should - budget two A-press cycles per phone trainer, not one. -11. The walkthrough's Route 45 "LEFT PATH ... Elixir ... one trainer (Hiker - Parry)" reads as if the Elixir is inside Dark Cave. Both the Elixir - (4, 21) and Parry (5, 28) are on Route 45 itself. There *is* a separate - hidden Elixer inside Dark Cave (`maps/DarkCaveVioletEntrance.asm` - `bg_event 26, 3, BGEVENT_ITEM, DarkCaveVioletEntranceHiddenElixer`), which - belongs to section 15. diff --git a/docs/gold-walkthrough/asm-walk/section-17-victory-road.md b/docs/gold-walkthrough/asm-walk/section-17-victory-road.md deleted file mode 100644 index b7bbe653..00000000 --- a/docs/gold-walkthrough/asm-walk/section-17-victory-road.md +++ /dev/null @@ -1,599 +0,0 @@ -# Section 17 - Victory Road - -Source: `../section-17-victory-road.txt` (the FAQ numbers this chapter "23 > Victory Road") -Maps covered: `MAP_VICTORY_ROAD`, `MAP_VICTORY_ROAD_GATE` (entry gate, badge check) -Badges / key milestones in this section: no badge. Milestones are the fifth and -final `RIVAL1` battle (`EVENT_RIVAL_VICTORY_ROAD`), TM26 Earthquake, and the -north exit onto Route 23 / Indigo Plateau. - -Important structural fact the walkthrough hides: **Gold/Silver Victory Road is a -single 10x36-block map**, not three floors. What the FAQ calls "first / second / -third floor" are three vertically stacked regions of the same map joined by -`warp_event` rows whose destination map is `VICTORY_ROAD` itself. Every "ladder" -and the "hole" are self-warps inside `maps/VictoryRoad.asm`. - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_VICTORY_ROAD_GATE` | `maps/VictoryRoadGate.asm` | gate warps 3/4 at (9,17)/(10,17), arriving from Route 26 (`maps/Route26.asm:397` warps to `VICTORY_ROAD_GATE, 3`) | gate warps 5/6 at (9,0)/(10,0) -> `VICTORY_ROAD` warp 1 | 8-badge check at coord event (10,11); belongs to the previous section but is the only door in | -| 1 | `MAP_VICTORY_ROAD` (entrance region, y 48..71) | `maps/VictoryRoad.asm` | warp 1 at (9,67) | warp 2, the ladder at (1,49) | "Head up the stairway and up the cliff"; Full Heal (15,48) and Max Revive (12,48) sit here, plus the hidden Full Heal at (3,65) | -| 2 | `MAP_VICTORY_ROAD` (mid region, y 32..43) | same | warp 3 at (1,35) | warp 4, the ladder at (13,31) | "second floor" - X Special at (7,38), then "up along the path ... another ladder" | -| 3 | `MAP_VICTORY_ROAD` (top region, y 5..21) | same | warp 5 at (13,17) | warp 7, the ladder at (17,19) | "Save when you get to the third floor. Go up the cliff to your left, go down the ladder" | -| 4 | `MAP_VICTORY_ROAD` (Full Restore shelf, y 28..34) | same | warp 6 at (17,33) | one-way `HOP_DOWN` ledge row y=34, x=16..19, landing on y=36 | "grab the Full Restore, and jump down" | -| 5 | `MAP_VICTORY_ROAD` (mid region again) | same | ledge landing (x,36) | warp 4 at (13,31) | "Head back up the ladder to the third floor" | -| 6 | `MAP_VICTORY_ROAD` (top region) | same | warp 5 at (13,17) | coord events at (12,8) / (13,8) | rival ambush, `VictoryRoadRivalLeft` / `VictoryRoadRivalRight` | -| 7 | `MAP_VICTORY_ROAD` (TM pocket, y 26..30) | same | warp 8, the `COLL_PIT` at (0,11) | warp 8's landing is warp 9 at (0,27); leave by `HOP_RIGHT` at (8,28)->(10,28) or `HOP_DOWN` at (2,30)/(3,30)->y=32 | "go left instead of out the door, and fall down the hole to get TM26 Earthquake" | -| 8 | `MAP_VICTORY_ROAD` exit | same | corridor x=12..13 at y=7..8, x=11..14 at y=6 | warp 10 at (13,5) -> `ROUTE_23` warp 3 | leave the dungeon | -| 9 | `MAP_ROUTE_23` | `maps/Route23.asm` | warp 3 at (9,13) | - | **next section** (Indigo Plateau / Pokemon League). `Route23FlypointCallback` sets `ENGINE_FLYPOINT_INDIGO_PLATEAU` on arrival. Stop here. | - -## 2. Maps - -### MAP_VICTORY_ROAD - -- Script: `maps/VictoryRoad.asm` (included from `data/maps/scripts.asm:24`) -- Blocks: `maps/VictoryRoad.blk` (360 bytes; `data/maps/blocks.asm:962`) -- Header: `data/maps/maps.asm:160` - `map VictoryRoad, TILESET_CAVE, CAVE, LANDMARK_VICTORY_ROAD, MUSIC_VICTORY_ROAD, TRUE, PALETTE_NITE, FISHGROUP_SHORE` - (per the `map` macro at the head of `data/maps/maps.asm`: tileset, environment, - location, music, phone-service flag TRUE = phone calls suppressed, time-of-day - palette, fishing group.) -- Attributes: `data/maps/attributes.asm:494` `map_attributes VictoryRoad, VICTORY_ROAD, $1d` - border block `$1d`, and **no `connection` rows follow**, so the map has no overworld connections. -- Dimensions: `constants/map_constants.asm:147` `map_const VICTORY_ROAD, 10, 36` -> 10x36 blocks = **20x72 walk cells**. Group `DUNGEONS` (group 3), map id 82. -- Scene variable: `data/maps/scenes.asm:60` `scene_var VICTORY_ROAD, wVictoryRoadSceneID` (sym `01:d6eb`) -- Callbacks: `def_callbacks` is empty - no `MAPCALLBACK_*` for this map. -- Connections: none. - -**Warps** (`def_warp_events`, transcribed from `maps/VictoryRoad.asm:242-252`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 67 | `VICTORY_ROAD_GATE` | 5 | -| 2 | 1 | 49 | `VICTORY_ROAD` | 3 | -| 3 | 1 | 35 | `VICTORY_ROAD` | 2 | -| 4 | 13 | 31 | `VICTORY_ROAD` | 5 | -| 5 | 13 | 17 | `VICTORY_ROAD` | 4 | -| 6 | 17 | 33 | `VICTORY_ROAD` | 7 | -| 7 | 17 | 19 | `VICTORY_ROAD` | 6 | -| 8 | 0 | 11 | `VICTORY_ROAD` | 9 | -| 9 | 0 | 27 | `VICTORY_ROAD` | 8 | -| 10 | 13 | 5 | `ROUTE_23` | 3 | - -Self-warp pairs, as a bot should read them: 2<->3, 4<->5, 6<->7, 8->9. -Warp 8's tile is `COLL_PIT` and warp 9's tile is plain `COLL_FLOOR`, so **warp 9 -never fires**: the hole at (0,11) is one-way down to (0,27). (Tile kinds verified -below.) - -**Coord events** (`def_coord_events`, `maps/VictoryRoad.asm:254-256`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_VICTORYROAD_RIVAL_BATTLE` (0) | 12 | 8 | `VictoryRoadRivalLeft` (sym `42:59ba`) | rival is teleported to (18,11), walks LEFTx6 + UPx2 to (12,9), battle, then leaves DOWNx2 + RIGHTx6 | -| `SCENE_VICTORYROAD_RIVAL_BATTLE` (0) | 13 | 8 | `VictoryRoadRivalRight` (sym `42:59dd`) | rival walks from (18,13) UPx2 + LEFTx5 + UPx2 to (13,9), battle, then leaves DOWNx2 + RIGHTx5 + DOWNx2 | - -These two tiles are the entire width of the corridor that leads to the exit -(y=8 is walkable only at x=6,7 and x=12,13), so the ambush is unavoidable. - -**BG events** (`def_bg_events`, `maps/VictoryRoad.asm:258-260`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 29 | `BGEVENT_ITEM` | `VictoryRoadHiddenMaxPotion` = `hiddenitem MAX_POTION, EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` | -| 3 | 65 | `BGEVENT_ITEM` | `VictoryRoadHiddenFullHeal` = `hiddenitem FULL_HEAL, EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` | - -Both target tiles are `COLL_WALL`, i.e. the player stands next to them and -presses A into the wall. (3,29) is faced from (2,29), (4,29) or (3,28); -(3,65) is faced from (2,65), (4,65), (3,64) or (3,66). - -**Object events** (`def_object_events`, `maps/VictoryRoad.asm:262-268`) - -Column order follows the `object_event` macro in `macros/scripts/maps.asm:113`: -x, y, sprite, movement, radius x, radius y, hour1, hour2, palette, type, sight, -script, event flag. - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VICTORYROAD_RIVAL` | `SPRITE_RIVAL` | 18 | 13 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (shared ROM0 stub, sym `00:2812`) | `EVENT_RIVAL_VICTORY_ROAD` | -| `VICTORYROAD_POKE_BALL1` | `SPRITE_POKE_BALL` | 3 | 28 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadTMEarthquake` = `itemball TM_EARTHQUAKE` | `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` | -| `VICTORYROAD_POKE_BALL2` | `SPRITE_POKE_BALL` | 12 | 48 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadMaxRevive` = `itemball MAX_REVIVE` | `EVENT_VICTORY_ROAD_MAX_REVIVE` | -| `VICTORYROAD_POKE_BALL3` | `SPRITE_POKE_BALL` | 18 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadFullRestore` = `itemball FULL_RESTORE` | `EVENT_VICTORY_ROAD_FULL_RESTORE` | -| `VICTORYROAD_POKE_BALL4` | `SPRITE_POKE_BALL` | 15 | 48 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadFullHeal` = `itemball FULL_HEAL` | `EVENT_VICTORY_ROAD_FULL_HEAL` | -| `VICTORYROAD_POKE_BALL5` | `SPRITE_POKE_BALL` | 7 | 38 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadXSpecial` = `itemball X_SPECIAL` | `EVENT_VICTORY_ROAD_X_SPECIAL` | - -All rows use radius 0,0, hours -1,-1 (always present), palette 0, sight 0. - -Object-flag polarity (this bites bot authors): `CheckObjectFlag` in -`engine/overworld/map_objects_2.asm:32` masks an object when its event flag is -**set**, and shows it when the flag is clear or is `-1`. `Script_appear` clears -the flag, `Script_disappear` sets it (`engine/overworld/scripting.asm:879-897`). -`InitializeEventsScript` in `engine/events/std_scripts.asm:521` sets -`EVENT_RIVAL_VICTORY_ROAD` at new game, so the rival object starts hidden and is -only revealed by the `appear` inside the ambush script. - -**Derived collision map** (not verbatim asm - built by decoding -`maps/VictoryRoad.blk` through `data/tilesets/cave_collision.asm` and -`data/collision/collision_permissions.asm`, with the quadrant index -`(x & 1) + 2 * (y & 1)` taken from `GetCoordTileCollision` in -`home/map.asm:2065`). It is included because Victory Road is the one map in this -section where "which tile can I stand on" is the whole problem. - -Verification: all ten `warp_event` tiles land on the expected collision -(`WARP_CARPET_DOWN`, six `LADDER`, `PIT`, `FLOOR`, `CAVE`) and all six -`object_event` tiles land on walkable tiles, so the decode is aligned. - -Legend: `.` floor, `#` wall (impassable), `^` `COLL_UP_WALL` (walkable, but you -cannot step *down* into it and cannot step *up* off it), `L` ladder, `P` pit, -`C` cave exit, `W` warp carpet (needs a DOWN press), `<` `HOP_LEFT`, -`>` `HOP_RIGHT`, `v` `HOP_DOWN` (all three are walkable land; pressing the -matching direction while standing on one jumps two tiles - see `.TryJump` and -`.ledge_table` in `engine/overworld/player_movement.asm:354-391`, which reads the -tile the player is **standing on**, not the tile ahead). - -``` - 01234567890123456789 - 0 .................... - 1 .................... - 2 .................... - 3 .................... - 4 .................... - 5 #############C###### - 6 ###########....##### - 7 ############..###### - 8 ######..####..###### - 9 #####....##....##### - 10 ......##...........# - 11 P....####........... - 12 ...######^^^^^^^^#.. - 13 #########........#.. - 14 #^^^^^^##........#.. - 15 #......##..#####.#.. - 16 #......##..#........ - 17 ###.#..##..#.L..#### - 18 #...#......#####..## - 19 #...##########...L## - 20 ##......>#.......### - 21 ###.....>#.....##### - 22 #################### - 23 #################### - 24 #################### - 25 #################### - 26 #...################ - 27 .......############# - 28 ........>#...####### - 29 ...#....>#....####.# - 30 ##vv..#####...####.. - 31 ###########..L###... - 32 ##..########..##.... - 33 ....########..##.L.. - 34 ..#^^^^^^^^#...#vvvv - 35 .L#........#...##### - 36 ..#........#........ - 37 ..###.###..##....... - 38 ......#<#...^^^^^#.. - 39 #..#..#<#........#.. - 40 ##....#<#........#.. - 41 ####..#<#####.####.. - 42 #######............. - 43 ##########........## - 44 #################### - 45 #################### - 46 #################### - 47 #################### - 48 ....########.##..### - 49 .L..########.###..## - 50 #.....####........## - 51 #......#####..#....# - 52 #...#^^^^^^^^^^^^#.. - 53 #...#............#.. - 54 ##..#............#.. - 55 ##..###.###..##.##.. - 56 ##........#..##..... - 57 ##........#..##....# - 58 ##.##^^####..####### - 59 ##..#..####..####### - 60 ##vv#...^^...####### - 61 #####........####### - 62 ##..#........######^ - 63 ##..###.###########. - 64 #....#..##.....####. - 65 #..#....#......####. - 66 ##............###^.. - 67 ######...W##.####... - 68 #######^^#^^^^^^.... - 69 #######............. - 70 ^^^^^^.............. - 71 .................... -``` - -One-way transitions a bot must respect (all derived from the grid above): - -- (0,11) `PIT` -> (0,27). No route back up from (0,27); the return is a ledge. -- (8,28) or (8,29) `HOP_RIGHT` + RIGHT -> (10,28) / (10,29), which reaches the (13,31) ladder. This is the short way out of the TM pocket. -- (2,30) or (3,30) `HOP_DOWN` + DOWN -> (2,32) / (3,32), which drops into the pocket that holds the (1,35) ladder. This is the long way out of the TM pocket. -- (16..19, 34) `HOP_DOWN` + DOWN -> (16..19, 36). This is the only exit from the Full Restore shelf, and it is why the FAQ says "jump down". -- (8,20) or (8,21) `HOP_RIGHT` + RIGHT -> (10,20) / (10,21) in the top region. -- Rows of `^` (`COLL_UP_WALL`) are walkable dead strips: you may enter them from the side or from below and leave sideways or downward, never upward, and you can never step down onto one. - -**Scripts of interest** - -- `VictoryRoadRivalLeft` (`maps/VictoryRoad.asm:22`, sym `42:59ba`) - `moveobject VICTORYROAD_RIVAL, 18, 11` / `turnobject PLAYER, DOWN` / - `showemote EMOTE_SHOCK, PLAYER, 15` / `special FadeOutMusic` / `pause 15` / - `appear VICTORYROAD_RIVAL` / `applymovement` `VictoryRoadRivalBattleApproachMovement1` - (LEFT x6, UP x2) / `scall VictoryRoadRivalNext` / - `applymovement VictoryRoadRivalBattleExitMovement1` (DOWN x2, RIGHT x6) / - `disappear VICTORYROAD_RIVAL` / `setscene SCENE_VICTORYROAD_NOOP` / - `playmapmusic` / `end`. -- `VictoryRoadRivalRight` (`maps/VictoryRoad.asm:37`, sym `42:59dd`) - identical - except there is no `moveobject` (he starts at his object_event position 18,13), - the approach is `VictoryRoadRivalBattleApproachMovement2` (UP x2, LEFT x5, - UP x2) and the exit is `VictoryRoadRivalBattleExitMovement2` (DOWN x2, - RIGHT x5, DOWN x2). -- `VictoryRoadRivalNext` (`maps/VictoryRoad.asm:51`, sym `42:59fc`) - the shared - body. `turnobject PLAYER, DOWN`, `playmusic MUSIC_RIVAL_ENCOUNTER`, text - `VictoryRoadRivalBeforeText`, then `setevent EVENT_RIVAL_VICTORY_ROAD` - (**before** the battle, so a reset or blackout mid-battle still leaves the - object hidden), then a three-way starter branch: - - `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `.GotTotodile` -> `loadtrainer RIVAL1, RIVAL1_5_CHIKORITA` - - `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `.GotChikorita` -> `loadtrainer RIVAL1, RIVAL1_5_CYNDAQUIL` - - fall-through (player picked Cyndaquil) -> `loadtrainer RIVAL1, RIVAL1_5_TOTODILE` - - Each arm runs `winlosstext VictoryRoadRivalDefeatText, VictoryRoadRivalVictoryText`, - `setlasttalked VICTORYROAD_RIVAL`, `startbattle`, `dontrestartmapmusic`, - `reloadmapafterbattle`, then `.AfterBattle`: `playmusic MUSIC_RIVAL_AFTER` and - `VictoryRoadRivalAfterText`. -- `VictoryRoadTMEarthquake` / `MaxRevive` / `FullRestore` / `FullHeal` / - `XSpecial` (`maps/VictoryRoad.asm:97-110`) - each is a bare - `itemball <ITEM>` (two bytes: item, quantity 1; `macros/scripts/maps.asm:155`), - not bytecode. Do not try to disassemble them. -- `VictoryRoadHiddenMaxPotion` / `VictoryRoadHiddenFullHeal` - (`maps/VictoryRoad.asm:112-116`) - `hiddenitem item, flag`, three bytes laid - down as `dwb flag, item` (`macros/scripts/maps.asm:165`). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RIVAL_VICTORY_ROAD` | `constants/event_flags.asm:1124` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:521`) and again by `VictoryRoadRivalNext`; cleared by the `appear` in the ambush scripts | set = rival object masked. Postcondition of the ambush. | -| `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` | `constants/event_flags.asm:1092` | itemball object flag | set once TM26 is taken | -| `EVENT_VICTORY_ROAD_MAX_REVIVE` | `constants/event_flags.asm:1093` | itemball object flag | set once Max Revive is taken | -| `EVENT_VICTORY_ROAD_FULL_RESTORE` | `constants/event_flags.asm:1094` | itemball object flag | set once Full Restore is taken | -| `EVENT_VICTORY_ROAD_FULL_HEAL` | `constants/event_flags.asm:1095` | itemball object flag | set once Full Heal is taken | -| `EVENT_VICTORY_ROAD_X_SPECIAL` | `constants/event_flags.asm:1096` | itemball object flag | set once X Special is taken | -| `EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` | `constants/event_flags.asm:168` | `hiddenitem` operand at bg (3,29) | set once dug up | -| `EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:169` | `hiddenitem` operand at bg (3,65) | set once dug up | -| `EVENT_GOT_TOTODILE_FROM_ELM` (`:37`), `EVENT_GOT_CHIKORITA_FROM_ELM` (`:38`) | `constants/event_flags.asm` | read by `VictoryRoadRivalNext` | selects which of the three rival parties loads. `EVENT_GOT_CYNDAQUIL_FROM_ELM` (`:36`) is never read here; Cyndaquil is the fall-through case | -| `SCENE_VICTORYROAD_RIVAL_BATTLE` = 0 | defined inline by the `scene_script` macro (`macros/scripts/maps.asm:25`, `const_def` starting at 0) via `maps/VictoryRoad.asm:11` | compared by both coord events | the scene the map starts a new game on | -| `SCENE_VICTORYROAD_NOOP` = 1 | same, `maps/VictoryRoad.asm:12` | written by `setscene` at the end of both ambush scripts | rival ambush disarmed | - -Both scene scripts (`VictoryRoadNoop1Scene`, `VictoryRoadNoop2Scene`) are a bare -`end`; the scene id only gates the coord events. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_EARTHQUAKE` (TM26 - counted off the `add_tm` list in `constants/item_constants.asm:218-246`) | Poke Ball at (3,28), only reachable through the pit at (0,11) | `VictoryRoadTMEarthquake` | `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` | -| `MAX_REVIVE` | Poke Ball at (12,48) | `VictoryRoadMaxRevive` | `EVENT_VICTORY_ROAD_MAX_REVIVE` | -| `FULL_RESTORE` | Poke Ball at (18,29), reached by ladder warp 7 -> (17,33) then north | `VictoryRoadFullRestore` | `EVENT_VICTORY_ROAD_FULL_RESTORE` | -| `FULL_HEAL` | Poke Ball at (15,48) | `VictoryRoadFullHeal` | `EVENT_VICTORY_ROAD_FULL_HEAL` | -| `X_SPECIAL` | Poke Ball at (7,38), standing on the `HOP_LEFT` column; walk north from (7,42) | `VictoryRoadXSpecial` | `EVENT_VICTORY_ROAD_X_SPECIAL` | -| `MAX_POTION` | hidden, face the wall at (3,29) | `bg_event 3, 29, BGEVENT_ITEM` | `EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` | -| `FULL_HEAL` (second one) | hidden, face the wall at (3,65) | `bg_event 3, 65, BGEVENT_ITEM` | `EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` | - -**Trainers** - -There are no `OBJECTTYPE_TRAINER` objects on this map. The single battle is a -scripted `loadtrainer` with no `EVENT_BEAT_*` flag of its own. - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL1_5_CHIKORITA` | `RIVAL1` (trainer class 9, `constants/trainer_constants.asm:51`) | 13th member of the class | `Rival1Group` "RIVAL1 (13)" (`data/trainers/parties.asm:164`, sym `0e:5a92` for the group) | `VictoryRoadRivalNext.GotTotodile` (sym `42:5a27`) | none | -| `RIVAL1_5_CYNDAQUIL` | `RIVAL1` | 14th | `Rival1Group` "RIVAL1 (14)" (`data/trainers/parties.asm:174`) | `VictoryRoadRivalNext.GotChikorita` (sym `42:5a37`) | none | -| `RIVAL1_5_TOTODILE` | `RIVAL1` | 15th | `Rival1Group` "RIVAL1 (15)" (`data/trainers/parties.asm:184`) | fall-through arm of `VictoryRoadRivalNext` | none | - -Parties, verbatim (`TRAINERTYPE_MOVES`, name string `"?@"`): - -RIVAL1 (13) - loaded when the player chose Totodile: - -``` -db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER -db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK -db 35, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT -db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY -db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT -db 38, MEGANIUM, REFLECT, RAZOR_LEAF, POISONPOWDER, BODY_SLAM -``` - -RIVAL1 (14) - loaded when the player chose Chikorita: - -``` -db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER -db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK -db 35, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT -db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY -db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT -db 38, TYPHLOSION, SMOKESCREEN, EMBER, QUICK_ATTACK, FLAME_WHEEL -``` - -RIVAL1 (15) - loaded when the player chose Cyndaquil: - -``` -db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER -db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK -db 34, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT -db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY -db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT -db 38, FERALIGATR, RAGE, WATER_GUN, SCARY_FACE, SLASH -``` - -Note the send-out order: Sneasel, Golbat, Magneton, Haunter, Kadabra, starter. -The FAQ lists the starter fourth; the asm puts it last. - -**Wild encounters** - -`data/wild/kanto_grass.asm:144` `def_grass_wildmons VICTORY_ROAD` (Victory Road -lives in the Kanto grass table even though it is Johto-side). - -Encounter rates: `db 6 percent, 6 percent, 6 percent` (morn / day / nite - all -equal). Slot weights come from `GrassMonProbTable` -(`data/wild/probabilities.asm:6`): 30 / 30 / 20 / 10 / 5 / 4 / 1 percent. - -Gold (`IF DEF(_GOLD)`), identical for morn, day and nite: - -| slot | % | level | species | -|---|---|---|---| -| 1 | 30 | 32 | `GRAVELER` | -| 2 | 30 | 32 | `GOLBAT` | -| 3 | 20 | 33 | `URSARING` | -| 4 | 10 | 34 | `ONIX` | -| 5 | 5 | 36 | `ONIX` | -| 6 | 4 | 35 | `RHYHORN` | -| 7 | 1 | 35 | `RHYHORN` | - -Silver (`ELIF DEF(_SILVER)`) is the same table with slot 3 replaced by -`db 33, DONPHAN`. - -No water, fishing, headbutt or rock-smash data: there is no `VICTORY_ROAD` row in -`data/wild/kanto_water.asm`, `data/wild/johto_water.asm`, `data/wild/fish.asm` or -`data/wild/treemons.asm`, and the decoded collision map contains no water tiles. -The `FISHGROUP_SHORE` in the header is therefore unreachable. No roamer either -(`VICTORY_ROAD` is absent from `data/wild/roammon_maps.asm`). - -### MAP_VICTORY_ROAD_GATE - -Included here because it is the only entrance and it carries the badge gate. The -NPC beats themselves belong to the previous / later sections. - -- Script: `maps/VictoryRoadGate.asm` (`data/maps/scripts.asm:452`) -- Blocks: `maps/VictoryRoadGate.blk` (`data/maps/blocks.asm:943`) -- Header: `data/maps/maps.asm:467` - `map VictoryRoadGate, TILESET_GATE, GATE, LANDMARK_ROUTE_26, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Attributes: `data/maps/attributes.asm:666`, border block `$00`, no connections. -- Dimensions: `constants/map_constants.asm:433` `map_const VICTORY_ROAD_GATE, 10, 9` -> 20x18 cells. Group `VIRIDIAN` (group 23), map id 13. -- Scene variable: `data/maps/scenes.asm:16` `scene_var VICTORY_ROAD_GATE, wVictoryRoadGateSceneID` (sym `01:d6bf`) - -**Warps** (`maps/VictoryRoadGate.asm:101-109`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 7 | `ROUTE_22` | 1 | -| 2 | 18 | 7 | `ROUTE_22` | 1 | -| 3 | 9 | 17 | `ROUTE_26` | 1 | -| 4 | 10 | 17 | `ROUTE_26` | 1 | -| 5 | 9 | 0 | `VICTORY_ROAD` | 1 | -| 6 | 10 | 0 | `VICTORY_ROAD` | 1 | -| 7 | 1 | 7 | `ROUTE_28` | 2 | -| 8 | 2 | 7 | `ROUTE_28` | 2 | - -Cross-checked from the other side: `maps/Route22.asm:20` warps to -`VICTORY_ROAD_GATE, 1`, `maps/Route26.asm:397` warps to `VICTORY_ROAD_GATE, 3`, -`maps/Route28.asm:21` warps to `VICTORY_ROAD_GATE, 7`. - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_VICTORYROADGATE_BADGE_CHECK` (0) | 10 | 11 | `VictoryRoadGateBadgeCheckScript` (sym `5f:4fdc`) | turns the player LEFT and falls into `_VictoryRoadGateBadgeCheckScript` | - -**BG events**: none. - -**Object events** (`maps/VictoryRoadGate.asm:116-119`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VICTORYROADGATE_OFFICER` | `SPRITE_OFFICER` | 8 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateOfficerScript` | `-1` (always present) | -| `VICTORYROADGATE_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 7 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateLeftBlackBeltScript` | `EVENT_OPENED_MT_SILVER` | -| `VICTORYROADGATE_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 12 | 5 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateRightBlackBeltScript` | `EVENT_FOUGHT_SNORLAX` | - -**Derived collision map** (same method, `data/tilesets/gate_collision.asm`; -`c` = `COLL_COUNTER`, a wall): - -``` - 01234567890123456789 - 0 #########DD######### - 1 ########....######## - 2 ########....######## - 3 ########....######## - 4 ########....######## - 5 .................... - 6 ....####....####.... - 7 .WW.####....####.WW. - 8 ########....######## - 9 ########....######## - 10 ########cc..######## - 11 ########.c.c######## - 12 ########.c..######## - 13 ########cc..######## - 14 ########....######## - 15 ########....######## - 16 ########....######## - 17 ########.WW.######## -``` - -Row 11 is walkable only at x=8 (where the officer stands) and x=10, so -**(10,11) is a mandatory chokepoint**: the badge check cannot be walked around. - -**Scripts of interest** - -- `_VictoryRoadGateBadgeCheckScript` (`maps/VictoryRoadGate.asm:25`, sym `5f:4fe3`): - `writetext VictoryRoadGateOfficerText`, `promptbutton`, `readvar VAR_BADGES`, - `ifgreater NUM_JOHTO_BADGES - 1, .AllEightBadges`. On failure it prints - `VictoryRoadGateNotEnoughBadgesText` and `applymovement PLAYER, - VictoryRoadGateStepDownMovement` (one `step DOWN`, pushing the player back - south). On success it prints `VictoryRoadGateEightBadgesText` and - `setscene SCENE_VICTORYROADGATE_NOOP`, permanently disarming the coord event. -- `VictoryRoadGateOfficerScript` (`maps/VictoryRoadGate.asm:23`) - `faceplayer` - then the same body, so talking to the officer also opens the gate. - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Entry to Victory Road | `maps/VictoryRoadGate.asm:19` `VictoryRoadGateBadgeCheckScript` / `_VictoryRoadGateBadgeCheckScript` | `readvar VAR_BADGES` then `ifgreater NUM_JOHTO_BADGES - 1` (that is, badge count must be >= 8). `NUM_JOHTO_BADGES` = 8 (`constants/ram_constants.asm:260`). `VAR_BADGES` resolves to `.CountBadges` in `engine/overworld/variables.asm:80`, which is `CountSetBits` over **two** bytes (`wBadges`, b = 2), i.e. Johto **and** Kanto badges together | own all eight Johto badges. The script then sets `SCENE_VICTORYROADGATE_NOOP`, so the check runs at most once | -| Physical block west to Route 28 | `maps/VictoryRoadGate.asm:118` object `VICTORYROADGATE_BLACK_BELT1` at (7,5), masked when `EVENT_OPENED_MT_SILVER` is set | row 5 is the only east-west corridor in the gate and he stands in it | out of scope here (Mt. Silver, later section) | -| Physical block east to Route 22 | `maps/VictoryRoadGate.asm:119` object `VICTORYROADGATE_BLACK_BELT2` at (12,5), masked when `EVENT_FOUGHT_SNORLAX` is set | same corridor | out of scope here (Kanto, later section) | -| Rival ambush before the exit | `maps/VictoryRoad.asm:255-256` coord events at (12,8) and (13,8), gated on `SCENE_VICTORYROAD_RIVAL_BATTLE` | those two tiles are the whole corridor to the exit at (13,5) | win the battle; the script then runs `setscene SCENE_VICTORYROAD_NOOP` | -| TM26 Earthquake | geometry only, no flag: (3,28) is inside the y=26..30 pocket whose only entrance is the `COLL_PIT` at (0,11) | reach the top region first (so: beat the ladders, not the rival) | none | -| Full Restore | geometry only: (18,29) is on a shelf whose only entrance is warp 7 (17,19) -> (17,33) | reach the top region | none | - -Field moves: **none required**. There is no `COLL_CUT_TREE`, no water, no -strength boulder and no whirlpool anywhere in the decoded map, and the header -palette is `PALETTE_NITE`, not `PALETTE_DARK`, so **Flash is not needed** either. -The FAQ's "no puzzles to solve now" is accurate. - -## 4. Bot checklist - -Coordinates are map cells `(x, y)` on the named map. "step" means walk onto the -tile; ladders and the pit fire on arrival (`Permissions.isImmediateWarp` -equivalent: `COLL_LADDER` = `$72` and `COLL_PIT` = `$60` are warp collisions), -while the entrance carpet at (9,67) fires on a DOWN press. - -1. `MAP_VICTORY_ROAD_GATE`: from the Route 26 door at (9,17)/(10,17) walk north along x=10. Precondition: 8 Johto badges. Step (10,11) -> `VictoryRoadGateBadgeCheckScript` fires. Postcondition: `wVictoryRoadGateSceneID` = `SCENE_VICTORYROADGATE_NOOP`. If badges < 8 the script pushes you one step DOWN and you cannot pass. -2. `MAP_VICTORY_ROAD_GATE`: step (9,0) or (10,0) -> warp to `MAP_VICTORY_ROAD` (9,67). -3. `MAP_VICTORY_ROAD` entrance region. Waypoints that were checked against the - collision map: (9,66) -> west along y=66 to (7,66) -> north x=7 through - y=65,64 to (7,63) -> (7,62) -> east along y=62 to (11,62)/(12,62) -> north - x=11/12 through y=61..55 -> (11,54) -> the long y=53/54 corridor (x=5..16). -4. Max Revive and Full Heal: from the y=53/54 corridor go east to (15,54) -> - (15,55) -> (15,56) -> east along y=56 to (18,56) -> north along x=18 through - y=55,54,53,52 to (18,51) -> west along y=51 to (15,51) -> (15,50) -> (16,50) - -> (16,49) -> (16,48), press A facing LEFT - into (15,48) -> Full Heal (`EVENT_VICTORY_ROAD_FULL_HEAL`). Then west along - y=50 to (12,50) -> (12,49) -> press A facing UP into (12,48) -> Max Revive - (`EVENT_VICTORY_ROAD_MAX_REVIVE`). -5. Optional hidden Full Heal: face the wall at (3,65) from (2,65), (4,65), - (3,64) or (3,66) and press A. Postcondition - `EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL`. -6. Back along the y=53/54 corridor to its west end, (3,53) -> (2,52) -> (2,51) - -> (2,50) -> (1,49); the ladder fires on arrival -> warp 2 -> (1,35). -7. X Special. Verified chain from the ladder landing: (1,35) -> (1,36) -> (1,37) - -> (1,38) -> east along y=38 to (5,38) -> (5,37) -> (5,36) -> east along y=36 - to (9,36)/(10,36) -> (9,37)/(10,37) -> (9,38)/(10,38) -> south through - y=39,40 (x=9..16 is open) -> (13,41) -> (13,42) -> west along y=42 to (7,42) - -> north along x=7 (the `HOP_LEFT` column is walkable) through y=41,40,39 to - (7,38). Press A -> X Special. Postcondition `EVENT_VICTORY_ROAD_X_SPECIAL`. - Do **not** press LEFT while on that column (it triggers a two-tile jump into - a wall column). -8. To the (13,31) ladder: back to (7,42) -> east along y=42 to (18,42) -> north - along x=18 through y=41,40,39,38 to (18,37) -> west along y=37 then y=36 to - (13,36) -> north (13,35), (13,34), (13,33), (13,32) -> step (13,31) -> warp 4 - -> arrive (13,17). Note you cannot climb x=13 straight up from y=41: (13,38) - is `COLL_UP_WALL` and blocks the step up out of it, and the x=11 column walls - off the y=34..36 strip on its west side. -9. Optional Full Restore detour. The verified chain from the ladder to (17,19) is: - (13,17) -> (13,16) -> east along y=16 to (16,16) -> (16,15) -> (16,14) -> - west along y=14 to (9,14)/(10,14) -> south x=9/10 through y=15,16,17 to - (9,18)/(10,18) -> west along y=18 to (5,18) -> (5,17) -> (5,16) -> west along - y=16 to (3,16) -> (3,17) -> (3,18) -> (3,19) -> (3,20) -> east along y=20 to - (8,20) -> **press RIGHT to hop the ledge**, landing (10,20) -> east/north to - (16,19) -> (17,19). On the cart that `HOP_RIGHT` is the only way into the - (17,19) pocket. Step (17,19) -> warp 7 -> arrive (17,33). Walk north along - x=17..19 to (18,29), press A -> Full Restore. Postcondition - `EVENT_VICTORY_ROAD_FULL_RESTORE`. Return to y=34 (x=16..19) and press DOWN to - hop the ledge, landing on y=36 - which is already the y=36 corridor, so go - west to (13,36) and north to (13,31), warp 4, back to (13,17). -10. From (13,17): (13,16) -> east along y=16 to (18,16) -> north along x=18 - through (18,15), (18,14), (18,13) (the rival's masked object tile), (18,12) - to (18,11) -> west along y=11 to (13,11) -> north (13,10), (13,9) to (13,8). -11. Step (12,8) or (13,8) -> rival ambush. Precondition `wVictoryRoadSceneID` = 0. Battle `RIVAL1` member 13/14/15 depending on the starter events. Postconditions: `EVENT_RIVAL_VICTORY_ROAD` set, `wVictoryRoadSceneID` = `SCENE_VICTORYROAD_NOOP`. -12. TM26 detour. The x=6,7 wall pair on y=10 and the x=5..8 wall run on y=11 mean - you cannot walk straight west; the verified chain is (9,11) -> (9,10) -> - (8,10) -> (8,9) -> west along y=9 to (5,9) -> (5,10) -> west along y=10 to - (0,10) -> (0,11), the `COLL_PIT`, which fires on arrival -> land at (0,27). - Then (0,28) -> east along y=28 to (2,28), press A facing RIGHT into the ball - object at (3,28) -> TM26 Earthquake (`EVENT_VICTORY_ROAD_TM_EARTHQUAKE`). - Optionally step to (2,29) and press A facing RIGHT into the wall at (3,29) - -> hidden Max Potion (`EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION`). -13. Leave the pocket: east along y=28 to (7,28) -> step onto (8,28), the - `HOP_RIGHT` tile -> press RIGHT to hop to (10,28) -> (10,29) -> east to - (12,29) -> (12,30) -> (12,31) -> step (13,31) -> warp 4 -> (13,17). - (Alternative, longer: stand on (2,30) or (3,30) and press DOWN, landing at - y=32, which drops you back in the (1,35) ladder pocket and forces a full - re-cross of the middle region via steps 7 and 8.) -14. Repeat step 10 to return to the exit corridor. The coord events no longer fire. -15. Walk (13,7) -> (13,6) -> step (13,5) (`COLL_CAVE`) -> warp 10 -> `MAP_ROUTE_23` (9,13). End of section. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map header, dimensions, warps, coord events, bg events, object events for any Gold map | `src/import/RomExtractorGen2.lua` (map event reader at lines 785-865, `extractMaps` from 866) | implemented - data driven off the ROM, so `VICTORY_ROAD` needs no hand port | -| `OBJECTTYPE_ITEMBALL` item data extraction | `src/import/RomExtractorGen2.lua:2968` (`obj.itemball = readItemBall(...)`) | implemented (extraction only) | -| **Picking up a Poke Ball object** (all five Victory Road items) | `src/world/gen2/World.lua:5257` `World:interact` | **missing** - the A-press path handles trainers, strength boulders, `scriptKey` NPCs, `BGEVENT_READ` signs, `BGEVENT_ITEM` hidden items and the tile-collision field moves. Nothing consumes `obj.itemball`, and an itemball object has no `scriptKey`, so the press falls through | -| Hidden items (`BGEVENT_ITEM`, the two here) | `src/world/gen2/HiddenItems.lua`, used at `src/world/gen2/World.lua:5290` | implemented | -| Ladder / cave / pit warps | `src/world/gen2/Permissions.lua:162` `isWarpCollision` (covers `$60` PIT and the `$7x` block) and `isImmediateWarp` | implemented - the (0,11) hole and all six ladders work | -| Warp carpet needing a direction press (the (9,67) entrance) | `src/world/gen2/Permissions.lua:170` `CARPET_DIR` | implemented | -| Walkability from `CollisionPermissionTable` | `src/world/gen2/Permissions.lua:11` (256-entry table), `src/world/gen2/Map.lua:59` | implemented | -| **Ledge hops** (`COLL_HOP_LEFT/RIGHT/DOWN`, five of them in this map) | - | **missing** - no `$a0..$a7` handling anywhere under `src/world/gen2/`. The tiles are LAND in the port's permission table, so a bot can walk over them freely. Net effect: the one-way ledges become two-way, and the Full Restore shelf / TM pocket exits behave differently from the cart | -| **Directional side walls** (`COLL_UP_WALL`, heavily used here) | `src/world/gen2/FieldMoves.lua:313` `BLOCKED_BY` | partial - the `$b0..$b7` table exists but is only consulted by `FieldMoves.directionBlocked` for surf/field-move refusals. There is no port equivalent of `GetMovementPermissions` (`home/map.asm:1868`), so `UP_WALL` does not restrict ordinary walking | -| Coord events gated on the map scene | `src/world/gen2/World.lua:5007` `World:tryCoordScript`, scene storage at `World.lua:740` / `World:scene` at 1161 | implemented | -| Scene scripts / `setscene` persistence across save | `src/world/gen2/World.lua:558-584`, `src/core/gen2/Save.lua` | implemented | -| Rival ambush opcodes (`moveobject`, `turnobject`, `showemote`, `appear`, `disappear`, `applymovement`, `scall`, `setscene`, `playmapmusic`, `playmusic`, `winlosstext`, `setlasttalked`, `loadtrainer`, `startbattle`, `dontrestartmapmusic`, `reloadmapafterbattle`) | `src/script/gen2/Opcodes.lua` (table), `src/script/gen2/Vm.lua` (`moveobject` 337, `showemote` 961, `loadtrainer` 806, `winlosstext` 918, `reloadmapafterbattle` 886) | implemented | -| `special FadeOutMusic` | `src/script/gen2/Specials.lua:1066` | implemented | -| `VAR_BADGES` (the gate check) | `src/world/gen2/World.lua:1240` - counts `player.badges` + `player.kantoBadges`, matching `CountSetBits` over two bytes | implemented | -| Object masking by event flag | `src/import/RomExtractorGen2.lua` object `eventFlag` field; `src/world/gen2/Npc.lua` | partial - the flag is extracted, but confirm the mask polarity (hidden when **set**) matches `CheckObjectFlag`; not verified in this pass | -| Wild encounters for this map | `src/import/RomExtractorGen2.lua:3720` (`JohtoGrassWildMons` / `KantoGrassWildMons`), `src/battle/gen2/Encounter.lua` | implemented | -| Battle music landmark split | `src/battle/gen2/BattleMusic.lua:45-56` explicitly names the Victory Road landmark boundary | implemented | -| A driver that walks Victory Road | - | **missing** - no `tests/drivers/gold_*.lua` mentions Victory Road | - -## 6. Unresolved / verify by hand - -- **Rival Magneton level.** The FAQ says "Level 34 Magneton". The asm has 35 in - RIVAL1 (13) and RIVAL1 (14) and 34 only in RIVAL1 (15), the Feraligatr set - loaded when the player picked Cyndaquil. The FAQ appears to have been written - from one playthrough. -- **Rival party order.** The FAQ lists the starter evolution fourth. The asm - order is Sneasel, Golbat, Magneton, Haunter, Kadabra, starter (starter last). -- **"You get: 2280G"** and the per-mon EXP figures were not verified; base money - for the `RIVAL1` class lives in the trainer class attribute table, which was - not opened for this pass. -- **Wild list.** The FAQ lists only Golbat, Graveler, Rhyhorn and "Donphan - (Silver only)". The asm table also carries Onix (two slots, L34 and L36) and, - in Gold, Ursaring at L33 in the slot Silver gives to Donphan. -- **Item list.** The FAQ's item list omits the hidden Max Potion at (3,29) and - does not distinguish the itemball Full Heal at (15,48) from the hidden Full - Heal at (3,65). -- **"Head up the stairway"** - there is no `COLL_STAIRCASE` in the decoded - Victory Road map. What the FAQ calls stairways and cliffs are ordinary floor - plus the `COLL_UP_WALL` strips; the only vertical transitions are the six - ladders, the pit and the five ledges. -- **"Head back up the ladder to get back to the third floor"** (after TM26). - There is no ladder inside the TM pocket. The two ways out are the `HOP_RIGHT` - at (8,28)/(8,29) into the x=10..13 corridor that reaches the (13,31) ladder, - and the `HOP_DOWN` at (2,30)/(3,30) into the (1,35) pocket. The checklist above - prefers the first; which one the FAQ author meant is not recoverable from the - text. -- **X Special tile.** The Poke Ball at (7,38) sits on a `COLL_HOP_LEFT` tile with - `COLL_WALL` on both sides and above. Ledge tiles are `LAND_TILE` in - `CollisionPermissionTable`, so the column x=7, y=38..41 is walkable from - (7,42) upward and the ball is reachable; but this should be confirmed on - hardware or in an emulator, because it is the one placement in this map that - looks like a mistake rather than a design. -- **The gate's black belts as physical blockers.** The claim that - `VICTORYROADGATE_BLACK_BELT1` at (7,5) and `BLACK_BELT2` at (12,5) block the - only east-west corridor is inferred from the decoded gate collision (row 5 is - the sole corridor and rows 4/6 are walls beside it) plus the fact that NPCs are - solid. It is not stated in any script, and it belongs to later sections - (Route 28 / Route 22) rather than this one. -- **Object flag polarity in the port** was not tested; see the Port coverage row. diff --git a/docs/gold-walkthrough/asm-walk/section-18-pok-mon-league.md b/docs/gold-walkthrough/asm-walk/section-18-pok-mon-league.md deleted file mode 100644 index 7cbe69bb..00000000 --- a/docs/gold-walkthrough/asm-walk/section-18-pok-mon-league.md +++ /dev/null @@ -1,883 +0,0 @@ -# Section 18 - Pokémon League - -Source: `../section-18-pok-mon-league.txt` -Maps covered: `MAP_ROUTE_23`, `MAP_INDIGO_PLATEAU_POKECENTER_1F`, `MAP_WILLS_ROOM`, -`MAP_KOGAS_ROOM`, `MAP_BRUNOS_ROOM`, `MAP_KARENS_ROOM`, `MAP_LANCES_ROOM`, -`MAP_HALL_OF_FAME` -Badges / key milestones in this section: no badge. `ENGINE_FLYPOINT_INDIGO_PLATEAU` -(fly point), `EVENT_BEAT_ELITE_4_WILL/KOGA/BRUNO/KAREN`, -`EVENT_BEAT_CHAMPION_LANCE`, `EVENT_BEAT_ELITE_FOUR`, `STATUSFLAGS_HALL_OF_FAME_F` -(Hall of Fame induction -> credits -> post-game respawn at `SPAWN_NEW_BARK`). - -All eight maps live in map group `INDIGO` (group 16, `constants/map_constants.asm:321`). - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_VICTORY_ROAD` | `maps/VictoryRoad.asm` | (previous section) | warp 10 at `(13, 5)` -> `ROUTE_23` warp 3 | Victory Road's north exit; the walkthrough opens already standing on the Plateau approach | -| 1 | `MAP_ROUTE_23` | `maps/Route23.asm` | warps 3/4 at `(9, 13)`/`(10, 13)` from Victory Road | warps 1/2 at `(9, 5)`/`(10, 5)` -> `INDIGO_PLATEAU_POKECENTER_1F` warps 1/2 | "Route 32 is very short, and you'll immediately be at the Pokémon League building" (the FAQ means Route **23**). `MAPCALLBACK_NEWMAP` registers the fly point here | -| 2 | `MAP_INDIGO_PLATEAU_POKECENTER_1F` | `maps/IndigoPlateauPokecenter1F.asm` | warps 1/2 at `(5, 13)`/`(6, 13)` | warp 4 at `(14, 3)` -> `WILLS_ROOM` warp 1 | Heal (nurse, left), buy (clerk, right), optional Abra teleport home, optional Mon/Wed rival ambush, then through the north door | -| 3 | `MAP_WILLS_ROOM` | `maps/WillsRoom.asm` | warp 1 at `(5, 17)` | warps 2/3 at `(4, 2)`/`(5, 2)` -> `KOGAS_ROOM` warps 1/2 | Elite Four 1 - Will | -| 4 | `MAP_KOGAS_ROOM` | `maps/KogasRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `BRUNOS_ROOM` warps 1/2 | Elite Four 2 - Koga | -| 5 | `MAP_BRUNOS_ROOM` | `maps/BrunosRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `KARENS_ROOM` warps 1/2 | Elite Four 3 - Bruno | -| 6 | `MAP_KARENS_ROOM` | `maps/KarensRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `LANCES_ROOM` warps 1/2 | Elite Four 4 - Karen | -| 7 | `MAP_LANCES_ROOM` | `maps/LancesRoom.asm` | warps 1/2 at `(4, 23)`/`(5, 23)` | scripted `warp HALL_OF_FAME, 4, 13` at the tail of `LancesRoomLanceScript` (warps 3/4 at `(4, 0)`/`(5, 0)` are the same door) | Champion Lance, then the Mary / Prof. Oak cutscene | -| 8 | `MAP_HALL_OF_FAME` | `maps/HallOfFame.asm` | scripted warp to `(4, 13)` | `halloffame` -> `Credits` -> title screen | Induction, credits, "The End" | - -Post-credits: `engine/menus/intro_menu.asm` `.SpawnAfterE4` sends the next CONTINUE -to `SPAWN_NEW_BARK`, not to the saved position. That is the start of the Kanto -half and belongs to the next section. - ---- - -## 2. Maps - -### MAP_ROUTE_23 - -- Script: `maps/Route23.asm` -- Blocks: `maps/Route23.blk` -- Header (`data/maps/maps.asm:349`): `map Route23, TILESET_KANTO, TOWN, LANDMARK_ROUTE_23, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:322` -> `map_const ROUTE_23, 10, 9` (10 x 9 blocks = 20 x 18 tiles) -- Attributes: `data/maps/attributes.asm:397` -> `map_attributes Route23, ROUTE_23, $0f`, **no connections** (despite `TOWN` environment) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 5 | `INDIGO_PLATEAU_POKECENTER_1F` | 1 | -| 2 | 10 | 5 | `INDIGO_PLATEAU_POKECENTER_1F` | 2 | -| 3 | 9 | 13 | `VICTORY_ROAD` | 10 | -| 4 | 10 | 13 | `VICTORY_ROAD` | 10 | - -**Coord events** - none (`def_coord_events` is empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 7 | `BGEVENT_READ` | `IndigoPlateauSign` (`IndigoPlateauSignText`) | - -**Object events** - none (`def_object_events` is empty). - -**Scripts of interest** - -- `Route23FlypointCallback` (`50:5888`), registered as `callback MAPCALLBACK_NEWMAP`. - Body is two opcodes: `setflag ENGINE_FLYPOINT_INDIGO_PLATEAU` / `endcallback`. - This is the whole of the walkthrough's "You'll now be able to fly to this - location directly", and it is also the gate on the Kanto half of the Fly map - (see section 5 - Blockers). -- `IndigoPlateauSign` -> `jumptext IndigoPlateauSignText`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_INDIGO_PLATEAU` | `constants/engine_flags.asm:78` | set by `Route23FlypointCallback` | after one step onto Route 23, Fly to Indigo Plateau is available; also unlocks the Kanto Fly map | - -**Items** - none. - -**Trainers** - none. - -**Wild encounters** - none. `ROUTE_23` has no entry in `data/wild/johto_grass.asm`, -`data/wild/kanto_grass.asm`, `data/wild/johto_water.asm` or -`data/wild/kanto_water.asm` (grepped; `kanto_grass.asm` jumps -`ROUTE_22` -> `ROUTE_24`). The header's `FISHGROUP_SHORE` is the header default and -there is no water to fish from. - ---- - -### MAP_INDIGO_PLATEAU_POKECENTER_1F - -- Script: `maps/IndigoPlateauPokecenter1F.asm` -- Blocks: `maps/IndigoPlateauPokecenter1F.blk` -- Header (`data/maps/maps.asm:350`): `map IndigoPlateauPokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:323` -> `map_const INDIGO_PLATEAU_POKECENTER_1F, 9, 7` (18 x 14 tiles) -- Attributes: `data/maps/attributes.asm:598`, border `$00`, no connections - -`object_const_def` order (the ids `moveobject` / `applymovement` / `appear` take): - -``` -0 INDIGOPLATEAUPOKECENTER1F_NURSE -1 INDIGOPLATEAUPOKECENTER1F_CLERK -2 INDIGOPLATEAUPOKECENTER1F_COOLTRAINER_M -3 INDIGOPLATEAUPOKECENTER1F_RIVAL -4 INDIGOPLATEAUPOKECENTER1F_GRAMPS -5 INDIGOPLATEAUPOKECENTER1F_ABRA -``` - -(the macro is `const_def 2`, so the emitted numbers start at 2) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 13 | `ROUTE_23` | 1 | -| 2 | 6 | 13 | `ROUTE_23` | 2 | -| 3 | 0 | 13 | `POKECENTER_2F` | 1 | -| 4 | 14 | 3 | `WILLS_ROOM` | 1 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` (= 0) | 16 | 4 | `PlateauRivalBattle1` (`5a:48ff`) | rival walks up from the left column and blocks you | -| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` (= 0) | 17 | 4 | `PlateauRivalBattle2` (`5a:4940`) | same fight, approached from the right column | - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `..._NURSE` | `SPRITE_NURSE` | 3 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FNurseScript` | -1 | -| `..._CLERK` | `SPRITE_CLERK` | 11 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FClerkScript` | -1 | -| `..._COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 11 | 11 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FCooltrainerMScript` | -1 | -| `..._RIVAL` | `SPRITE_RIVAL` | 16 | 9 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` | -| `..._GRAMPS` | `SPRITE_GRAMPS` (`PAL_NPC_BLUE`) | 1 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TeleportGuyScript` | `EVENT_TELEPORT_GUY` | -| `..._ABRA` | `SPRITE_JYNX` (`PAL_NPC_BROWN`) | 0 | 9 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `AbraScript` | `EVENT_TELEPORT_GUY` | - -Note the last row: the "Abra" the walkthrough names is declared with -`SPRITE_JYNX` (`constants/sprite_constants.asm:122`; `data/sprites/sprite_mons.asm` -maps that slot to `JYNX`). Its script is `AbraScript`, which does -`cry ABRA`. Verbatim from the file, not a typo of mine. - -An object's event flag hides it when the flag is **set** (`Script_disappear` -sets, `Script_appear` clears - `engine/overworld/scripting.asm:879-898`). -`EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` is set on New Game by -`engine/events/std_scripts.asm:556` (the `EVENT_INITIALIZED_EVENTS` block), so -the rival starts hidden. `EVENT_TELEPORT_GUY` is not in that block, so the old -man and his Pokémon are visible from the first visit. - -**Scripts of interest** - -- `IndigoPlateauPokecenter1FPrepareElite4Callback` (`5a:48b6`), a - `callback MAPCALLBACK_NEWMAP`. **This is the single most important script in - the section for a bot**: every time you set foot in the lobby it resets the - whole Elite Four run. - - ``` - setmapscene WILLS_ROOM, SCENE_WILLSROOM_LOCK_DOOR - setmapscene KOGAS_ROOM, SCENE_KOGASROOM_LOCK_DOOR - setmapscene BRUNOS_ROOM, SCENE_BRUNOSROOM_LOCK_DOOR - setmapscene KARENS_ROOM, SCENE_KARENSROOM_LOCK_DOOR - setmapscene LANCES_ROOM, SCENE_LANCESROOM_LOCK_DOOR - setmapscene HALL_OF_FAME, SCENE_HALLOFFAME_ENTER - clearevent EVENT_{WILLS,KOGAS,BRUNOS,KARENS,LANCES}_ROOM_ENTRANCE_CLOSED - clearevent EVENT_{WILLS,KOGAS,BRUNOS,KARENS,LANCES}_ROOM_EXIT_OPEN - clearevent EVENT_BEAT_ELITE_4_WILL / _KOGA / _BRUNO / _KAREN - clearevent EVENT_BEAT_CHAMPION_LANCE - setevent EVENT_LANCES_ROOM_OAK_AND_MARY ; hide Oak + Mary again - endcallback - ``` - - Consequences: the five `EVENT_BEAT_ELITE_4_*` flags are **not** durable - progress markers - they only survive within one uninterrupted run. The durable - "I beat the League" flag is `EVENT_BEAT_ELITE_FOUR`, set in `HallOfFameEnterScript`. -- `PlateauRivalBattle1` / `PlateauRivalBattle2` -> `PlateauRivalBattleCommon`. - Guards, in order: - 1. `checkevent EVENT_BEAT_RIVAL_IN_MT_MOON` / `iffalse` -> done (so this only - ever fires post-game, after the Mt. Moon rematch), - 2. `checkflag ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` / `iftrue` -> done (once a day), - 3. `readvar VAR_WEEKDAY` with `ifequal SUNDAY / TUESDAY / THURSDAY / FRIDAY / - SATURDAY` -> done. **Only Monday and Wednesday.** - - Then `moveobject`/`appear` the rival, `showemote EMOTE_SHOCK`, - `special FadeOutMusic`, a 5-step `applymovement`, `playmusic MUSIC_RIVAL_ENCOUNTER`, - `setevent EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL`, then a starter-dependent - `loadtrainer RIVAL2, RIVAL2_2_*` + `startbattle`. Afterwards - `PlateauRivalPostBattle` plays `MUSIC_RIVAL_AFTER`, walks him off with - `disappear`, and `setflag ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT`. -- `IndigoPlateauPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` (free heal). -- `IndigoPlateauPokecenter1FClerkScript` -> `pokemart MARTTYPE_STANDARD, MART_INDIGO_PLATEAU`. -- `TeleportGuyScript` (`5a:49e5`): `yesorno`; on yes, `playsound SFX_WARP_TO`, - `special FadeOutToWhite`, `warp NEW_BARK_TOWN, 13, 6`. The walkthrough's "Abra - will teleport you back" lands you in **New Bark Town**, specifically, not - "back to Johto" generically. -- `AbraScript`: text + `cry ABRA`, nothing else. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` | `constants/event_flags.asm:1309` | set by `std_scripts.asm:556` on New Game and by `PlateauRivalBattleCommon`; cleared by `appear` | hides the rival object | -| `EVENT_TELEPORT_GUY` | `constants/event_flags.asm:1310` | set by `HallOfFameEnterScript` | after the Hall of Fame the old man **and** his Pokémon vanish; the ride home is a pre-champion service only | -| `EVENT_BEAT_RIVAL_IN_MT_MOON` | `constants/event_flags.asm:456` | read by both `PlateauRivalBattle*` | precondition for the lobby ambush | -| `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` | `constants/engine_flags.asm:111` (in the `wDailyFlags2` block) | read/set by `PlateauRivalBattle*` | daily one-shot; cleared with the rest of the daily flags at midnight | -| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` | the map's own `def_scene_scripts` (value 0) | coord events | see the quirk note below | - -Scene quirk worth knowing: this map declares exactly one scene script, so -`SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE == 0`, which is also the default -value of `wMapScenes` for the map. `PlateauRivalPostBattle`'s -`setscene SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` therefore writes 0 over -0 and the two coord events stay armed forever. The real one-shot guards are -`ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` and the weekday check. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `ULTRA_BALL`, `MAX_REPEL`, `HYPER_POTION`, `MAX_POTION`, `FULL_RESTORE`, `REVIVE`, `FULL_HEAL` | bought | `MartIndigoPlateau` (`data/items/marts.asm:384`, symbol `05:645a`), opened by `IndigoPlateauPokecenter1FClerkScript` | n/a | - -This is where the walkthrough's "ten Hyper Potions, ten Revives, ten Full Heals" -shopping list is actually satisfiable - the mart carries all three plus Full -Restore. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL2` | `RIVAL2` (`$2a`) | `RIVAL2_2_CHIKORITA` / `RIVAL2_2_CYNDAQUIL` / `RIVAL2_2_TOTODILE` (chosen by which starter the player took: `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> Chikorita team, `EVENT_GOT_CHIKORITA_FROM_ELM` -> Cyndaquil team, else Totodile team) | `Rival2Group` entries 4/5/6 | `PlateauRivalBattleCommon` | daily, Mon/Wed only, post Mt. Moon | - -`Rival2Group` (4)-(6), all `TRAINERTYPE_MOVES`: -`45 SNEASEL`, `48 CROBAT`, `45 MAGNETON`, `46 GENGAR`, `46 ALAKAZAM`, and then -`50 MEGANIUM` / `50 TYPHLOSION` / `50 FERALIGATR` respectively. The walkthrough -does not mention this fight at all - a bot that walks the lobby on a Monday -will hit it whether or not it planned to. - -**Wild encounters** - none (indoor). - ---- - -### MAP_WILLS_ROOM - -- Script: `maps/WillsRoom.asm` -- Blocks: `maps/WillsRoom.blk` -- Header (`data/maps/maps.asm:351`): `map WillsRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` (the `TRUE` is the phone-service flag: **no phone calls** in any Elite Four room) -- Dimensions: `constants/map_constants.asm:324` -> `map_const WILLS_ROOM, 5, 9` (10 x 18 tiles) -- Attributes: `data/maps/attributes.asm:599`, border `$00`, no connections - -`object_const_def`: `WILLSROOM_WILL` (emitted id 2). - -Scene ids from the map's own `def_scene_scripts`: -`SCENE_WILLSROOM_LOCK_DOOR = 0`, `SCENE_WILLSROOM_NOOP = 1`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 17 | `INDIGO_PLATEAU_POKECENTER_1F` | 4 | -| 2 | 4 | 2 | `KOGAS_ROOM` | 1 | -| 3 | 5 | 2 | `KOGAS_ROOM` | 2 | - -**Coord events** - none. The door-lock cutscene is a **scene script**, not a -coord event: `scene_script WillsRoomLockDoorScene, SCENE_WILLSROOM_LOCK_DOOR` -fires on map load via `sdefer`. - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `WILLSROOM_WILL` | `SPRITE_WILL` (`PAL_NPC_RED`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `WillScript_Battle` | -1 | - -**Scripts of interest** - -- `WillsRoomDoorLocksBehindYouScript` (`5a:4d8c`), deferred by the scene script: - `applymovement PLAYER, WillsRoom_EnterMovement` (4 x `step UP`, so you enter at - `(5, 17)` and end at `(5, 13)`), `reanchormap $85`, `playsound SFX_STRENGTH`, - `earthquake 80`, `changeblock 4, 14, $2a` (wall), `refreshmap`, - `setscene SCENE_WILLSROOM_NOOP`, `setevent EVENT_WILLS_ROOM_ENTRANCE_CLOSED`. - `changeblock` operands are in the same tile space as warps but are halved - internally (`Script_changeblock` adds 4 then `GetBlockLocation` shifts right, - `home/map.asm:2099`), so `4, 14` is the block covering tiles `(4..5, 14..15)` - - the corridor square immediately below where the walk-in leaves you. That is - what makes the walkthrough's "You can't go back to the Pokémon Center to heal - between battles" literally true. -- `WillsRoomDoorsCallback`, `callback MAPCALLBACK_TILES`: re-applies - `changeblock 4, 14, $2a` while `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` is set and - `changeblock 4, 2, $16` (open door) while `EVENT_WILLS_ROOM_EXIT_OPEN` is set, - so the room's geometry survives a battle reload. -- `WillScript_Battle` (`5a:4da4`): `faceplayer`, `opentext`, - `checkevent EVENT_BEAT_ELITE_4_WILL` / `iftrue WillScript_AfterBattle`, - `writetext WillScript_WillBeforeText`, `winlosstext WillScript_WillBeatenText, 0`, - `loadtrainer WILL, WILL1`, `startbattle`, `reloadmapafterbattle`, - `setevent EVENT_BEAT_ELITE_4_WILL`, defeat text, `playsound SFX_ENTER_DOOR`, - `changeblock 4, 2, $16` (open the north door), `refreshmap`, - `setevent EVENT_WILLS_ROOM_EXIT_OPEN`. - - Note there is **no `winlosstext` loss branch** (the second operand is `0`) and - no `BATTLETYPE_CANLOSE`: losing to any Elite Four member is an ordinary - blackout, which is why the FAQ says "If you lose, you have to start all over". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:440` | set by `WillsRoomDoorLocksBehindYouScript`, read by `WillsRoomDoorsCallback`, cleared by the lobby callback | south door sealed | -| `EVENT_WILLS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:441` | set by `WillScript_Battle`, read by `WillsRoomDoorsCallback`, cleared by the lobby callback | north door open -> `(4, 2)` / `(5, 2)` are walkable warps | -| `EVENT_BEAT_ELITE_4_WILL` | `constants/event_flags.asm:980` | set by `WillScript_Battle`, cleared by the lobby callback | run-local "Will is done" | -| `SCENE_WILLSROOM_LOCK_DOOR` / `SCENE_WILLSROOM_NOOP` | this map's `def_scene_scripts` (0 / 1) | `setmapscene` from the lobby; `setscene` in the lock script | 0 = play the seal-in cutscene on entry | - -**Items** - none. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `WILL1` | `WILL` (`$b`) | 1 | `WillGroup` (`data/trainers/parties.asm:197`, symbol `0e:5bf4`) | `WillScript_Battle` | none | - -`WillGroup`, `TRAINERTYPE_MOVES`, in **send-out order**: - -| # | lvl | species | moves | -|---|---|---|---| -| 1 | 40 | `XATU` | Quick Attack, Future Sight, Confuse Ray, Psychic | -| 2 | 41 | `JYNX` | DoubleSlap, Lovely Kiss, Ice Punch, Psychic | -| 3 | 41 | `EXEGGUTOR` | Reflect, Leech Seed, Egg Bomb, Psychic | -| 4 | 41 | `SLOWBRO` | Curse, Amnesia, Body Slam, Psychic | -| 5 | 42 | `XATU` | Quick Attack, Future Sight, Confuse Ray, Psychic | - -Class attributes (`data/trainers/attributes.asm`, `; Will` block): -items `MAX_POTION, NO_ITEM`; base reward 25; DVs `atk 13 / def 12 / spd 13 / spc 13` -(`data/trainers/dvs.asm:15`). Money = base reward x level of the last enemy mon -(`ComputeTrainerReward`, `engine/battle/read_trainer_party.asm:300`) = **25 x 42 = 1050**. - -**Wild encounters** - none. - ---- - -### MAP_KOGAS_ROOM - -- Script: `maps/KogasRoom.asm` -- Blocks: `maps/KogasRoom.blk` -- Header (`data/maps/maps.asm:352`): `map KogasRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:325` -> `map_const KOGAS_ROOM, 5, 9` -- Attributes: `data/maps/attributes.asm:600`, border `$00` - -`object_const_def`: `KOGASROOM_KOGA`. -Scenes: `SCENE_KOGASROOM_LOCK_DOOR = 0`, `SCENE_KOGASROOM_NOOP = 1`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `WILLS_ROOM` | 2 | -| 2 | 5 | 17 | `WILLS_ROOM` | 3 | -| 3 | 4 | 2 | `BRUNOS_ROOM` | 1 | -| 4 | 5 | 2 | `BRUNOS_ROOM` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `KOGASROOM_KOGA` | `SPRITE_KOGA` (`PAL_NPC_BLUE`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `KogaScript_Battle` | -1 | - -**Scripts of interest** - -Structurally identical to Will's room: `KogasRoomDoorLocksBehindYouScript` -(4 x `step UP`, `changeblock 4, 14, $2a`, `setevent EVENT_KOGAS_ROOM_ENTRANCE_CLOSED`, -`setscene SCENE_KOGASROOM_NOOP`), `KogasRoomDoorsCallback` (`MAPCALLBACK_TILES`), -and `KogaScript_Battle` (`5a:5006`) -> `loadtrainer KOGA, KOGA1` / -`setevent EVENT_BEAT_ELITE_4_KOGA` / `changeblock 4, 2, $16` / -`setevent EVENT_KOGAS_ROOM_EXIT_OPEN`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_KOGAS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:442` | lock script / tiles callback | south door sealed | -| `EVENT_KOGAS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:443` | `KogaScript_Battle` / tiles callback | north door open | -| `EVENT_BEAT_ELITE_4_KOGA` | `constants/event_flags.asm:981` | `KogaScript_Battle` | run-local | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `KOGA1` | `KOGA` (`$f`) | 1 | `KogaGroup` (`data/trainers/parties.asm:249`, symbol `0e:5c89`) | `KogaScript_Battle` | none | - -| # | lvl | species | moves | -|---|---|---|---| -| 1 | 40 | `ARIADOS` | Double Team, Spider Web, Baton Pass, Giga Drain | -| 2 | 41 | `VENOMOTH` | Supersonic, Gust, Psychic, Toxic | -| 3 | 43 | `FORRETRESS` | Protect, Swift, Explosion, Spikes | -| 4 | 42 | `MUK` | Minimize, Acid Armor, Sludge Bomb, Toxic | -| 5 | 44 | `CROBAT` | Double Team, Quick Attack, Wing Attack, Toxic | - -Class attributes (`; Koga`): items `FULL_HEAL, FULL_RESTORE` (the walkthrough's -"three Full Restores" is HGSS; in GS the AI holds **one** Full Heal and **one** -Full Restore per class-attribute slot); base reward 25; DVs `13/12/13/13` -(`data/trainers/dvs.asm:19`). Money = 25 x 44 = **1100**. - -**Wild encounters** - none. - ---- - -### MAP_BRUNOS_ROOM - -- Script: `maps/BrunosRoom.asm` -- Blocks: `maps/BrunosRoom.blk` -- Header (`data/maps/maps.asm:353`): `map BrunosRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:326` -> `map_const BRUNOS_ROOM, 5, 9` -- Attributes: `data/maps/attributes.asm:601`, border `$00` - -`object_const_def`: `BRUNOSROOM_BRUNO`. -Scenes: `SCENE_BRUNOSROOM_LOCK_DOOR = 0`, `SCENE_BRUNOSROOM_NOOP = 1`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `KOGAS_ROOM` | 3 | -| 2 | 5 | 17 | `KOGAS_ROOM` | 4 | -| 3 | 4 | 2 | `KARENS_ROOM` | 1 | -| 4 | 5 | 2 | `KARENS_ROOM` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BRUNOSROOM_BRUNO` | `SPRITE_BRUNO` (`PAL_NPC_BROWN`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BrunoScript_Battle` | -1 | - -**Scripts of interest** - `BrunosRoomDoorLocksBehindYouScript`, -`BrunosRoomDoorsCallback`, `BrunoScript_Battle` (`5a:5271`); same shape as Will's -room, with `loadtrainer BRUNO, BRUNO1` and -`setevent EVENT_BEAT_ELITE_4_BRUNO` / `EVENT_BRUNOS_ROOM_EXIT_OPEN`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BRUNOS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:444` | lock script / tiles callback | south door sealed | -| `EVENT_BRUNOS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:445` | `BrunoScript_Battle` / tiles callback | north door open | -| `EVENT_BEAT_ELITE_4_BRUNO` | `constants/event_flags.asm:982` | `BrunoScript_Battle` | run-local | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `BRUNO1` | `BRUNO` (`$d`) | 1 | `BrunoGroup` (`data/trainers/parties.asm:229`, symbol `0e:5c3d`) | `BrunoScript_Battle` | none | - -| # | lvl | species | moves | -|---|---|---|---| -| 1 | 42 | `HITMONTOP` | Pursuit, Quick Attack, Dig, Detect | -| 2 | 42 | `HITMONLEE` | Swagger, Double Kick, Hi Jump Kick, Foresight | -| 3 | 42 | `HITMONCHAN` | ThunderPunch, Ice Punch, Fire Punch, Mach Punch | -| 4 | 43 | `ONIX` | Bind, Earthquake, Sandstorm, Rock Slide | -| 5 | 46 | `MACHAMP` | Rock Slide, Foresight, Vital Throw, Cross Chop | - -Class attributes (`; Bruno`): items `MAX_POTION, NO_ITEM`; base reward 25; DVs -`13/12/13/13` (`data/trainers/dvs.asm:17`). Money = 25 x 46 = **1150**. - -**Wild encounters** - none. - ---- - -### MAP_KARENS_ROOM - -- Script: `maps/KarensRoom.asm` -- Blocks: `maps/KarensRoom.blk` -- Header (`data/maps/maps.asm:354`): `map KarensRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:327` -> `map_const KARENS_ROOM, 5, 9` -- Attributes: `data/maps/attributes.asm:602`, border `$00` - -`object_const_def`: `KARENSROOM_KAREN`. -Scenes: `SCENE_KARENSROOM_LOCK_DOOR = 0`, `SCENE_KARENSROOM_NOOP = 1`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `BRUNOS_ROOM` | 3 | -| 2 | 5 | 17 | `BRUNOS_ROOM` | 4 | -| 3 | 4 | 2 | `LANCES_ROOM` | 1 | -| 4 | 5 | 2 | `LANCES_ROOM` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `KARENSROOM_KAREN` | `SPRITE_KAREN` (`PAL_NPC_RED`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `KarenScript_Battle` | -1 | - -**Scripts of interest** - `KarensRoomDoorLocksBehindYouScript`, -`KarensRoomDoorsCallback`, `KarenScript_Battle` (`5a:549a`) with -`loadtrainer KAREN, KAREN1`, `setevent EVENT_BEAT_ELITE_4_KAREN`, -`changeblock 4, 2, $16`, `setevent EVENT_KARENS_ROOM_EXIT_OPEN`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_KARENS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:446` | lock script / tiles callback | south door sealed | -| `EVENT_KARENS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:447` | `KarenScript_Battle` / tiles callback | north door open | -| `EVENT_BEAT_ELITE_4_KAREN` | `constants/event_flags.asm:983` | `KarenScript_Battle` | run-local | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `KAREN1` | `KAREN` (`$e`) | 1 | `KarenGroup` (`data/trainers/parties.asm:239`, symbol `0e:5c63`) | `KarenScript_Battle` | none | - -| # | lvl | species | moves | -|---|---|---|---| -| 1 | 42 | `UMBREON` | Sand-Attack, Confuse Ray, Faint Attack, Mean Look | -| 2 | 42 | `VILEPLUME` | Stun Spore, Acid, Moonlight, Petal Dance | -| 3 | 45 | `GENGAR` | Lick, Spite, Curse, Destiny Bond | -| 4 | 44 | `MURKROW` | Quick Attack, Whirlwind, Pursuit, Faint Attack | -| 5 | 47 | `HOUNDOOM` | Roar, Pursuit, Flamethrower, Crunch | - -Class attributes (`; Karen`): items `FULL_HEAL, MAX_POTION` - **no Full Restore**, -contra the walkthrough; base reward 25; DVs `atk 7 / def 15 / spd 13 / spc 15` -(`data/trainers/dvs.asm:18`, the only Elite Four member with a different DV row). -Money = 25 x 47 = **1175**. - -**Wild encounters** - none. - ---- - -### MAP_LANCES_ROOM - -- Script: `maps/LancesRoom.asm` -- Blocks: `maps/LancesRoom.blk` -- Header (`data/maps/maps.asm:355`): `map LancesRoom, TILESET_CHAMPIONS_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:328` -> `map_const LANCES_ROOM, 5, 12` (10 x 24 tiles - the tallest room in the run) -- Attributes: `data/maps/attributes.asm:603`, border `$00` - -`object_const_def`: `LANCESROOM_LANCE`, `LANCESROOM_MARY`, `LANCESROOM_OAK`. -Scenes: `SCENE_LANCESROOM_LOCK_DOOR = 0`, `SCENE_LANCESROOM_APPROACH_LANCE = 1`. -Note this map's second scene is **not** a noop-by-name: it is the state in which -the two coord events are live. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 23 | `KARENS_ROOM` | 3 | -| 2 | 5 | 23 | `KARENS_ROOM` | 4 | -| 3 | 4 | 0 | `HALL_OF_FAME` | 1 | -| 4 | 5 | 0 | `HALL_OF_FAME` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_LANCESROOM_APPROACH_LANCE` (= 1) | 4 | 5 | `Script_ApproachLanceFromLeft` (`5a:5716`) | `FadeOutMusic`, walk `UP UP UP` + face right, fall into `LancesRoomLanceScript` | -| `SCENE_LANCESROOM_APPROACH_LANCE` (= 1) | 5 | 5 | `Script_ApproachLanceFromRight` (`5a:5720`) | `FadeOutMusic`, walk `UP LEFT UP UP` + face right, then the same | - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LANCESROOM_LANCE` | `SPRITE_LANCE` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `LancesRoomLanceScript` | -1 | -| `LANCESROOM_MARY` | `SPRITE_TEACHER` (`PAL_NPC_GREEN`) | 4 | 7 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_LANCES_ROOM_OAK_AND_MARY` | -| `LANCESROOM_OAK` | `SPRITE_OAK` | 4 | 7 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_LANCES_ROOM_OAK_AND_MARY` | - -Mary and Oak share the same spawn tile `(4, 7)` and the same hide flag; both are -`appear`ed one at a time during the cutscene. - -**Scripts of interest** - -- `LancesRoomDoorLocksBehindYouScript` (`5a:56ff`): 4 x `step UP` from - `(4|5, 23)` to y = 19, `reanchormap $85`, `SFX_STRENGTH`, `earthquake 80`, - `changeblock 4, 22, $34` (wall - the champion's-room tileset uses a different - wall block id than the Elite Four rooms' `$2a`), `refreshmap`, - `setscene SCENE_LANCESROOM_APPROACH_LANCE`, - `setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED`. -- `LancesRoomLanceScript` (`5a:5727`), the section's climax: - `turnobject LANCESROOM_LANCE, LEFT`, intro text, - `winlosstext LanceBattleWinText, 0`, `setlasttalked LANCESROOM_LANCE`, - `loadtrainer CHAMPION, LANCE`, `startbattle`, `dontrestartmapmusic`, - `reloadmapafterbattle`, `setevent EVENT_BEAT_CHAMPION_LANCE`, after-battle - text, `SFX_ENTER_DOOR`, `changeblock 4, 0, $0b` (open the north door), - `setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED` (again - the script re-sets the - entrance flag here, it does not clear it), then - `musicfadeout MUSIC_BEAUTY_ENCOUNTER, 16` and the cutscene: - `appear LANCESROOM_MARY` -> `LancesRoomMovementData_MaryRushesIn` -> - `appear LANCESROOM_OAK` -> `LancesRoomMovementData_OakWalksIn` -> - `follow`/`stopfollow` pairs -> Mary's interview attempt -> Lance leads the - player north (`follow LANCESROOM_LANCE, PLAYER`, - `LancesRoomMovementData_LanceLeadsPlayerToHallOfFame`), `disappear` both, then - `special FadeOutToWhite` and `warp HALL_OF_FAME, 4, 13`. - - This is the only exit that matters: warps 3/4 exist but the script drives the - transition itself. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_LANCES_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:448` | lock script + `LancesRoomLanceScript`; read by `LancesRoomDoorsCallback` | south door sealed | -| `EVENT_LANCES_ROOM_EXIT_OPEN` | `constants/event_flags.asm:449` | read by `LancesRoomDoorsCallback`; cleared by the lobby callback | **never set by this map** - the north door is opened by a direct `changeblock 4, 0, $0b` in the battle script, and the flag only matters if you re-enter the map | -| `EVENT_BEAT_CHAMPION_LANCE` | `constants/event_flags.asm:984` | set by `LancesRoomLanceScript`, cleared by the lobby callback | run-local | -| `EVENT_LANCES_ROOM_OAK_AND_MARY` | `constants/event_flags.asm:1281` | set on New Game (`std_scripts.asm:523`) and by the lobby callback; cleared by `appear` during the cutscene | hides Mary and Oak until the champion falls | -| `SCENE_LANCESROOM_APPROACH_LANCE` | this map's `def_scene_scripts` (= 1) | `setscene` at the end of the lock script | arms the two coord events at y = 5 | - -**Items** - none. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `LANCE` | `CHAMPION` (`$10`) | 1 | `ChampionGroup` (`data/trainers/parties.asm:259`, symbol `0e:5cae`) | `LancesRoomLanceScript` | none | - -| # | lvl | species | moves | -|---|---|---|---| -| 1 | 44 | `GYARADOS` | Flail, Rain Dance, Surf, Hyper Beam | -| 2 | 47 | `DRAGONITE` | Thunder Wave, Twister, Thunder, Hyper Beam | -| 3 | 47 | `DRAGONITE` | Thunder Wave, Twister, Blizzard, Hyper Beam | -| 4 | 46 | `AERODACTYL` | Wing Attack, AncientPower, Rock Slide, Hyper Beam | -| 5 | 46 | `CHARIZARD` | Flamethrower, Wing Attack, Slash, Hyper Beam | -| 6 | 50 | `DRAGONITE` | Fire Blast, Safeguard, Outrage, Hyper Beam | - -Class attributes (`; Champion`): items `FULL_HEAL, FULL_RESTORE`; base reward 25; -DVs `13/12/13/13` (`data/trainers/dvs.asm:20`). Money = 25 x 50 = **1250**. -`data/trainers/leaders.asm:20` lists `CHAMPION`, but the file's own comment notes -`CHAMPION` and `RED` are unused for the leader battle-music check. - -**Wild encounters** - none. - ---- - -### MAP_HALL_OF_FAME - -- Script: `maps/HallOfFame.asm` -- Blocks: `maps/HallOfFame.blk` -- Header (`data/maps/maps.asm:356`): `map HallOfFame, TILESET_ICE_PATH, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_NEW_BARK_TOWN, TRUE, PALETTE_DAY, FISHGROUP_SHORE` (yes: `TILESET_ICE_PATH` and the New Bark Town theme) -- Dimensions: `constants/map_constants.asm:329` -> `map_const HALL_OF_FAME, 5, 7` (10 x 14 tiles) -- Attributes: `data/maps/attributes.asm:604`, border `$00` - -`object_const_def`: `HALLOFFAME_LANCE`. -Scenes: `SCENE_HALLOFFAME_ENTER = 0`, `SCENE_HALLOFFAME_NOOP = 1`. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 13 | `LANCES_ROOM` | 3 | -| 2 | 5 | 13 | `LANCES_ROOM` | 4 | - -**Coord events** - none (the induction is a scene script). **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `HALLOFFAME_LANCE` | `SPRITE_LANCE` | 4 | 12 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | -1 | - -**Scripts of interest** - -- `HallOfFameEnterScript` (`5a:5cff`), deferred from - `scene_script HallOfFameEnterScene, SCENE_HALLOFFAME_ENTER`. In order: - `follow HALLOFFAME_LANCE, PLAYER`, - `applymovement HALLOFFAME_LANCE, HallOfFame_WalkUpWithLance` (8 x `step UP`, - 1 x `step RIGHT`, `turn_head LEFT`), `stopfollow`, Lance's speech, - `applymovement PLAYER, HallOfFame_SlowlyApproachMachine` (one `slow_step UP`), - `setscene SCENE_HALLOFFAME_NOOP`, then the state writes: - - ``` - setval HEALMACHINE_HALL_OF_FAME ; constants/script_constants.asm:295 - special HealMachineAnim - setevent EVENT_BEAT_ELITE_FOUR ; constants/event_flags.asm:77 - setevent EVENT_TELEPORT_GUY ; removes the Abra ride home - setevent EVENT_RIVAL_SPROUT_TOWER - clearevent EVENT_RED_IN_MT_SILVER ; Red becomes available later - setevent EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME - clearevent EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME - setmapscene SPROUT_TOWER_3F, SCENE_SPROUTTOWER3F_NOOP - special HealParty - checkevent EVENT_GOT_SS_TICKET_FROM_ELM / iftrue .SkipPhoneCall - specialphonecall SPECIALCALL_SSTICKET ; constants/phone_constants.asm:49 - halloffame - ``` - - So the S.S. Ticket call from Elm is queued **here**, and the Kanto half of the - game is switched on by this one script. -- `halloffame` ($9f) -> `Script_halloffame` (`engine/overworld/scripting.asm:2207`) - -> `HallOfFame` (`21:640a`, `engine/events/halloffame.asm:3`): - fade out music, `wSpawnAfterChampion = SPAWN_LANCE`, - `set STATUSFLAGS_HALL_OF_FAME_F`, bump `wHallOfFameCount` (capped at - `HOF_MASTER_COUNT = 200`), `farcall SaveGameData`, `GetHallOfFameParty`, - `AddHallOfFameEntry`, `AnimateHallOfFame`, then `jp Credits` with the - **pre-set** copy of `wStatusFlags` (which is why a first-time champion cannot - hold B to skip the roll). -- After the credits, `ReturnFromCredits` (`engine/overworld/scripting.asm:2216`) - ends the script and returns `MAPSTATUS_DONE`; the game goes to the title - screen. On the next CONTINUE, `engine/menus/intro_menu.asm:296` `.SpawnAfterE4` - sets `wDefaultSpawnpoint = SPAWN_NEW_BARK` and enters via `MAPSETUP_WARP` - - the walkthrough's "the game isn't actually over". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_ELITE_FOUR` | `constants/event_flags.asm:77` | set by `HallOfFameEnterScript` | the durable "League cleared" flag; the five `EVENT_BEAT_ELITE_4_*` flags are wiped by the lobby callback and must not be used for this | -| `EVENT_TELEPORT_GUY` | `constants/event_flags.asm:1310` | set here | Abra ride home disappears | -| `EVENT_RED_IN_MT_SILVER` | `constants/event_flags.asm:1284` | set on New Game (`std_scripts.asm:526`), **cleared** here | Red can now exist at Mt. Silver once the rest of his gate is met | -| `EVENT_RIVAL_SPROUT_TOWER` / `SCENE_SPROUTTOWER3F_NOOP` | `constants/event_flags.asm:1126` / Sprout Tower 3F's scenes | set here | post-game Sprout Tower state | -| `EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` / `_AFTER_HALL_OF_FAME` | `constants/event_flags.asm:1241-1242` | flipped here | Olivine Port repopulates for the Fast Ship | -| `EVENT_GOT_SS_TICKET_FROM_ELM` | `constants/event_flags.asm:45` | read here | if unset, `specialphonecall SPECIALCALL_SSTICKET` queues Elm's call (`engine/phone/scripts/elm.asm:69`) | -| `STATUSFLAGS_HALL_OF_FAME_F` | `constants/ram_constants.asm` | set by `HallOfFame::` | unlocks the Kanto Pokégear map, gates credit skipping | -| `SCENE_HALLOFFAME_ENTER` / `_NOOP` | this map's `def_scene_scripts` (0 / 1) | `setmapscene` from the lobby callback; `setscene` mid-script | 0 = play the induction | - -**Items** - none. **Trainers** - none. **Wild encounters** - none. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Entry to Victory Road (and therefore to this whole section) | `maps/VictoryRoadGate.asm:_VictoryRoadGateBadgeCheckScript` - `readvar VAR_BADGES` / `ifgreater NUM_JOHTO_BADGES - 1, .AllEightBadges` | all 8 Johto badges | `.AllEightBadges` does `setscene SCENE_VICTORYROADGATE_NOOP`; otherwise you get `applymovement PLAYER, VictoryRoadGateStepDownMovement` and are pushed back. **This gate belongs to the previous section**, listed here only because it is the precondition for everything below | -| Fly to Indigo Plateau | `maps/Route23.asm:Route23FlypointCallback` | one `MAPCALLBACK_NEWMAP` on Route 23 | `setflag ENGINE_FLYPOINT_INDIGO_PLATEAU` | -| No retreat once inside an Elite Four room | `maps/WillsRoom.asm:WillsRoomDoorLocksBehindYouScript` (and the Koga/Bruno/Karen/Lance twins) - `changeblock 4, 14, $2a` (Lance: `changeblock 4, 22, $34`) plus `EVENT_*_ROOM_ENTRANCE_CLOSED` re-applied by each map's `MAPCALLBACK_TILES` | none - it is unconditional on entry | never, within a run. The lobby's `MAPCALLBACK_NEWMAP` clears the flags, but you can only reach the lobby by finishing or blacking out | -| Each room's north door | `WillScript_Battle` / `KogaScript_Battle` / `BrunoScript_Battle` / `KarenScript_Battle` - `changeblock 4, 2, $16` + `setevent EVENT_*_ROOM_EXIT_OPEN`, replayed by the map's `MAPCALLBACK_TILES` | beat that member | door block becomes `$16` (open) and warps at `(4, 2)`/`(5, 2)` become reachable | -| Lance's north door | `maps/LancesRoom.asm:LancesRoomLanceScript` - `changeblock 4, 0, $0b` | beat Lance | opened, but the script warps you through immediately anyway | -| Approaching Lance at all | `def_coord_events` at `(4, 5)` / `(5, 5)` gated on `SCENE_LANCESROOM_APPROACH_LANCE` | that scene is only set at the end of `LancesRoomDoorLocksBehindYouScript` | walking in through warp 1/2 always sets it; a bot teleported straight to y < 5 would skip the fight | -| Lobby rival ambush | `maps/IndigoPlateauPokecenter1F.asm:PlateauRivalBattle1/2` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` clear, `VAR_WEEKDAY` in {MONDAY, WEDNESDAY} | not a progress gate - a hazard. Avoid tiles `(16, 4)` / `(17, 4)` if the run cannot afford a level-45-50 six-mon fight | -| No HM field move is required anywhere in this section | grep: no `Route23`/Elite Four map references any `engine/overworld/` field-move script; the walkthrough's "you do NOT need to have every HM" is correct | - | - | - ---- - -## 4. Bot checklist - -Coordinates are the raw asm map coordinates (tile grid, origin top-left, before -the +4 border offset the object/warp readers apply). - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `VICTORY_ROAD` | warp at `(13, 5)` | walk onto warp | 8 Johto badges (already spent at `VictoryRoadGate`) | on `ROUTE_23` at warp 3 `(9, 13)` | -| 2 | `ROUTE_23` | any tile (map load) | step | - | `ENGINE_FLYPOINT_INDIGO_PLATEAU` set | -| 3 | `ROUTE_23` | `(9, 5)` or `(10, 5)` | walk onto warp | - | in `INDIGO_PLATEAU_POKECENTER_1F` at `(5, 13)`/`(6, 13)`; the `MAPCALLBACK_NEWMAP` resets all Elite Four scenes/flags | -| 4 | `INDIGO_PLATEAU_POKECENTER_1F` | `INDIGOPLATEAUPOKECENTER1F_NURSE` at `(3, 7)` | talk (A, facing up from `(3, 8)`) | - | party healed (`jumpstd PokecenterNurseScript`) | -| 5 | `INDIGO_PLATEAU_POKECENTER_1F` | `INDIGOPLATEAUPOKECENTER1F_CLERK` at `(11, 7)` | talk, buy | money | stock Hyper Potion / Full Heal / Revive / Full Restore from `MartIndigoPlateau` | -| 6 | `INDIGO_PLATEAU_POKECENTER_1F` | avoid `(16, 4)` and `(17, 4)` | pathing constraint | only matters if `EVENT_BEAT_RIVAL_IN_MT_MOON` is set and today is Mon/Wed | skipping avoids `RIVAL2_2_*` | -| 7 | `INDIGO_PLATEAU_POKECENTER_1F` | warp at `(14, 3)` | walk onto warp | - | in `WILLS_ROOM` at `(5, 17)`; scene 0 fires `WillsRoomDoorLocksBehindYouScript` | -| 8 | `WILLS_ROOM` | (automatic) | wait out `applymovement` | - | player at `(5, 13)`, `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` set, scene = 1 | -| 9 | `WILLS_ROOM` | `WILLSROOM_WILL` at `(5, 7)` | walk to `(5, 8)`, face up, A | - | win -> `EVENT_BEAT_ELITE_4_WILL`, `EVENT_WILLS_ROOM_EXIT_OPEN` | -| 10 | `WILLS_ROOM` | `(4, 2)` or `(5, 2)` | walk onto warp | `EVENT_WILLS_ROOM_EXIT_OPEN` | in `KOGAS_ROOM` at `(4, 17)`/`(5, 17)` | -| 11 | `KOGAS_ROOM` | `KOGASROOM_KOGA` at `(5, 7)` | walk to `(5, 8)`, face up, A | door-lock cutscene done (player at `(4|5, 13)`) | `EVENT_BEAT_ELITE_4_KOGA`, `EVENT_KOGAS_ROOM_EXIT_OPEN` | -| 12 | `KOGAS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `BRUNOS_ROOM` at `(4, 17)`/`(5, 17)` | -| 13 | `BRUNOS_ROOM` | `BRUNOSROOM_BRUNO` at `(5, 7)` | talk | cutscene done | `EVENT_BEAT_ELITE_4_BRUNO`, `EVENT_BRUNOS_ROOM_EXIT_OPEN` | -| 14 | `BRUNOS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `KARENS_ROOM` at `(4, 17)`/`(5, 17)` | -| 15 | `KARENS_ROOM` | `KARENSROOM_KAREN` at `(5, 7)` | talk | cutscene done | `EVENT_BEAT_ELITE_4_KAREN`, `EVENT_KARENS_ROOM_EXIT_OPEN` | -| 16 | `KARENS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `LANCES_ROOM` at `(4, 23)`/`(5, 23)` | -| 17 | `LANCES_ROOM` | (automatic) | wait out `applymovement` | - | player at `(4|5, 19)`, `EVENT_LANCES_ROOM_ENTRANCE_CLOSED` set, scene = `SCENE_LANCESROOM_APPROACH_LANCE` | -| 18 | `LANCES_ROOM` | walk to `(4, 5)` (or `(5, 5)`) | walk onto coord event | scene = 1 | `Script_ApproachLanceFromLeft/Right` -> Champion battle vs `CHAMPION`/`LANCE` | -| 19 | `LANCES_ROOM` | (automatic) | hold A through the Mary / Oak cutscene | won | `EVENT_BEAT_CHAMPION_LANCE`, then `warp HALL_OF_FAME, 4, 13` | -| 20 | `HALL_OF_FAME` | (automatic) | hold A through Lance's speech | scene = `SCENE_HALLOFFAME_ENTER` | `EVENT_BEAT_ELITE_FOUR`, `EVENT_TELEPORT_GUY`, S.S. Ticket call queued if needed, `halloffame` runs | -| 21 | (screens) | Hall of Fame roster -> credits -> "The End" | wait; B skips **only** if `STATUSFLAGS_HALL_OF_FAME_F` was already set before this induction | - | back to title; save has been written by `SaveGameData` inside the ceremony | -| 22 | (title) | CONTINUE | select | `wSpawnAfterChampion == SPAWN_LANCE` | game resumes at `SPAWN_NEW_BARK`, not at Indigo Plateau | - -Bot notes: - -- Do **not** re-enter `INDIGO_PLATEAU_POKECENTER_1F` mid-run to heal. You cannot - (the doors are sealed), and if some other path got you there, its - `MAPCALLBACK_NEWMAP` wipes `EVENT_BEAT_ELITE_4_*` and re-arms all five - door-lock scenes, restarting the gauntlet from Will. -- Every fight uses `winlosstext <win>, 0` - no loss branch, no - `BATTLETYPE_CANLOSE` - so a loss is a full blackout to the last Pokémon Center. -- After each `startbattle` the scripts run `reloadmapafterbattle`, which re-runs - the map's `MAPCALLBACK_TILES` and therefore restores both the sealed south door - and (once its flag is set) the opened north door. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map/warp/coord/object tables for all 8 maps | extracted generically by `src/import/RomExtractorGen2.lua` into the Gold cache and read by `src/world/gen2/World.lua` + `src/world/gen2/Map.lua` | implemented (data-driven; nothing map-specific is hand-written) | -| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES` dispatch (the lobby reset, the door callbacks) | `src/world/gen2/World.lua`, asserted end-to-end by `tests/drivers/gold_map_callbacks.lua` | implemented | -| `changeblock` / `refreshmap` door swaps | `src/world/gen2/World.lua:1454` (`World:changeBlock`), `src/script/gen2/Opcodes.lua` | implemented | -| `reanchormap` | `src/script/gen2/Vm.lua:166` - grouped with `opentext`/`closetext` as a no-op | partial (the camera re-anchor the cart does after the wall drops is not reproduced; cosmetic, but the seal-in frame may differ) | -| `earthquake 80` shake | `src/script/gen2/Vm.lua:1044` -> `earthquakeFn`, `World:earthquake` (`src/world/gen2/World.lua:1537`) | implemented | -| Scene scripts / `sdefer` (all five door locks, the Hall of Fame induction) | `src/script/gen2/Vm.lua:92` - `sdefer` is executed **immediately** rather than deferred until the map settles | partial (ordering deviation, called out in the source comment) | -| `setmapscene` / `setscene` | `src/script/gen2/Opcodes.lua`, `World:mapSceneOf` (`src/world/gen2/World.lua:1179`) | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` | `src/script/gen2/Vm.lua`, `src/battle/gen2/Battle.lua`, `src/world/gen2/Trainers.lua` | implemented | -| Trainer party data (`WillGroup` … `ChampionGroup`), class DVs, class items | extracted by `src/import/RomExtractorGen2.lua` (`TrainerGroups`, ~line 3871) | implemented (data-driven) | -| AI item use (Koga/Lance Full Restore, Will/Bruno Max Potion, Karen Full Heal) | `src/battle/gen2/Ai.lua:1369-1375`, `src/battle/gen2/Battle.lua:1747` | implemented | -| Prize money `base x last level` | `src/battle/gen2/Prize.lua` (`Prize.rewardLevel`), used at `src/battle/gen2/Battle.lua:1438` | implemented | -| `pokemart MARTTYPE_STANDARD, MART_INDIGO_PLATEAU` | `src/script/gen2/Vm.lua:597`, `src/ui/gen2/MartMenu.lua`, mart tables extracted at `src/import/RomExtractorGen2.lua:2582` | implemented | -| `jumpstd PokecenterNurseScript` (lobby heal) | `src/script/gen2/Vm.lua` `jumpstd` + std-script table | implemented | -| `TeleportGuyScript` warp to New Bark Town, `AbraScript` `cry ABRA` | generic `warp`/`yesorno`/`cry` opcodes | implemented (data-driven) | -| `halloffame` opcode -> induction bookkeeping | `src/script/gen2/Opcodes.lua:171`, `src/script/gen2/Vm.lua:1382`, `src/world/gen2/World.lua:2374` (`World:hallOfFame`), `src/core/gen2/HallOfFame.lua` (`induct`, `bumpCount`, `buildParty`) | implemented | -| Hall of Fame roster screen + credits, first-time-champion skip lockout | `src/ui/gen2/HallOfFame.lua`, `src/ui/gen2/Credits.lua`; eyeball driver `tests/drivers/gold_halloffame_shots.lua` | implemented | -| `wSpawnAfterChampion` -> post-credits respawn at `SPAWN_NEW_BARK` | `src/core/gen2/HallOfFame.lua:51` (`POST_CREDITS_SPAWN`) and `:254` (`consumePostGameSpawn`) | **partial** - the logic exists and is unit-tested (`tests/gen2_halloffame_test.lua:164`), but grepping `src/` finds **no caller**: nothing on the CONTINUE path consumes it, so a port save resumes where it stood rather than at New Bark Town | -| `specialphonecall SPECIALCALL_SSTICKET` from the induction | `src/script/gen2/Vm.lua` (`specialphonecall`), `src/core/gen2/Phone.lua:394` (`SPECIALCALL_SSTICKET`, id 5) | implemented | -| `ENGINE_FLYPOINT_INDIGO_PLATEAU` -> Fly menu + Kanto map unlock | `src/world/gen2/FieldMoves.lua:368` (`SPAWN_INDIGO`, flag 63), `:414` (Kanto half gate) | implemented | -| `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` daily reset | `src/core/gen2/Apricorns.lua:88` (`DAILY_ENGINE_FLAGS`, id 92) | implemented | -| A Gold-side driver that actually walks Route 23 -> Hall of Fame | none found (`tests/drivers/gold_*.lua` has boot/walk/battle/HOF-screen drivers but no League run; `tests/drivers/bot_route.lua` and `tests/parity_lance.lua` are the **Gen 1** Kanto Elite Four) | missing | - ---- - -## 6. Unresolved / verify by hand - -1. **"Route 32 is very short, and you'll immediately be at the Pokémon League - building."** The map between Victory Road and the Plateau is `MAP_ROUTE_23` - (`constants/map_constants.asm:322`), not Route 32 (which is south of Violet - City). Walkthrough typo. -2. **The walkthrough is describing HeartGold/SoulSilver parties, not Gold.** Four - concrete divergences, all verified against `data/trainers/parties.asm`: - - Will's send-out order is Xatu, Jynx, **Exeggutor, Slowbro**, Xatu; the FAQ - lists Slowbro third and Exeggutor fifth. - - Koga's order is Ariados, **Venomoth, Forretress**, Muk, Crobat; the FAQ - swaps 2 and 3. - - Bruno's order is Hitmontop, **Hitmonlee, Hitmonchan**, Onix, Machamp; the - FAQ swaps 2 and 3. - - Karen and Lance match in order and level. -3. **Prize money.** The FAQ's 4200 / 4400 / 4600 / 4700 / 5000 G are 100 x the - last mon's level (HGSS). GS pays `base reward x level` - (`ComputeTrainerReward`, `engine/battle/read_trainer_party.asm:300`) with a - base reward of 25 for every one of these classes - (`data/trainers/attributes.asm`), i.e. **1050 / 1100 / 1150 / 1175 / 1250 G**. - The EXP figures quoted per Pokémon were not checked against - `data/pokemon/base_stats/` and should be treated as HGSS numbers too. -4. **Held items and AI items.** "Koga has three Full Restores", "Karen has one - Full Restore", "Lance has three Full Restores", "Houndoom holding a Sitrus - Berry", "One of the Dragonites may be holding a Sitrus Berry". In GS these - parties are `TRAINERTYPE_MOVES`, which carries **no held item field at all**, - and the AI's item pool is the two-byte class row: Will `MAX_POTION, NO_ITEM`; - Bruno `MAX_POTION, NO_ITEM`; Karen `FULL_HEAL, MAX_POTION` (no Full Restore); - Koga `FULL_HEAL, FULL_RESTORE`; Champion `FULL_HEAL, FULL_RESTORE`. Sitrus - Berry does not exist in Gen 2. -5. **Move names.** "Dragon Rush" (Lance's Dragonites) is a Gen 4 move; the GS - Dragonites run Thunder Wave / Twister / Thunder or Blizzard / Hyper Beam, and - the level-50 one runs Fire Blast / Safeguard / Outrage / Hyper Beam. Lugia's - "Aero Blast" and Donphan/Sudowoodo suggestions in the sample lineup are fine - as species, but none of that is asm-checkable. -6. **"Routes 46 and 47 aren't bad, either."** `ROUTE_47` does not exist in - pokegold (`constants/map_constants.asm` has `ROUTE_45` and `ROUTE_46` and then - moves on). Route 47/48 are HGSS additions. -7. **"You can go back to the Move Deleter in Blackthorn City."** There is a - `maps/MoveDeletersHouse.asm` reached from `maps/BlackthornCity.asm`; I did not - read its script, so the claim is plausible but unverified here. -8. **"If you trade a Red Scale to Mr. Pokémon, he will give you EXP Share."** - `maps/MrPokemonsHouse.asm` does `checkitem RED_SCALE` (line 53), - `verbosegiveitem EXP_SHARE` (line 72), `takeitem RED_SCALE` (line 74), so the - claim holds; the surrounding flag guards were not read (out of scope). -9. **The "Abra" sprite.** `maps/IndigoPlateauPokecenter1F.asm:324` declares - `SPRITE_JYNX` for the object whose script is `AbraScript` and whose cry is - `cry ABRA`. There is no `SPRITE_ABRA` in `constants/sprite_constants.asm`. - Whether this renders as a Jynx on hardware, or whether some `variablesprite` - elsewhere rewrites it, was not chased down - no `variablesprite` targeting - `SPRITE_JYNX` appears in `engine/events/std_scripts.asm`'s init block. -10. **`EVENT_LANCES_ROOM_EXIT_OPEN` is never set.** `LancesRoomDoorsCallback` - reads it, the lobby callback clears it, but no script in the tree sets it - (grepped `maps/`). The champion's north door is opened by a bare - `changeblock 4, 0, $0b`, which does not survive a map reload. Harmless in - practice because the script warps you out immediately, but a bot that - somehow re-enters Lance's room post-battle will find the door shut again. -11. **`setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED` appears twice**: once in - `LancesRoomDoorLocksBehindYouScript` (correct) and again immediately after - the champion's after-battle text in `LancesRoomLanceScript`. The second one - looks like it was meant to be the `EXIT_OPEN` set in item 10; recorded as - observed, not corrected. -12. **The lobby's redundant `setscene`.** `PlateauRivalPostBattle` sets the scene - to `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE`, which is 0, which is the - map's only scene and its default. Whether this was intended to disarm the - coord events (it does not) is a judgement call; the effective guards are the - engine flag and the weekday check. -13. **"Fully heal your Pokémon and save after every trainer battle."** There is - no heal source inside the sealed rooms - no nurse object, no - `special HealParty` until `HallOfFameEnterScript`. The advice is about bag - items, and the asm agrees that nothing else is available. diff --git a/docs/gold-walkthrough/asm-walk/section-19-s-s-aqua-and-vermilion-city-gym.md b/docs/gold-walkthrough/asm-walk/section-19-s-s-aqua-and-vermilion-city-gym.md deleted file mode 100644 index 591f5f4b..00000000 --- a/docs/gold-walkthrough/asm-walk/section-19-s-s-aqua-and-vermilion-city-gym.md +++ /dev/null @@ -1,1093 +0,0 @@ -# Section 19 - S.S. Aqua and Vermilion City Gym - -Source: `../section-19-s-s-aqua-and-vermilion-city-gym.txt` -Maps covered: `NEW_BARK_TOWN`, `ELMS_LAB`, `OLIVINE_PORT_PASSAGE`, `OLIVINE_PORT`, -`FAST_SHIP_1F`, `FAST_SHIP_CABINS_NNW_NNE_NE`, `FAST_SHIP_CABINS_SW_SSW_NW`, -`FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN`, `FAST_SHIP_B1F`, `VERMILION_PORT`, -`VERMILION_PORT_PASSAGE`, `VERMILION_CITY`, `POKEMON_FAN_CLUB`, `VERMILION_GYM` -Badges / key milestones in this section: S.S. TICKET from Prof. Elm, first Kanto -crossing on the Fast Ship, METAL COAT from the grandpa on the S.S. Aqua, -`ENGINE_FLYPOINT_VERMILION`, RARE CANDY from the Fan Club Chairman, -**THUNDERBADGE** (`ENGINE_THUNDERBADGE`) from Lt. Surge. - -This section begins immediately after the Hall of Fame. `maps/HallOfFame.asm` -`HallOfFameEnterScript` is what arms it: it sets `EVENT_BEAT_ELITE_FOUR`, sets -`EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` (hiding the "no entry" sailor -pair), clears `EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME` (revealing the -boarding sailor), and, if `EVENT_GOT_SS_TICKET_FROM_ELM` is still clear, queues -`specialphonecall SPECIALCALL_SSTICKET` (the Elm call the walkthrough mentions). - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | post-credits respawn | warp 1 `(6,3)` -> `ELMS_LAB` 1 | Elm phones you to come back | -| 2 | `ELMS_LAB` | `maps/ElmsLab.asm` | warp 1/2 `(4,11)`/`(5,11)` | same warps back to `NEW_BARK_TOWN` 1 | talk to `ProfElmScript` -> `ElmGiveTicketScript` -> S.S. TICKET | -| 3 | `OLIVINE_CITY` | `maps/OlivineCity.asm` | Fly (`ENGINE_FLYPOINT_OLIVINE`) | warp 10 `(19,27)` / warp 11 `(20,27)` -> `OLIVINE_PORT_PASSAGE` 1/2 | "go south from the Pokemon Center" to the port entrance | -| 4 | `OLIVINE_PORT_PASSAGE` | `maps/OlivinePortPassage.asm` | warps 1/2 `(15,0)`/`(16,0)` | warp 3 `(15,4)` -> warp 4 `(3,2)`, then warp 5 `(3,14)` -> `OLIVINE_PORT` 1 | "take the stairs down twice" | -| 5 | `OLIVINE_PORT` | `maps/OlivinePort.asm` | warp 1 `(11,7)` | coord_event `(7,15)` -> `OlivinePortWalkUpToShipScript` -> `warp FAST_SHIP_1F, 25, 1` | show the S.S. TICKET, board | -| 6 | `FAST_SHIP_1F` | `maps/FastShip1F.asm` | scripted warp to `(25,1)` | 12 warps (see below) | ship deck hub; grandpa bumps you at `(24,6)`/`(25,6)` | -| 7 | `FAST_SHIP_CABINS_NNW_NNE_NE` | `maps/FastShipCabins_NNW_NNE_NE.asm` | 1F warps 2/3/4 (`(27,8)`, `(23,8)`, `(19,8)`) | same | Hiker Noland; later the lazy Sailor Stanly | -| 8 | `FAST_SHIP_CABINS_SW_SSW_NW` | `maps/FastShipCabins_SW_SSW_NW.asm` | 1F warps 5/6/7 (`(15,8)`, `(15,15)`, `(19,15)`) | same | your cabin + healing bed; Firebreather Lyle | -| 9 | `FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN` | `maps/FastShipCabins_SE_SSE_CaptainsCabin.asm` | 1F warps 8/9/10 (`(23,15)`, `(27,15)`, `(3,13)`) | same | PoKeFan Colin + Twins; grandpa; captain's cabin + granddaughter | -| 10 | `FAST_SHIP_B1F` | `maps/FastShipB1F.asm` | 1F warps 11/12 (`(6,12)`, `(30,14)`) | same | on-duty sailor blocks; Fritz, Jeff, Debra | -| 11 | `VERMILION_PORT` | `maps/VermilionPort.asm` | `FastShip1FSailor1Script` -> `warp VERMILION_PORT, 7, 17` | warp 1 `(9,5)` -> `VERMILION_PORT_PASSAGE` 5 | ship docks in Kanto | -| 12 | `VERMILION_PORT_PASSAGE` | `maps/VermilionPortPassage.asm` | warp 5 `(3,14)` | warp 4 `(3,2)` -> warp 3 `(15,4)`, then warps 1/2 `(15,0)`/`(16,0)` -> `VERMILION_CITY` 8/9 | "down the stairs, then up the stairs and out the door" | -| 13 | `VERMILION_CITY` | `maps/VermilionCity.asm` | warps 8/9 `(19,31)`/`(20,31)` | warp 3 `(7,13)`, warp 7 `(10,19)` | first look at Kanto | -| 14 | `POKEMON_FAN_CLUB` | `maps/PokemonFanClub.asm` | warp 1/2 `(2,7)`/`(3,7)` | same | Chairman's speech -> RARE CANDY | -| 15 | `VERMILION_GYM` | `maps/VermilionGym.asm` | warps 1/2 `(4,17)`/`(5,17)` | same | Vincent, Horton, Gregory, then Lt. Surge -> THUNDERBADGE | - -Spill-over note: the walkthrough's closing line ("Congrats on your first badge in -Kanto") ends the section; Saffron City / Route 6 are handled by the next -section's agent. `VERMILION_CITY` connects `north Route6 (+5)` and -`east Route11 (0)` (`data/maps/attributes.asm:342-344`), and warp 10 `(34,7)` to -`DIGLETTS_CAVE` sits behind the sleeping Snorlax; none of those are used here. - ---- - -## 2. Maps - -### MAP_ELMS_LAB - -- Script: `maps/ElmsLab.asm` -- Blocks: `maps/ElmsLab.blk` -- Header: `data/maps/maps.asm:476` -> `map ElmsLab, TILESET_LAB, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_PROF_ELM, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:441` `map_const ELMS_LAB, 5, 6` (10 x 12 cells) -- Connections: none (indoor) - -Only the S.S. TICKET beat is in scope. Full warp/object tables for the early-game -scenes belong to section 1; the rows below are the ones this section touches. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 11 | `NEW_BARK_TOWN` | 1 | -| 2 | 5 | 11 | `NEW_BARK_TOWN` | 1 | - -**Object events** (`def_object_events`) - relevant row only - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ELMSLAB_ELM` | `SPRITE_ELM` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ProfElmScript` | `-1` | - -**Scripts of interest** - -- `ProfElmScript` (`maps/ElmsLab.asm:50`, sym `60:427b` for `ElmGiveTicketScript`). - First two opcodes after `faceplayer`/`opentext` are the ticket gate: - `checkevent EVENT_GOT_SS_TICKET_FROM_ELM` / `iftrue ElmCheckMasterBall`, then - `checkevent EVENT_BEAT_ELITE_FOUR` / `iftrue ElmGiveTicketScript`. So the ticket - is handed out on the first post-Hall-of-Fame conversation and never again. -- `ElmGiveTicketScript`: `writetext ElmGiveTicketText1` -> - `verbosegiveitem S_S_TICKET` -> `setevent EVENT_GOT_SS_TICKET_FROM_ELM` -> - `writetext ElmGiveTicketText2` -> `closetext` / `end`. - Note there is **no** `iffalse` bag-full guard on this `verbosegiveitem`, unlike - every other Elm gift in the same file - the flag is set unconditionally. -- `engine/phone/scripts/elm.asm:69` `ElmPhoneCallerScript` routes - `SPECIALCALL_SSTICKET` to `.gift`, which prints `ElmPhoneGiftText` and clears - the pending call with `specialphonecall SPECIALCALL_NONE`. The call is only the - nudge; it does not give the item. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_ELITE_FOUR` | `constants/event_flags.asm:77` | set by `maps/HallOfFame.asm:33`; read by `ProfElmScript` | precondition for the ticket | -| `EVENT_GOT_SS_TICKET_FROM_ELM` | `constants/event_flags.asm:45` | set by `ElmGiveTicketScript`; read by `ProfElmScript` and `HallOfFame.asm:41` | one-time ticket guard | -| `SPECIALCALL_SSTICKET` | `constants/phone_constants.asm:49` | queued by `HallOfFame.asm:43`, consumed in `engine/phone/scripts/elm.asm` | the phone nudge; ignorable | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `S_S_TICKET` | talk to Elm | `ElmGiveTicketScript` `verbosegiveitem S_S_TICKET` | `EVENT_GOT_SS_TICKET_FROM_ELM` | - -### MAP_OLIVINE_PORT_PASSAGE - -- Script: `maps/OlivinePortPassage.asm` -- Blocks: `maps/PortPassage.blk` (shared with `VermilionPortPassage`; there is no - `OlivinePortPassage.blk`) -- Header: `data/maps/maps.asm:340` -> `map OlivinePortPassage, TILESET_UNDERGROUND, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:314` `map_const OLIVINE_PORT_PASSAGE, 10, 9` (20 x 18 cells) -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 0 | `OLIVINE_CITY` | 10 | -| 2 | 16 | 0 | `OLIVINE_CITY` | 11 | -| 3 | 15 | 4 | `OLIVINE_PORT_PASSAGE` | 4 | -| 4 | 3 | 2 | `OLIVINE_PORT_PASSAGE` | 3 | -| 5 | 3 | 14 | `OLIVINE_PORT` | 1 | - -**Coord events** - none. - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINEPORTPASSAGE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 1 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `OlivinePortPassagePokefanMScript` | `EVENT_OLIVINE_PORT_PASSAGE_POKEFAN_M` | - -The Pokefan is hidden at new game (`InitializeEventsScript`, -`engine/events/std_scripts.asm:530` sets the flag) and revealed by -`VermilionPortLeaveShipScript` (`clearevent EVENT_OLIVINE_PORT_PASSAGE_POKEFAN_M`) -once you have made the crossing once. His text is the Monday/Friday schedule. - -### MAP_OLIVINE_PORT - -- Script: `maps/OlivinePort.asm` -- Blocks: `maps/OlivinePort.blk` -- Header: `data/maps/maps.asm:333` -> `map OlivinePort, TILESET_PORT, ROUTE, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:307` `map_const OLIVINE_PORT, 10, 18` (20 x 36 cells) -- Attributes: `data/maps/attributes.asm:586` `map_attributes OlivinePort, OLIVINE_PORT, $0a` - no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 7 | `OLIVINE_PORT_PASSAGE` | 5 | -| 2 | 7 | 23 | `FAST_SHIP_1F` | 1 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_OLIVINEPORT_ASK_ENTER_SHIP` (0) | 7 | 15 | `OlivinePortWalkUpToShipScript` (sym `5b:407d`) | boarding prompt + ticket check | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 22 | `BGEVENT_ITEM` | `OlivinePortHiddenProtein` -> `hiddenitem PROTEIN, EVENT_OLIVINE_PORT_HIDDEN_PROTEIN` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OLIVINEPORT_SAILOR1` | `SPRITE_SAILOR` | 7 | 23 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `OlivinePortSailorAtGangwayScript` | `EVENT_OLIVINE_PORT_SAILOR_AT_GANGWAY` | -| `OLIVINEPORT_SAILOR2` | `SPRITE_SAILOR` | 7 | 15 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `OlivinePortSailorBeforeHOFScript` | `EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` | -| `OLIVINEPORT_SAILOR3` | `SPRITE_SAILOR` | 6 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `OlivinePortSailorAfterHOFScript` | `EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME` | -| `OLIVINEPORT_FISHING_GURU1` | `SPRITE_FISHING_GURU` | 4 | 14 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `OlivinePortFishingGuru1Script` | `EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` | -| `OLIVINEPORT_FISHING_GURU2` | `SPRITE_FISHING_GURU` | 13 | 14 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `OlivinePortFishingGuru2Script` | `EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` | -| `OLIVINEPORT_YOUNGSTER` | `SPRITE_YOUNGSTER` | 4 | 15 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivinePortYoungsterScript` | `EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME` | -| `OLIVINEPORT_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 11 | 15 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivinePortCooltrainerFScript` | `EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME` | - -Object event flags hide when **set**. Post-Hall-of-Fame, `SAILOR2` and the two -fishing gurus are gone and `SAILOR3` / youngster / cooltrainer are present. - -**Scripts of interest** - -- `OlivinePortWalkUpToShipScript` (coord event at `(7,15)`): turns `SAILOR3` - right, bails via `.skip` if either `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` or - `_2` is set, else asks `OlivinePortAskBoardText` with `yesorno`. - If `EVENT_FAST_SHIP_FIRST_TIME` is **clear** it jumps straight past the - weekday gate (`.FirstTime`) - the maiden voyage sails any day. - Otherwise `readvar VAR_WEEKDAY`: `SUNDAY`/`SATURDAY` -> `.NextShipMonday`, - `TUESDAY`/`WEDNESDAY`/`THURSDAY` -> `.NextShipFriday`. So from Olivine the ship - sails **Monday and Friday**. - Then `checkitem S_S_TICKET`; `iffalse .NoTicket`. On success it sets - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_2`, walks the player 7 steps down - (`OlivinePortApproachFastShipFirstTimeMovement`) and `sjump`s into - `OlivinePortSailorAtGangwayScript`. -- `OlivinePortSailorAtGangwayScript`: `disappear OLIVINEPORT_SAILOR1`, one step - down, `special FadeOutToWhite`, then the passenger-roster bookkeeping. - On repeat trips only (`checkevent EVENT_FAST_SHIP_FIRST_TIME` / `iffalse - .FirstTime`) it does `clearevent EVENT_FAST_SHIP_PASSENGERS_EASTBOUND`, - `setevent EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` and clears the eight - eastbound `EVENT_BEAT_*` rematch flags. Always: - `clearevent EVENT_FAST_SHIP_DESTINATION_OLIVINE`, - `appear OLIVINEPORT_SAILOR1`, - `setmapscene FAST_SHIP_1F, SCENE_FASTSHIP1F_ENTER_SHIP`, - `warp FAST_SHIP_1F, 25, 1`. -- `OlivinePortLeaveShipScript` (scene `SCENE_OLIVINEPORT_LEAVE_SHIP`, 1): the - arrival cutscene for the return trip - one step up, `appear` the sailor, - `setscene SCENE_OLIVINEPORT_ASK_ENTER_SHIP`, - `setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`, `blackoutmod OLIVINE_CITY`. - -**Wild encounters** - -`data/wild/johto_water.asm:278` `def_water_wildmons OLIVINE_PORT`, encounter rate -`2 percent`: `20 TENTACOOL / 15 TENTACOOL / 20 TENTACRUEL`. -Fishing group `FISHGROUP_OCEAN` (`data/wild/fish.asm:13`, `.Ocean_*` at lines -42-55). No grass, no headbutt table. - -### MAP_FAST_SHIP_1F - -- Script: `maps/FastShip1F.asm` -- Blocks: `maps/FastShip1F.blk` -- Header: `data/maps/maps.asm:335` -> `map FastShip1F, TILESET_LIGHTHOUSE, INDOOR, LANDMARK_FAST_SHIP, MUSIC_SS_AQUA, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:309` `map_const FAST_SHIP_1F, 16, 9` (32 x 18 cells) -- Connections: none - -Scene ids are declared inline by `scene_script` (`macros/scripts/maps.asm:25`): -`SCENE_FASTSHIP1F_NOOP` = 0, `SCENE_FASTSHIP1F_ENTER_SHIP` = 1, -`SCENE_FASTSHIP1F_MEET_GRANDPA` = 2. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 25 | 1 | `FAST_SHIP_1F` | -1 (arrival tile only) | -| 2 | 27 | 8 | `FAST_SHIP_CABINS_NNW_NNE_NE` | 1 | -| 3 | 23 | 8 | `FAST_SHIP_CABINS_NNW_NNE_NE` | 2 | -| 4 | 19 | 8 | `FAST_SHIP_CABINS_NNW_NNE_NE` | 3 | -| 5 | 15 | 8 | `FAST_SHIP_CABINS_SW_SSW_NW` | 1 | -| 6 | 15 | 15 | `FAST_SHIP_CABINS_SW_SSW_NW` | 2 | -| 7 | 19 | 15 | `FAST_SHIP_CABINS_SW_SSW_NW` | 4 | -| 8 | 23 | 15 | `FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN` | 1 | -| 9 | 27 | 15 | `FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN` | 3 | -| 10 | 3 | 13 | `FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN` | 5 | -| 11 | 6 | 12 | `FAST_SHIP_B1F` | 1 | -| 12 | 30 | 14 | `FAST_SHIP_B1F` | 2 | - -Warp 5 `(15,8)` is "your cabin" (the one with the healing bed). Warp 10 `(3,13)` -is the staircase to the captain's cabin. - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_FASTSHIP1F_MEET_GRANDPA` (2) | 24 | 6 | `WorriedGrandpaSceneLeft` (sym `5b:495d`) | grandpa runs in and bumps you | -| `SCENE_FASTSHIP1F_MEET_GRANDPA` (2) | 25 | 6 | `WorriedGrandpaSceneRight` | `moveobject FASTSHIP1F_GENTLEMAN, 20, 6` then falls through into `WorriedGrandpaSceneLeft` | - -**BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `FASTSHIP1F_SAILOR1` | `SPRITE_SAILOR` | 25 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `FastShip1FSailor1Script` | `-1` | -| `FASTSHIP1F_SAILOR2` | `SPRITE_SAILOR` | 14 | 7 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `FastShip1FSailor2Script` | `-1` | -| `FASTSHIP1F_SAILOR3` | `SPRITE_SAILOR` | 22 | 17 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `FastShip1FSailor3Script` | `-1` | -| `FASTSHIP1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 19 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_FAST_SHIP_1F_GENTLEMAN` | - -**Scripts of interest** - -- `FastShip1FEnterShipScript` (sym `5b:48ad`, scene 1, run via `sdefer`): sailor - steps aside, player walks 2 down, sailor steps back and blocks the door, - `playsound SFX_BOAT`, `earthquake 30`, - `blackoutmod FAST_SHIP_CABINS_SW_SSW_NW` (your blackout respawn becomes your - cabin), `clearevent EVENT_FAST_SHIP_HAS_ARRIVED`. Then - `checkevent EVENT_FAST_SHIP_FIRST_TIME` / `iftrue .SkipGrandpa`: on the first - crossing it sets `SCENE_FASTSHIP1F_MEET_GRANDPA`, otherwise - `SCENE_FASTSHIP1F_NOOP`. -- `WorriedGrandpaSceneLeft`: `appear FASTSHIP1F_GENTLEMAN`, four `big_step RIGHT`, - `playsound SFX_TACKLE`, knocks the player one step right, - `writetext FastShip1FGrandpaText` ("My granddaughter is missing!"), then the - gentleman runs off, `disappear`s and the scene is reset to - `SCENE_FASTSHIP1F_NOOP`. Purely informational - it sets no quest flag. -- `FastShip1FSailor1Script` (the door guard at `(25,2)`): if - `EVENT_FAST_SHIP_HAS_ARRIVED` is clear it just says "en route", branching on - `EVENT_FAST_SHIP_DESTINATION_OLIVINE` for the destination name. Once arrived it - runs `.LetThePlayerOut` (`readvar VAR_FACING`, two step-up variants), then - `setevent EVENT_VERMILION_PORT_SAILOR_AT_GANGWAY` (note: **set** hides that - object), `setmapscene VERMILION_PORT, SCENE_VERMILIONPORT_LEAVE_SHIP` and - `warp VERMILION_PORT, 7, 17`. The Olivine-bound mirror is `._Olivine` -> - `warp OLIVINE_PORT, 7, 23`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_FAST_SHIP_FIRST_TIME` | `constants/event_flags.asm:57` | set by `VermilionPortLeaveShipScript`; read by both port scripts, `FastShip1FEnterShipScript`, `FastShip1FSailor2Script`, `SSAquaCaptain`, `FastShipBed`, `FastShipB1FSailorScript` | "the maiden crossing is finished"; gates the weekday schedule and the grandpa quest | -| `EVENT_FAST_SHIP_HAS_ARRIVED` | `constants/event_flags.asm:58` | cleared on boarding, set by `SSAquaMetalCoatAndDocking` / `FastShipBed.CanArrive`; read by `FastShip1FSailor1Script` | the door guard will only let you off once this is set | -| `EVENT_FAST_SHIP_DESTINATION_OLIVINE` | `constants/event_flags.asm:56` | cleared at Olivine, set at Vermilion | which port the ship is heading for | -| `EVENT_FAST_SHIP_1F_GENTLEMAN` | `constants/event_flags.asm:1230` | set at new game (`std_scripts.asm:497`), `appear`/`disappear` in `WorriedGrandpaSceneLeft` | grandpa sprite visibility | - -### MAP_FAST_SHIP_CABINS_NNW_NNE_NE - -- Script: `maps/FastShipCabins_NNW_NNE_NE.asm` -- Blocks: `maps/FastShipCabins_NNW_NNE_NE.blk` -- Header: `data/maps/maps.asm:336` -> `map FastShipCabins_NNW_NNE_NE, TILESET_LIGHTHOUSE, INDOOR, LANDMARK_FAST_SHIP, MUSIC_SS_AQUA, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:310` `map_const FAST_SHIP_CABINS_NNW_NNE_NE, 4, 16` (8 x 32 cells) -- Layout: three stacked sub-rooms, `y` 0-11, 12-23, 24-31. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 0 | `FAST_SHIP_1F` | 2 | -| 2 | 2 | 12 | `FAST_SHIP_1F` | 3 | -| 3 | 2 | 24 | `FAST_SHIP_1F` | 4 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 6 | 13 | `BGEVENT_READ` | `FastShipCabins_NNW_NNE_NETrashcan` (`jumpstd TrashCanScript`) | -| 7 | 19 | `BGEVENT_READ` | `FastShipCabins_NNW_NNE_NETrashcan` | -| 7 | 31 | `BGEVENT_READ` | `FastShipCabins_NNW_NNE_NETrashcan` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `..._COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 4 | 3 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerCooltrainermSean` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `..._COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 1 | 5 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerCooltrainerfCarol` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `..._SUPER_NERD` | `SPRITE_SUPER_NERD` | 1 | 5 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPokemaniacEthan` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `..._POKEFAN_M` | `SPRITE_POKEFAN_M` | 4 | 17 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerHikerNoland` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `..._SAILOR` | `SPRITE_SAILOR` | 4 | 26 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | 0 | `FastShipLazySailorScript` | `EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR` | -| `..._GENTLEMAN` | `SPRITE_GENTLEMAN` | 7 | 30 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_TRAINER` | 1 | `TrainerGentlemanEdward` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `..._PHARMACIST` | `SPRITE_PHARMACIST` | 2 | 30 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 4 | `TrainerBurglarCorey` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | - -On the maiden voyage only `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` is clear, so -Hiker Noland is the only trainer visible on this map - exactly what the -walkthrough describes. - -**Scripts of interest** - -- `FastShipLazySailorScript` (sym `5b:4d68`): not a `trainer` object, a scripted - battle. `playmusic MUSIC_HIKER_ENCOUNTER` -> `SailorStanlySeenText` -> - `winlosstext SailorStanlyBeatenText, 0` -> `loadtrainer SAILOR, STANLY` -> - `startbattle` -> `reloadmap` -> `special HealParty` -> - `setevent EVENT_BEAT_SAILOR_STANLY` -> after-battle text -> - `setevent EVENT_FAST_SHIP_LAZY_SAILOR` -> - `setmapscene FAST_SHIP_B1F, SCENE_FASTSHIPB1F_NOOP` (this is what unblocks - B1F) -> `readvar VAR_FACING`, walk him out, `disappear`. - Note `special HealParty` fires unconditionally after the fight. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | notes | -|---|---|---|---|---|---| -| `HIKER, NOLAND` | `HIKER` (`$2c`) | 14 | `; HIKER (14) db "NOLAND@", TRAINERTYPE_NORMAL` -> L31 `SANDSLASH`, L33 `GOLEM` | `TrainerHikerNoland` | flag `EVENT_BEAT_HIKER_NOLAND` | -| `SAILOR, STANLY` | `SAILOR` (`$28`) | 9 | `; SAILOR (9) db "STANLY@", TRAINERTYPE_NORMAL` -> L31 `MACHOP`, L33 `MACHOKE`, L26 `PSYDUCK` | `FastShipLazySailorScript` | scripted `loadtrainer`, not an `OBJECTTYPE_TRAINER` | - -### MAP_FAST_SHIP_CABINS_SW_SSW_NW - -- Script: `maps/FastShipCabins_SW_SSW_NW.asm` -- Blocks: `maps/FastShipCabins_SW_SSW_NW.blk` -- Header: `data/maps/maps.asm:337` -> `map FastShipCabins_SW_SSW_NW, TILESET_LIGHTHOUSE, INDOOR, LANDMARK_FAST_SHIP, MUSIC_SS_AQUA, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:311` `map_const FAST_SHIP_CABINS_SW_SSW_NW, 4, 16` (8 x 32 cells) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 0 | `FAST_SHIP_1F` | 5 | -| 2 | 2 | 19 | `FAST_SHIP_1F` | 6 | -| 3 | 3 | 19 | `FAST_SHIP_1F` | 6 | -| 4 | 2 | 31 | `FAST_SHIP_1F` | 7 | -| 5 | 3 | 31 | `FAST_SHIP_1F` | 7 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 1 | `BGEVENT_READ` | `FastShipBed` (sym `5b:521b`) | -| 7 | 2 | `BGEVENT_READ` | `FastShipBed` | -| 7 | 7 | `BGEVENT_READ` | `FastShipCabinsNorthwestCabinTrashcan` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `..._FISHER` | `SPRITE_FISHER` | 1 | 15 | `SPRITEMOVEDATA_SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` | 2 | `TrainerFirebreatherLyle` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `..._BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 6 | 15 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerBugCatcherKen` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `..._BEAUTY` | `SPRITE_BEAUTY` | 1 | 26 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBeautyCassie` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `..._ROCKER` | `SPRITE_ROCKER` | 3 | 28 | `SPRITEMOVEDATA_SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` | 2 | `TrainerGuitaristClyde` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | - -**Scripts of interest** - -- `FastShipBed` (bg_event at `(7,1)` / `(7,2)`, warp-1 sub-room = your cabin): - text, `FadeOutToBlack`, `ReloadSpritesNoPalettes`, `special HealParty`, - `playmusic MUSIC_HEAL`, `pause 60`, `RestartMapMusic`, `FadeInFromBlack`. - Then a docking check: skip if `EVENT_FAST_SHIP_HAS_ARRIVED`; otherwise - `checkevent EVENT_FAST_SHIP_FOUND_GIRL` **or** - `checkevent EVENT_FAST_SHIP_FIRST_TIME` -> `.CanArrive`, which plays - `SFX_ELEVATOR_END`, prints the arrival text and - `setevent EVENT_FAST_SHIP_HAS_ARRIVED`. - **Bot-relevant**: on the maiden voyage, sleeping in the bed does *not* dock the - ship, because both `EVENT_FAST_SHIP_FOUND_GIRL` and `EVENT_FAST_SHIP_FIRST_TIME` - are still clear. You must finish the granddaughter errand. - -**Trainers** - -| const | class | id | party | script label | notes | -|---|---|---|---|---|---| -| `FIREBREATHER, LYLE` | `FIREBREATHER` (`$30`) | 8 | `; FIREBREATHER (8) db "LYLE@", TRAINERTYPE_NORMAL` -> L28 `KOFFING`, L31 `FLAREON`, L28 `KOFFING` | `TrainerFirebreatherLyle` | flag `EVENT_BEAT_FIREBREATHER_LYLE` | - -### MAP_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN - -- Script: `maps/FastShipCabins_SE_SSE_CaptainsCabin.asm` -- Blocks: `maps/FastShipCabins_SE_SSE_CaptainsCabin.blk` -- Header: `data/maps/maps.asm:338` -> `map FastShipCabins_SE_SSE_CaptainsCabin, TILESET_LIGHTHOUSE, INDOOR, LANDMARK_FAST_SHIP, MUSIC_SS_AQUA, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:312` `map_const FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN, 5, 17` (10 x 34 cells) -- Layout: sub-room 1 `y` 0-11 (Colin + Twins), sub-room 2 `y` 12-23 (grandpa), - sub-room 3 `y` 24-33 (captain's cabin). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `FAST_SHIP_1F` | 8 | -| 2 | 3 | 7 | `FAST_SHIP_1F` | 8 | -| 3 | 2 | 19 | `FAST_SHIP_1F` | 9 | -| 4 | 3 | 19 | `FAST_SHIP_1F` | 9 | -| 5 | 2 | 33 | `FAST_SHIP_1F` | 10 | -| 6 | 3 | 33 | `FAST_SHIP_1F` | 10 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 25 | `BGEVENT_READ` | `FastShipCaptainsCabinTrashcan` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `..._CAPTAIN` | `SPRITE_CAPTAIN` | 3 | 25 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SSAquaCaptain` | `-1` | -| `..._GENTLEMAN` | `SPRITE_GENTLEMAN` | 2 | 17 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | 0 | `SSAquaGrandpa` (sym `5b:561b`) | `EVENT_FAST_SHIP_CABINS_SE_SSE_GENTLEMAN` | -| `..._TWIN1` | `SPRITE_TWIN` | 3 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | 0 | `SSAquaGranddaughterAfter` | `EVENT_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN_TWIN_1` | -| `..._TWIN2` | `SPRITE_TWIN` | 2 | 25 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | 0 | `SSAquaGranddaughterBefore` | `EVENT_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN_TWIN_2` | -| `..._POKEFAN_M1` | `SPRITE_POKEFAN_M` | 5 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerPokefanmColin` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `..._TWIN3` | `SPRITE_TWIN` | 2 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 1 | `TrainerTwinsMegandpeg1` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `..._TWIN4` | `SPRITE_TWIN` | 3 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 1 | `TrainerTwinsMegandpeg2` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `..._SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 5 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 5 | `TrainerPsychicRodney` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `..._POKEFAN_M2` | `SPRITE_POKEFAN_M` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPokefanmJeremy` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `..._POKEFAN_F` | `SPRITE_POKEFAN_F` | 5 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerPokefanfGeorgia` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `..._SUPER_NERD2` | `SPRITE_SUPER_NERD` | 1 | 15 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_TRAINER` | 2 | `TrainerSupernerdShawn` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | - -**Scripts of interest** - -- `SSAquaGrandpa` (talk to `..._GENTLEMAN` at `(2,17)`): - `checkevent EVENT_GOT_METAL_COAT_FROM_GRANDPA_ON_SS_AQUA` -> `SSAquaGotMetalCoat`; - `checkevent EVENT_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN_TWIN_2` (i.e. the - granddaughter object is *hidden* = already found) -> `SSAquaFoundGranddaughter`; - otherwise the "I can't find my granddaughter" text plus - `setmapscene FAST_SHIP_1F, SCENE_FASTSHIP1F_NOOP`. -- `SSAquaGranddaughterBefore` (talk to `..._TWIN2` at `(2,25)` in the captain's - cabin) - the payoff scene: `FadeOutToBlack`, `disappear` TWIN2, teleport the - player with `SSAquaCaptainsCabinWarpsToGrandpasCabinMovement` - (`big_step RIGHT` + six `big_step UP`), `moveobject TWIN1, 3, 19` + `appear`, - `showemote EMOTE_SHOCK` on the gentleman, dialogue, then - `setevent EVENT_VERMILION_PORT_SAILOR_AT_GANGWAY`, - `setmapscene FAST_SHIP_1F, SCENE_FASTSHIP1F_NOOP`, - `sjump SSAquaMetalCoatAndDocking`. -- `SSAquaMetalCoatAndDocking` (sym `5b:5633`): `verbosegiveitem METAL_COAT`, - `iffalse .NoRoom`, `setevent EVENT_GOT_METAL_COAT_FROM_GRANDPA_ON_SS_AQUA`, - then `playsound SFX_ELEVATOR_END`, `pause 30`, arrival text, - `setevent EVENT_FAST_SHIP_HAS_ARRIVED`, `setevent EVENT_FAST_SHIP_FOUND_GIRL`. - **This is the docking trigger for the maiden voyage.** A full bag skips the - item but still docks the ship. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `METAL_COAT` | finish the granddaughter errand | `SSAquaMetalCoatAndDocking` / `SSAquaFoundGranddaughter` `verbosegiveitem METAL_COAT` | `EVENT_GOT_METAL_COAT_FROM_GRANDPA_ON_SS_AQUA` | - -**Trainers** - -| const | class | id | party | script label | notes | -|---|---|---|---|---|---| -| `POKEFANM, COLIN` | `POKEFANM` (`$3b`) | 9 | `; POKEFANM (9) db "COLIN@", TRAINERTYPE_ITEM` -> L32 `DELIBIRD` holding `BERRY` | `TrainerPokefanmColin` | `EVENT_BEAT_POKEFANM_COLIN` | -| `TWINS, MEGANDPEG1` | `TWINS` (`$3d`) | 7 | `; TWINS (7) db "MEG & PEG@", TRAINERTYPE_NORMAL` -> L31 `TEDDIURSA`, L31 `PHANPY` | `TrainerTwinsMegandpeg1` | shares flag `EVENT_BEAT_TWINS_MEG_AND_PEG` | -| `TWINS, MEGANDPEG2` | `TWINS` (`$3d`) | 8 | `; TWINS (8)` -> L31 `PHANPY`, L31 `TEDDIURSA` | `TrainerTwinsMegandpeg2` | same flag - beating either removes both | - -### MAP_FAST_SHIP_B1F - -- Script: `maps/FastShipB1F.asm` -- Blocks: `maps/FastShipB1F.blk` -- Header: `data/maps/maps.asm:339` -> `map FastShipB1F, TILESET_LIGHTHOUSE, INDOOR, LANDMARK_FAST_SHIP, MUSIC_SS_AQUA, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:313` `map_const FAST_SHIP_B1F, 16, 8` (32 x 16 cells) - -Scene ids: `SCENE_FASTSHIPB1F_SAILOR_BLOCKS` = 0, `SCENE_FASTSHIPB1F_NOOP` = 1. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 11 | `FAST_SHIP_1F` | 11 | -| 2 | 31 | 13 | `FAST_SHIP_1F` | 12 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_FASTSHIPB1F_SAILOR_BLOCKS` (0) | 30 | 7 | `FastShipB1FSailorBlocksLeft` | if `EVENT_FAST_SHIP_B1F_SAILOR_RIGHT` set -> no-op; else move sailor2 left, `moveobject FASTSHIPB1F_SAILOR1, 30, 6` + `appear`, `disappear` sailor2 | -| `SCENE_FASTSHIPB1F_SAILOR_BLOCKS` (0) | 31 | 7 | `FastShipB1FSailorBlocksRight` | mirror: `moveobject FASTSHIPB1F_SAILOR2, 31, 6` + `appear`, `disappear` sailor1 | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 27 | 9 | `BGEVENT_READ` | `FastShipB1FTrashcan` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `FASTSHIPB1F_SAILOR1` | `SPRITE_SAILOR` | 30 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `FastShipB1FSailorScript` | `EVENT_FAST_SHIP_B1F_SAILOR_LEFT` | -| `FASTSHIPB1F_SAILOR2` | `SPRITE_SAILOR` | 31 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `FastShipB1FSailorScript` | `EVENT_FAST_SHIP_B1F_SAILOR_RIGHT` | -| `FASTSHIPB1F_SAILOR3` | `SPRITE_SAILOR` | 9 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSailorJeff` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `FASTSHIPB1F_LASS` | `SPRITE_LASS` | 6 | 4 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 1 | `TrainerPicnickerDebra` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `FASTSHIPB1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 26 | 9 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 1 | `TrainerJugglerFritz` | `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` | -| `FASTSHIPB1F_SAILOR4` | `SPRITE_SAILOR` | 17 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSailorGarrett` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `FASTSHIPB1F_FISHER` | `SPRITE_FISHER` | 25 | 8 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerFisherJonah` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `FASTSHIPB1F_BLACK_BELT` | `SPRITE_BLACK_BELT` | 15 | 11 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` | 3 | `TrainerBlackbeltWai` | `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND` | -| `FASTSHIPB1F_SAILOR5` | `SPRITE_SAILOR` | 23 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSailorKenneth` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `FASTSHIPB1F_TEACHER` | `SPRITE_TEACHER` | 9 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 3 | `TrainerTeacherShirley` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `FASTSHIPB1F_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 14 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_TRAINER` | 1 | `TrainerSchoolboyNate` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | -| `FASTSHIPB1F_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 14 | 11 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 1 | `TrainerSchoolboyRicky` | `EVENT_FAST_SHIP_PASSENGERS_WESTBOUND` | - -**Scripts of interest** - -- `FastShipB1FSailorScript` (sym `5b:5e37`), shared by both blocking sailors: - - `checkevent EVENT_FAST_SHIP_FIRST_TIME` / `iftrue .FirstTime` -> on repeat - trips he just gives directions and does not block the story. - - `checkevent EVENT_FAST_SHIP_LAZY_SAILOR` / `iftrue .LazySailor` -> thanks you; - if `EVENT_FAST_SHIP_FOUND_GIRL` is still clear he also prints - `FastShipB1FOnDutySailorSawLittleGirlText` (the "a little girl went by" hint). - - `checkevent EVENT_FAST_SHIP_INFORMED_ABOUT_LAZY_SAILOR` / `iftrue - .AlreadyInformed`. - - First talk: prints the request, `setevent - EVENT_FAST_SHIP_INFORMED_ABOUT_LAZY_SAILOR` and - `clearevent EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR` - **this is what spawns - the lazy sailor** in the NNW/NNE/NE cabins map. -- The physical block is the sailor object standing on `(30,6)` or `(31,6)`; the - coord events at `(30,7)`/`(31,7)` shuffle whichever sailor is active onto the - tile you approached. Once `FastShipLazySailorScript` runs - `setmapscene FAST_SHIP_B1F, SCENE_FASTSHIPB1F_NOOP`, the coord events stop - firing and only one of the two tiles stays occupied. - -**Trainers** - -| const | class | id | party | script label | notes | -|---|---|---|---|---|---| -| `SAILOR, JEFF` | `SAILOR` (`$28`) | 6 | `; SAILOR (6) db "JEFF@", TRAINERTYPE_NORMAL` -> L32 `RATICATE`, L32 `RATICATE` | `TrainerSailorJeff` | `EVENT_BEAT_SAILOR_JEFF` | -| `PICNICKER, DEBRA` | `PICNICKER` (`$35`) | 8 | `; PICNICKER (8) db "DEBRA@", TRAINERTYPE_NORMAL` -> L33 `SEAKING` | `TrainerPicnickerDebra` | `EVENT_BEAT_PICNICKER_DEBRA` | -| `JUGGLER, FRITZ` | `JUGGLER` (`$31`) | 2 | `; JUGGLER (2) db "FRITZ@", TRAINERTYPE_NORMAL` -> L29 `MR__MIME`, L29 `MAGMAR`, L29 `MACHOKE` | `TrainerJugglerFritz` | `EVENT_BEAT_JUGGLER_FRITZ` | - -Note the party order in the asm is Mr. Mime, **Magmar**, Machoke - the -walkthrough lists Mr. Mime, Machoke, Magmar. - -### MAP_VERMILION_PORT - -- Script: `maps/VermilionPort.asm` -- Blocks: `maps/VermilionPort.blk` -- Header: `data/maps/maps.asm:334` -> `map VermilionPort, TILESET_PORT, ROUTE, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:308` `map_const VERMILION_PORT, 10, 18` (20 x 36 cells) -- Attributes: `data/maps/attributes.asm:587` `map_attributes VermilionPort, VERMILION_PORT, $0a` - no connections -- Callback: `callback MAPCALLBACK_NEWMAP, VermilionPortFlypointCallback` -> - `setflag ENGINE_FLYPOINT_VERMILION` - -Scene ids: `SCENE_VERMILIONPORT_ASK_ENTER_SHIP` = 0, -`SCENE_VERMILIONPORT_LEAVE_SHIP` = 1. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 5 | `VERMILION_PORT_PASSAGE` | 5 | -| 2 | 7 | 17 | `FAST_SHIP_1F` | 1 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_VERMILIONPORT_ASK_ENTER_SHIP` (0) | 7 | 11 | `VermilionPortWalkUpToShipScript` | boarding prompt on the way back to Johto | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 13 | `BGEVENT_ITEM` | `VermilionPortHiddenIron` -> `hiddenitem IRON, EVENT_VERMILION_PORT_HIDDEN_IRON` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VERMILIONPORT_SAILOR1` | `SPRITE_SAILOR` | 7 | 17 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `VermilionPortSailorAtGangwayScript` | `EVENT_VERMILION_PORT_SAILOR_AT_GANGWAY` | -| `VERMILIONPORT_SAILOR2` | `SPRITE_SAILOR` | 6 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VermilionPortSailorScript` | `-1` | -| `VERMILIONPORT_SUPER_NERD` | `SPRITE_SUPER_NERD` | 11 | 11 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `VermilionPortSuperNerdScript` | `-1` | - -**Scripts of interest** - -- `VermilionPortLeaveShipScript` (sym `5b:450e`, scene 1, `sdefer`): the arrival - cutscene. One `step UP`, `appear VERMILIONPORT_SAILOR1`, - `setscene SCENE_VERMILIONPORT_ASK_ENTER_SHIP`, then the end-of-voyage - bookkeeping: - `setevent EVENT_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN_TWIN_1`, - `setevent EVENT_FAST_SHIP_CABINS_SE_SSE_GENTLEMAN` (grandpa and granddaughter - vanish from the ship for good), - `setevent EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP` (the eight maiden-voyage - trainers never appear again), - `clearevent EVENT_OLIVINE_PORT_PASSAGE_POKEFAN_M`, - `setevent EVENT_FAST_SHIP_FIRST_TIME`, - `setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`, - `blackoutmod VERMILION_CITY`. -- `VermilionPortWalkUpToShipScript` / `VermilionPortSailorScript`: - `readvar VAR_WEEKDAY`; `MONDAY`/`TUESDAY` -> `.NextShipWednesday`, - `THURSDAY`/`FRIDAY`/`SATURDAY` -> `.NextShipSunday`. So from Vermilion the ship - sails **Wednesday and Sunday**, matching the walkthrough. There is no - first-time bypass on this side. Boarding sets - `EVENT_FAST_SHIP_PASSENGERS_EASTBOUND`, clears `..._WESTBOUND`, clears the ten - westbound `EVENT_BEAT_*` flags, sets - `EVENT_FAST_SHIP_DESTINATION_OLIVINE`, `setmapscene FAST_SHIP_1F, - SCENE_FASTSHIP1F_ENTER_SHIP`, `warp FAST_SHIP_1F, 25, 1`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `IRON` | hidden, face `(16,13)` | `VermilionPortHiddenIron` | `EVENT_VERMILION_PORT_HIDDEN_IRON` | - -**Wild encounters** - -`data/wild/kanto_water.asm:166` `def_water_wildmons VERMILION_PORT`, rate -`2 percent`: `35 TENTACOOL / 30 TENTACOOL / 35 TENTACRUEL`. Fishing -`FISHGROUP_OCEAN`. - -### MAP_VERMILION_PORT_PASSAGE - -- Script: `maps/VermilionPortPassage.asm` -- Blocks: `maps/PortPassage.blk` (shared) -- Header: `data/maps/maps.asm:341` -> `map VermilionPortPassage, TILESET_UNDERGROUND, INDOOR, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:315` `map_const VERMILION_PORT_PASSAGE, 10, 9` (20 x 18 cells) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 0 | `VERMILION_CITY` | 8 | -| 2 | 16 | 0 | `VERMILION_CITY` | 9 | -| 3 | 15 | 4 | `VERMILION_PORT_PASSAGE` | 4 | -| 4 | 3 | 2 | `VERMILION_PORT_PASSAGE` | 3 | -| 5 | 3 | 14 | `VERMILION_PORT` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VERMILIONPORTPASSAGE_TEACHER` | `SPRITE_TEACHER` | 17 | 1 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `VermilionPortPassageTeacherScript` | `-1` | - -### MAP_VERMILION_CITY - -- Script: `maps/VermilionCity.asm` -- Blocks: `maps/VermilionCity.blk` -- Header: `data/maps/maps.asm:296` -> `map VermilionCity, TILESET_KANTO, TOWN, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:273` `map_const VERMILION_CITY, 20, 18` (40 x 36 cells) -- Attributes: `data/maps/attributes.asm:342` `map_attributes VermilionCity, VERMILION_CITY, $43` -- Connections: `connection north, Route6, ROUTE_6, 5`; `connection east, Route11, ROUTE_11, 0` -- Callback: `callback MAPCALLBACK_NEWMAP, VermilionCityFlypointCallback` -> - `setflag ENGINE_FLYPOINT_VERMILION` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `VERMILION_FISHING_SPEECH_HOUSE` | 1 | -| 2 | 9 | 5 | `VERMILION_POKECENTER_1F` | 1 | -| 3 | 7 | 13 | `POKEMON_FAN_CLUB` | 1 | -| 4 | 13 | 13 | `VERMILION_MAGNET_TRAIN_SPEECH_HOUSE` | 1 | -| 5 | 21 | 13 | `VERMILION_MART` | 2 | -| 6 | 21 | 17 | `VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE` | 1 | -| 7 | 10 | 19 | `VERMILION_GYM` | 1 | -| 8 | 19 | 31 | `VERMILION_PORT_PASSAGE` | 1 | -| 9 | 20 | 31 | `VERMILION_PORT_PASSAGE` | 2 | -| 10 | 34 | 7 | `DIGLETTS_CAVE` | 1 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 25 | 3 | `BGEVENT_READ` | `VermilionCitySign` | -| 5 | 19 | `BGEVENT_READ` | `VermilionGymSign` | -| 5 | 13 | `BGEVENT_READ` | `PokemonFanClubSign` | -| 33 | 9 | `BGEVENT_READ` | `VermilionCityDiglettsCaveSign` | -| 27 | 15 | `BGEVENT_READ` | `VermilionCityPortSign` | -| 10 | 5 | `BGEVENT_READ` | `VermilionCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 22 | 13 | `BGEVENT_READ` | `VermilionCityMartSign` (`jumpstd MartSignScript`) | -| 12 | 19 | `BGEVENT_ITEM` | `VermilionCityHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VERMILIONCITY_TEACHER` | `SPRITE_TEACHER` | 18 | 9 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCityTeacherScript` | `-1` | -| `VERMILIONCITY_GRAMPS` | `SPRITE_GRAMPS` | 23 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VermilionMachopOwner` | `-1` | -| `VERMILIONCITY_MACHOP` | `SPRITE_MACHOP` | 26 | 7 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `VermilionMachop` | `-1` | -| `VERMILIONCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 14 | 16 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCitySuperNerdScript` | `-1` | -| `VERMILIONCITY_BIG_SNORLAX` | `SPRITE_BIG_SNORLAX` | 34 | 8 | `SPRITEMOVEDATA_BIGDOLLSYM` | `OBJECTTYPE_SCRIPT` | `VermilionSnorlax` | `EVENT_VERMILION_CITY_SNORLAX` | -| `VERMILIONCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 31 | 12 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VermilionGymBadgeGuy` | `-1` | - -**Scripts of interest** - -- `VermilionGymBadgeGuy` at `(31,12)`: `readvar VAR_BADGES`, - `ifequal NUM_BADGES, .AllBadges` (16 badges -> `verbosegiveitem HP_UP`, - `EVENT_GOT_HP_UP_FROM_VERMILION_GUY`), `ifgreater 13`, `ifgreater 9` for the - intermediate lines. Not reachable this section. -- `VermilionSnorlax` at `(34,8)`: `special SnorlaxAwake`; asleep unless the - Poke Flute radio channel is playing. Awake -> `loadvar VAR_BATTLETYPE, - BATTLETYPE_FORCEITEM`, `loadwildmon SNORLAX, 50`, `startbattle`, - `setevent EVENT_FOUGHT_SNORLAX`. Gates warp 10 to `DIGLETTS_CAVE`; out of scope - here but it is the reason the east side is closed. -- The **cut tree**: `maps/VermilionCity.blk` block index `(6,9)` holds - `TILESET_KANTO` block `$35`, whose collision quad is - `tilecoll FLOOR, CUT_TREE, WALL, FLOOR` (`data/tilesets/kanto_collision.asm` - row `$35`). That puts the cuttable tile at cell **`(13,18)`**, replacement - block `$4c`, animation `0` (`data/collision/field_move_blocks.asm:31`). It is - the only cut tree on the map. The hidden Full Heal bg_event at `(12,19)` is the - `WALL` quadrant of the same block. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `FULL_HEAL` | hidden, face `(12,19)` | `VermilionCityHiddenFullHeal` | `EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | -| `HP_UP` | all 16 badges (not this section) | `VermilionGymBadgeGuy` `.AllBadges` | `EVENT_GOT_HP_UP_FROM_VERMILION_GUY` | - -**Wild encounters** - -`data/wild/kanto_water.asm:138` `def_water_wildmons VERMILION_CITY`, rate -`6 percent`: `35 TENTACOOL / 30 TENTACOOL / 35 TENTACRUEL`. Fishing -`FISHGROUP_OCEAN` (`data/wild/fish.asm` `.Ocean_Old/Good/Super`, lines 42-55). -No entry in `kanto_grass.asm` and no `treemons.asm` row. - -### MAP_POKEMON_FAN_CLUB - -- Script: `maps/PokemonFanClub.asm` -- Blocks: `maps/PokemonFanClub.blk` -- Header: `data/maps/maps.asm:300` -> `map PokemonFanClub, TILESET_HOUSE, INDOOR, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:277` `map_const POKEMON_FAN_CLUB, 5, 4` (10 x 8 cells) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `VERMILION_CITY` | 3 | -| 2 | 3 | 7 | `VERMILION_CITY` | 3 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 0 | `BGEVENT_READ` | `PokemonFanClubListenSign` | -| 9 | 0 | `BGEVENT_READ` | `PokemonFanClubBraggingSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `POKEMONFANCLUB_CHAIRMAN` | `SPRITE_GENTLEMAN` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubChairmanScript` (sym `59:4340`) | `-1` | -| `POKEMONFANCLUB_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 4 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubReceptionistScript` | `-1` | -| `POKEMONFANCLUB_CLEFAIRY_GUY` | `SPRITE_FISHER` | 2 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubClefairyGuyScript` | `-1` | -| `POKEMONFANCLUB_TEACHER` | `SPRITE_TEACHER` | 7 | 2 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubTeacherScript` | `-1` | -| `POKEMONFANCLUB_FAIRY` | `SPRITE_FAIRY` | 2 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubClefairyDollScript` | `EVENT_VERMILION_FAN_CLUB_DOLL` | -| `POKEMONFANCLUB_ODDISH` | `SPRITE_ODDISH` | 7 | 3 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubBayleefScript` | `-1` | - -**Scripts of interest** - -- `PokemonFanClubChairmanScript`: `checkevent - EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT` -> `.HeardSpeech`; - `checkevent EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT_BUT_BAG_WAS_FULL` -> - `.HeardSpeechButBagFull` (skips the speech, retries the gift). Otherwise - `yesorno` on `...DidYouVisitToHearAboutMyMonText`; **answering No** goes to - `.NotListening` and gives nothing. Yes -> the Rapidash speech -> - `verbosegiveitem RARE_CANDY`, `iffalse .BagFull`, - `setevent EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT`. - (Nothing in this file ever *sets* - `EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT_BUT_BAG_WAS_FULL`; it is only read.) -- `PokemonFanClubClefairyGuyScript` handles the `LOST_ITEM` / Copycat doll chain - (`EVENT_RETURNED_MACHINE_PART`, `EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM`, - `EVENT_GOT_LOST_ITEM_FROM_FAN_CLUB`) - a later section. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `RARE_CANDY` | answer "Yes" and sit through the speech | `PokemonFanClubChairmanScript` `verbosegiveitem RARE_CANDY` | `EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT` | - -### MAP_VERMILION_GYM - -- Script: `maps/VermilionGym.asm` -- Blocks: `maps/VermilionGym.blk` -- Header: `data/maps/maps.asm:304` -> `map VermilionGym, TILESET_GAME_CORNER, INDOOR, LANDMARK_VERMILION_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:281` `map_const VERMILION_GYM, 5, 9` (10 x 18 cells) -- No scene scripts, no callbacks, no coord events. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `VERMILION_CITY` | 7 | -| 2 | 5 | 17 | `VERMILION_CITY` | 7 | - -**Coord events** - none. (The Gen 1 trash-can/switch puzzle is gone - the guide -NPC even lampshades it in `VermilionGymGuideText`.) - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 7 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 3 | 7 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 5 | 7 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 7 | 7 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 9 | 7 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 1 | 9 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 3 | 9 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 5 | 9 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 7 | 9 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 9 | 9 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 1 | 11 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 3 | 11 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 5 | 11 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 7 | 11 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 9 | 11 | `BGEVENT_READ` | `VermilionGymTrashCan` | -| 3 | 15 | `BGEVENT_READ` | `VermilionGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `VermilionGymStatue` | - -`VermilionGymStatue`: `checkflag ENGINE_THUNDERBADGE` -> if set, -`gettrainername STRING_BUFFER_4, LT_SURGE, LT_SURGE1` + `jumpstd GymStatue2Script`, -else `jumpstd GymStatue1Script`. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `VERMILIONGYM_SURGE` | `SPRITE_SURGE` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `VermilionGymSurgeScript` (sym `59:4bfc`) | `-1` | -| `VERMILIONGYM_GENTLEMAN` | `SPRITE_GENTLEMAN` | 8 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerGentlemanGregory` | `-1` | -| `VERMILIONGYM_ROCKER` | `SPRITE_ROCKER` | 4 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` (radius x 3) | `OBJECTTYPE_TRAINER` | 3 | `TrainerGuitaristVincent` | `-1` | -| `VERMILIONGYM_SUPER_NERD` | `SPRITE_SUPER_NERD` | 0 | 10 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerJugglerHorton` | `-1` | -| `VERMILIONGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 15 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 1 | `VermilionGymGuideScript` | `-1` | - -**Scripts of interest** - -- `VermilionGymSurgeScript`: `checkflag ENGINE_THUNDERBADGE` / `iftrue - .FightDone`; else `LtSurgeIntroText`, `winlosstext LtSurgeWinLossText, 0`, - `loadtrainer LT_SURGE, LT_SURGE1`, `startbattle`, `reloadmapafterbattle`, - then `setevent EVENT_BEAT_LTSURGE` **and** - `setevent EVENT_BEAT_GENTLEMAN_GREGORY`, - `setevent EVENT_BEAT_GUITARIST_VINCENT`, - `setevent EVENT_BEAT_JUGGLER_HORTON` - beating Surge retroactively clears the - three gym trainers, so a bot may legally skip them. Then - `ReceivedThunderBadgeText`, `playsound SFX_GET_BADGE`, `waitsfx`, - `setflag ENGINE_THUNDERBADGE`, `LtSurgeThunderBadgeText`. - There is no TM award in this gym script. -- `VermilionGymGuideScript` branches on `EVENT_BEAT_LTSURGE`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_THUNDERBADGE` | `constants/engine_flags.asm:49` | set by `VermilionGymSurgeScript`; read there and by `VermilionGymStatue` | the section's terminal milestone | -| `EVENT_BEAT_LTSURGE` | `constants/event_flags.asm:717` | set by `VermilionGymSurgeScript`; read by `VermilionGymGuideScript` | post-win dialogue switch | -| `EVENT_BEAT_GUITARIST_VINCENT` / `EVENT_BEAT_JUGGLER_HORTON` / `EVENT_BEAT_GENTLEMAN_GREGORY` | `constants/event_flags.asm` | set by each `trainer` macro and also by Surge's script | trainer already-beaten guards | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `GUITARIST, VINCENT` | `GUITARIST` (`$2b`) | 2 | `; GUITARIST (2) db "VINCENT@", TRAINERTYPE_NORMAL` -> L27 `MAGNEMITE`, L33 `VOLTORB`, L32 `MAGNEMITE`, L32 `MAGNEMITE` | `TrainerGuitaristVincent` | none | -| `JUGGLER, HORTON` | `JUGGLER` (`$31`) | 3 | `; JUGGLER (3) db "HORTON@", TRAINERTYPE_NORMAL` -> 4x L33 `ELECTRODE` | `TrainerJugglerHorton` | none | -| `GENTLEMAN, GREGORY` | `GENTLEMAN` (`$20`) | 3 | `; GENTLEMAN (3) db "GREGORY@", TRAINERTYPE_NORMAL` -> L37 `PIKACHU`, L33 `FLAAFFY` | `TrainerGentlemanGregory` | none | -| `LT_SURGE, LT_SURGE1` | `LT_SURGE` (`$13`) | 1 | `data/trainers/parties.asm:289-296`, `TRAINERTYPE_MOVES`: L44 `RAICHU` (Thunder Wave / Quick Attack / Thunderbolt / Thunder); L40 `ELECTRODE` (Screech / Double Team / Swift / Explosion); L40 `MAGNETON` (Lock-On / Double Team / Swift / Zap Cannon); L40 `ELECTRODE` (same set); L46 `ELECTABUZZ` (Quick Attack / ThunderPunch / Light Screen / Thunder) | `VermilionGymSurgeScript` | none | - -Party **order** is Raichu, Electrode, Magneton, Electrode, Electabuzz - the -walkthrough lists Raichu, Magneton, Electabuzz, Electrode, Electrode. - -**Wild encounters** - none (indoor). - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Elm will not hand over the ticket | `maps/ElmsLab.asm` `ProfElmScript` `checkevent EVENT_BEAT_ELITE_FOUR` | Hall of Fame done | `maps/HallOfFame.asm:33` `setevent EVENT_BEAT_ELITE_FOUR` | -| Cannot board the Fast Ship | `maps/OlivinePort.asm` `OlivinePortWalkUpToShipScript` / `OlivinePortSailorAfterHOFScript` `checkitem S_S_TICKET`, `iffalse .NoTicket` | `S_S_TICKET` in the bag | `ElmGiveTicketScript` | -| Boarding sailor absent before HOF | `maps/OlivinePort.asm` object rows with `EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME`; the pre-HOF sailor's `OlivinePortSailorBeforeHOFScript` says "you're not allowed in" | Hall of Fame done | `HallOfFame.asm:38` `clearevent EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME` | -| Ship only sails on certain days (repeat trips) | `OlivinePortWalkUpToShipScript` `readvar VAR_WEEKDAY` (Mon/Fri from Olivine); `VermilionPortWalkUpToShipScript` (Wed/Sun from Vermilion) | correct weekday | maiden voyage bypasses via `checkevent EVENT_FAST_SHIP_FIRST_TIME` / `iffalse .FirstTime` | -| Sailor blocks the B1F corridor | `maps/FastShipB1F.asm` coord events `(30,7)`/`(31,7)` -> `FastShipB1FSailorBlocksLeft/Right` plus the physical object at `(30,6)`/`(31,6)`; `FastShipB1FSailorScript` | find and beat the lazy sailor | `FastShipLazySailorScript` -> `setevent EVENT_FAST_SHIP_LAZY_SAILOR` + `setmapscene FAST_SHIP_B1F, SCENE_FASTSHIPB1F_NOOP` | -| Lazy sailor does not exist yet | object flag `EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR`, set at new game (`engine/events/std_scripts.asm:499`) | talk to the on-duty sailor first | `FastShipB1FSailorScript` `clearevent EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR` | -| Ship will not dock (maiden voyage) | `maps/FastShip1F.asm` `FastShip1FSailor1Script` `checkevent EVENT_FAST_SHIP_HAS_ARRIVED`; `FastShipBed` `.CanArrive` needs `EVENT_FAST_SHIP_FOUND_GIRL` or `EVENT_FAST_SHIP_FIRST_TIME` | finish the granddaughter errand | `SSAquaMetalCoatAndDocking` `setevent EVENT_FAST_SHIP_HAS_ARRIVED` / `EVENT_FAST_SHIP_FOUND_GIRL` | -| Chairman's Rare Candy | `maps/PokemonFanClub.asm` `PokemonFanClubChairmanScript` `yesorno` -> `.NotListening` | answer **Yes** | `setevent EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT` | -| Cut tree in Vermilion City | tile collision `CUT_TREE` at cell `(13,18)` + `data/collision/field_move_blocks.asm:31` `db $35, $4c, 0`; `engine/events/overworld.asm:133` `CheckAble` requires `ENGINE_HIVEBADGE`, `:169` `CheckMapForSomethingToCut` | HM01 CUT + HIVEBADGE | already held long before Kanto. **Does not gate the gym** - see section 6 | -| Snorlax blocks `DIGLETTS_CAVE` (warp 10, `(34,7)`) | `maps/VermilionCity.asm` `VermilionSnorlax` `special SnorlaxAwake` | Poke Flute radio channel | out of scope for this section | -| Lt. Surge re-battle refused | `VermilionGymSurgeScript` `checkflag ENGINE_THUNDERBADGE` -> `.FightDone` | - | one-shot badge | - ---- - -## 4. Bot checklist - -1. `NEW_BARK_TOWN` -> walk to `(6,3)`, warp 1, enter `ELMS_LAB`. - Pre: `EVENT_BEAT_ELITE_FOUR` set. (An `SPECIALCALL_SSTICKET` phone call may - fire on the way; answering it is optional.) -2. `ELMS_LAB` -> walk to `(5,3)` and face up to talk to `ELMSLAB_ELM` at `(5,2)`. - Intent: talk. Pre: `EVENT_BEAT_ELITE_FOUR`, `!EVENT_GOT_SS_TICKET_FROM_ELM`. - Post: `EVENT_GOT_SS_TICKET_FROM_ELM`, bag holds `S_S_TICKET`. -3. Fly to `OLIVINE_CITY`; walk to warp 10 `(19,27)` -> `OLIVINE_PORT_PASSAGE`. -4. `OLIVINE_PORT_PASSAGE`: warp 3 `(15,4)` -> lands at warp 4 `(3,2)`; then walk - to warp 5 `(3,14)` -> `OLIVINE_PORT` warp 1 at `(11,7)`. -5. `OLIVINE_PORT`: walk onto `(7,15)`. Intent: walk (coord event). - Post: yes/no prompt -> answer **Yes**; `checkitem S_S_TICKET` passes; the - script auto-walks you 7 south and warps you to `FAST_SHIP_1F (25,1)`. - Post-flags: `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_2`, - `!EVENT_FAST_SHIP_DESTINATION_OLIVINE`, scene `FAST_SHIP_1F` = 1. - (Alternative: talk to `OLIVINEPORT_SAILOR3` at `(6,15)` for the same flow.) -6. `FAST_SHIP_1F`: the boarding cutscene runs on entry (scene 1). Then walk south - to `(24,6)` or `(25,6)`. Intent: walk (coord event `WorriedGrandpaSceneLeft`). - Post: scene -> `SCENE_FASTSHIP1F_NOOP`; nothing else changes. -7. `FAST_SHIP_1F` warp 3 at `(23,8)` -> `FAST_SHIP_CABINS_NNW_NNE_NE (2,12)`. - Battle `TrainerHikerNoland` at `(4,17)` (sight 3, faces up). - Post: `EVENT_BEAT_HIKER_NOLAND`. -8. Back to 1F; warp 5 at `(15,8)` -> `FAST_SHIP_CABINS_SW_SSW_NW (2,0)`; face the - bed bg_event at `(7,1)`/`(7,2)` to heal (`FastShipBed` -> `special HealParty`). - Do **not** expect the ship to dock yet. -9. 1F warp 6 at `(15,15)` -> `..._SW_SSW_NW (2,19)`. Battle - `TrainerFirebreatherLyle` at `(1,15)`. Post: `EVENT_BEAT_FIREBREATHER_LYLE`. -10. 1F warp 8 at `(23,15)` -> `..._SE_SSE (2,7)`. Battle `TrainerPokefanmColin` - at `(5,6)` and the twins at `(2,4)`/`(3,4)`. - Post: `EVENT_BEAT_POKEFANM_COLIN`, `EVENT_BEAT_TWINS_MEG_AND_PEG`. -11. 1F warp 9 at `(27,15)` -> `..._SE_SSE (2,19)`. Talk to `SSAquaGrandpa` at - `(2,17)`. Post: `setmapscene FAST_SHIP_1F, SCENE_FASTSHIP1F_NOOP` (flavour - only; the errand is not flagged here). -12. 1F warp 12 at `(30,14)` -> `FAST_SHIP_B1F (31,13)`. Walk north onto `(31,7)` - (or `(30,7)`). Intent: walk (coord event) -> a sailor moves in front of you. - Talk to him. Post: `EVENT_FAST_SHIP_INFORMED_ABOUT_LAZY_SAILOR` set, - `EVENT_FAST_SHIP_CABINS_NNW_NNE_NE_SAILOR` cleared. -13. Back to 1F; warp 4 at `(19,8)` -> `..._NNW_NNE_NE (2,24)`. Talk to the sailor - at `(4,26)`. Intent: talk -> forced battle `SAILOR STANLY`. - Post: `EVENT_BEAT_SAILOR_STANLY`, `EVENT_FAST_SHIP_LAZY_SAILOR`, - `FAST_SHIP_B1F` scene -> `SCENE_FASTSHIPB1F_NOOP`, party healed. -14. `FAST_SHIP_B1F` again (1F warp 12 -> `(31,13)`): the corridor is now passable. - Battle `TrainerJugglerFritz` at `(26,9)`, `TrainerSailorJeff` at `(9,11)`, - `TrainerPicnickerDebra` at `(6,4)`. -15. `FAST_SHIP_B1F` warp 1 at `(5,11)` -> `FAST_SHIP_1F (6,12)`; then 1F warp 10 - at `(3,13)` -> `..._SE_SSE (2,33)`, the captain's cabin. -16. Talk to the granddaughter (`..._TWIN2`) at `(2,25)`. Intent: talk. - Pre: `!EVENT_FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN_TWIN_2`. - Post: teleport cutscene, `verbosegiveitem METAL_COAT`, - `EVENT_GOT_METAL_COAT_FROM_GRANDPA_ON_SS_AQUA`, - `EVENT_FAST_SHIP_HAS_ARRIVED`, `EVENT_FAST_SHIP_FOUND_GIRL`, - `EVENT_VERMILION_PORT_SAILOR_AT_GANGWAY`, 1F scene -> NOOP. - You end up standing in the grandpa's sub-room; leave via warp 3/4 `(2,19)`/`(3,19)`. -17. `FAST_SHIP_1F`: walk to `(25,3)` and talk to `FASTSHIP1F_SAILOR1` at `(25,2)`. - Pre: `EVENT_FAST_SHIP_HAS_ARRIVED`. - Post: `warp VERMILION_PORT, 7, 17`, `VERMILION_PORT` scene -> - `SCENE_VERMILIONPORT_LEAVE_SHIP`. -18. `VERMILION_PORT`: the arrival scene runs (one step up). Post-flags include - `EVENT_FAST_SHIP_FIRST_TIME`, `EVENT_FAST_SHIP_PASSENGERS_FIRST_TRIP`, - `blackoutmod VERMILION_CITY`, `ENGINE_FLYPOINT_VERMILION` (map callback). - Optional: face `(16,13)` for the hidden `IRON`. -19. Walk to warp 1 `(9,5)` -> `VERMILION_PORT_PASSAGE (3,14)`; warp 4 `(3,2)` -> - warp 3 `(15,4)`; walk to warps 1/2 `(15,0)`/`(16,0)` -> `VERMILION_CITY` - `(19,31)`/`(20,31)`. -20. `VERMILION_CITY` -> warp 3 at `(7,13)` -> `POKEMON_FAN_CLUB (2,7)`. Talk to - the chairman at `(3,1)`; answer **Yes**. - Post: `EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT`, bag holds `RARE_CANDY`. -21. Optional: `VERMILION_POKECENTER_1F` via warp 2 `(9,5)` to heal. -22. Optional: face cell `(13,18)` and use CUT (needs `ENGINE_HIVEBADGE`), then - face `(12,19)` for the hidden `FULL_HEAL`. -23. `VERMILION_CITY` warp 7 at `(10,19)` -> `VERMILION_GYM (4,17)`. -24. Gym: `TrainerGuitaristVincent` at `(4,7)` (sight 3, faces down), - `TrainerJugglerHorton` at `(0,10)` (sight 4, faces right), - `TrainerGentlemanGregory` at `(8,8)` (sight 4, faces left). - All three are optional - Surge's win script sets their flags. -25. Walk to `(5,3)` and talk to `VERMILIONGYM_SURGE` at `(5,2)`. - Pre: `!ENGINE_THUNDERBADGE`. Intent: talk -> battle `LT_SURGE, LT_SURGE1`. - Post: `EVENT_BEAT_LTSURGE`, `EVENT_BEAT_GENTLEMAN_GREGORY`, - `EVENT_BEAT_GUITARIST_VINCENT`, `EVENT_BEAT_JUGGLER_HORTON`, - `ENGINE_THUNDERBADGE`. **Section complete.** - ---- - -## 5. Port coverage - -Everything in this section is data-driven: the Gen 2 side of this repo extracts -map headers, blocks, warps, coord/bg/object events and scene scripts generically -(`src/import/RomExtractorGen2.lua:804-977`) and runs the scripts through -`src/script/gen2/Vm.lua`. There is no per-map hand-port for any of these maps, so -"implemented" below means the generic mechanism exists and the ROM data drives -it, not that the beat has been played through. - -| Beat | Port file | Status | -|---|---|---| -| Map load: blocks, collision quads, warps by cell | `src/world/gen2/Map.lua` | implemented (generic) | -| Warp / coord / bg event dispatch | `src/world/gen2/World.lua:5013` (coord), `:5148` (bg), `:1183` + `:5026` (scene scripts) | implemented (generic) | -| `verbosegiveitem` (S.S. TICKET, METAL COAT, RARE CANDY) | `src/script/gen2/Vm.lua:490-522` | implemented | -| `checkitem S_S_TICKET` boarding gate | `src/script/gen2/Vm.lua:523` | implemented | -| `loadtrainer` / `startbattle` (Stanly, Lt. Surge) | `src/script/gen2/Vm.lua:806`, `:918` (`winlosstext`) | implemented | -| `OBJECTTYPE_TRAINER` sight lines (ship + gym trainers) | `src/world/gen2/Trainers.lua:98` `Trainers.sees` | implemented | -| `TRAINERTYPE_MOVES` parties (Lt. Surge) and `TRAINERTYPE_ITEM` (Colin) | `src/import/RomExtractorGen2.lua:3900-3968` | implemented | -| `setmapscene` / `sdefer` scene plumbing (ports, ship, B1F) | `src/script/gen2/Vm.lua:92`, `:279` | implemented | -| `moveobject` + `appear` sailor-shuffle on B1F | `src/script/gen2/Vm.lua:337-350` (the comment calls out exactly this pattern) | implemented | -| `showemote` (grandpa's `!`) and `earthquake` (boarding) | `src/script/gen2/Vm.lua:961`, `:1799` | implemented | -| `blackoutmod` (cabin / Vermilion respawn) | `src/script/gen2/Vm.lua:1136` | implemented | -| `special HealParty` (bed, post-Stanly), `FadeOutToWhite/Black`, `ReloadSpritesNoPalettes`, `RestartMapMusic` | `src/script/gen2/Specials.lua:450`, `:998`, `:1025`, `:1059` | implemented | -| `specialphonecall SPECIALCALL_SSTICKET` | `src/script/gen2/Vm.lua:1364`; roster entry `src/core/gen2/Phone.lua:395` (`[5] = SPECIALCALL_SSTICKET`, condition `anywhere`) | implemented | -| CUT on the Vermilion tree (`TILESET_KANTO` `$35` -> `$4c`, anim 0) | `src/world/gen2/FieldMoves.lua:191-208`, `:242` | implemented | -| Hidden items (`PROTEIN`, `IRON`, `FULL_HEAL`) | `src/world/gen2/HiddenItems.lua:64`, `:112` | implemented | -| `ENGINE_FLYPOINT_VERMILION` from the map callback | `src/world/gen2/FieldMoves.lua:361` (`LANDMARK_VERMILION_CITY` -> `SPAWN_VERMILION`, flag 57) | implemented | -| `readvar VAR_BADGES` (Vermilion badge guy) | `src/world/gen2/World.lua:117`, `:1240` | implemented | -| `special SnorlaxAwake` | `src/script/gen2/Specials.lua:1562` | implemented | -| Water encounters / `FISHGROUP_OCEAN` at the ports | `src/battle/gen2/Encounter.lua` | not verified in this pass - no port-specific check was run | -| A driver that exercises the crossing end to end | `tests/drivers/gold_*.lua` | **missing** - no `gold_fastship_*`, `gold_vermilion_*` or `gold_surge_*` driver exists | -| Any hand-ported script for these 14 maps | - | **missing by design**; all behaviour comes from the extracted ROM script bytes | - -Note: the many `Vermilion*` hits under `src/world/OverworldController.lua` and -`src/world/FieldDefaults.lua` are the **Gen 1** (pokered) Vermilion Gym trash-can -puzzle. That puzzle does not exist in Gold, and none of that code is on the Gen 2 -path. - ---- - -## 6. Unresolved / verify by hand - -- **"cut down the tree blocking the gym" is wrong.** `maps/VermilionCity.blk` has - exactly one cuttable block, `$35` at block `(6,9)`, whose `CUT_TREE` quadrant is - cell `(13,18)`. The gym door is the `DOOR` quadrant of block `$12` at - `(5,9)` = cell `(10,19)`, and the tiles south of it (`(10,20)`, `(11,20)`, - `(12,20)`) are all `FLOOR`. Walking a straight path from the port entrance to - the gym door never touches the tree. Cutting it only opens cell `(13,18)`, which - is beside the hidden `FULL_HEAL` at `(12,19)`. Verified against - `data/tilesets/kanto_collision.asm` rows `$12`, `$35`, `$4c`, `$31`. -- **"Meal Coat"** in the walkthrough's item list is a typo for `METAL_COAT` - (`SSAquaMetalCoatAndDocking`). No `MEAL_*` item exists. -- **Lt. Surge's party order** in the walkthrough (Raichu, Magneton, Electabuzz, - Electrode, Electrode) does not match `data/trainers/parties.asm:289-296` - (Raichu, Electrode, Magneton, Electrode, Electabuzz). -- **Juggler Fritz's party order**: asm is Mr. Mime / Magmar / Machoke; the - walkthrough says Mr. Mime / Machoke / Magmar. -- **"Lt. Surge has a Full Restore"** - the party entry is `TRAINERTYPE_MOVES`, - which carries **no** held item. Gym-leader item use is AI behaviour - (`data/trainers/attributes.asm` / the trainer-class AI item list), which was not - opened in this pass. Verify before relying on it. -- **Prize money figures** ("You get: 1056G", "4600G", "???G" for Colin) are not - stored anywhere in the asm as literals - Gen 2 computes them from base money x - level. Not reproduced here. -- **EXP values** quoted per Pokemon are party- and level-dependent runtime - calculations, not table data. Not reproduced here. -- **`EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT_BUT_BAG_WAS_FULL`** is read by - `PokemonFanClubChairmanScript` but is never `setevent`'d anywhere in - `maps/PokemonFanClub.asm`. Either it is set elsewhere (nothing found by grep) or - it is dead in this revision; a full-bag player appears to have to sit through - the Rapidash speech again. -- **"Note the new Pokemon music for Kanto"**: the Vermilion maps use - `MUSIC_VERMILION_CITY` (`data/maps/maps.asm:296`). Whether that is the "Kanto - remix" the walkthrough means was not checked against the audio engine. -- The walkthrough's cabin-by-cabin narration ("two cabins to the right", "the - cabin next to yours") was reconciled against the 1F warp `x` positions - (north row `x` = 27/23/19/15, south row `x` = 15/19/23/27) and is consistent, - but the prose ordering of trainers within `FAST_SHIP_B1F` ("head left, then - down") was not re-walked tile by tile. diff --git a/docs/gold-walkthrough/asm-walk/section-20-saffron-city-gym.md b/docs/gold-walkthrough/asm-walk/section-20-saffron-city-gym.md deleted file mode 100644 index 086d72f8..00000000 --- a/docs/gold-walkthrough/asm-walk/section-20-saffron-city-gym.md +++ /dev/null @@ -1,680 +0,0 @@ -# Section 20 - Saffron City Gym - -Source: `../section-20-saffron-city-gym.txt` (the FAQ numbers this chapter "26 > Saffron City Gym") -Maps covered: `MAP_ROUTE_6`, `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE`, `MAP_ROUTE_6_SAFFRON_GATE`, `MAP_SAFFRON_CITY`, `MAP_MR_PSYCHICS_HOUSE`, `MAP_SILPH_CO_1F`, `MAP_SAFFRON_MAGNET_TRAIN_STATION`, `MAP_SAFFRON_MART`, `MAP_FIGHTING_DOJO`, `MAP_SAFFRON_GYM`, `MAP_SAFFRON_POKECENTER_1F` -Badges / key milestones in this section: MARSHBADGE (`ENGINE_MARSHBADGE`), TM29 Psychic, Up-Grade, Focus Band, `ENGINE_FLYPOINT_SAFFRON` unlocked on first entry to Saffron City. - -Map group / id pairs a bot needs (from `constants/map_constants.asm`): - -| Map constant | group | id | -|---|---|---| -| `MAP_ROUTE_6` | 12 (`VERMILION`) | 1 | -| `MAP_ROUTE_6_SAFFRON_GATE` | 12 | 12 | -| `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | 12 | 13 | -| `MAP_SAFFRON_CITY` | 25 (`SAFFRON`) | 2 | -| `MAP_FIGHTING_DOJO` | 25 | 3 | -| `MAP_SAFFRON_GYM` | 25 | 4 | -| `MAP_SAFFRON_MART` | 25 | 5 | -| `MAP_SAFFRON_POKECENTER_1F` | 25 | 6 | -| `MAP_MR_PSYCHICS_HOUSE` | 25 | 8 | -| `MAP_SAFFRON_MAGNET_TRAIN_STATION` | 25 | 9 | -| `MAP_SILPH_CO_1F` | 25 | 10 | - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_VERMILION_CITY` | `maps/VermilionCity.asm` (previous section) | - | north map connection | "Heal at the Pokémon Center, then head north onto Route 6" - Vermilion belongs to the previous section | -| 1 | `MAP_ROUTE_6` | `maps/Route6.asm` | south connection from Vermilion City | warp 2 `(6, 1)` -> `ROUTE_6_SAFFRON_GATE` warp 3 | "barren wasteland"; the Underground Path door is blocked | -| 1a | `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | `maps/Route6UndergroundPathEntrance.asm` | warp 1 `(17, 3)` on Route 6 | - | Walkthrough only notes it is blocked; do not enter this section | -| 2 | `MAP_ROUTE_6_SAFFRON_GATE` | `maps/Route6SaffronGate.asm` | warp 3/4 `(4, 7)` / `(5, 7)` | warp 1/2 `(4, 0)` / `(5, 0)` -> `SAFFRON_CITY` warps 12 / 13 | "make your way all the way up through the checkpoint house" | -| 3 | `MAP_SAFFRON_CITY` | `maps/SaffronCity.asm` | warp 12 `(16, 33)` / warp 13 `(17, 33)` | see below | Hub for the rest of the section; `MAPCALLBACK_NEWMAP` sets the fly point | -| 4 | `MAP_MR_PSYCHICS_HOUSE` | `maps/MrPsychicsHouse.asm` | Saffron warp 5 `(27, 29)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 5 | "Mr. Psychic ... gets you TM29 Psychic" | -| 5 | `MAP_SILPH_CO_1F` | `maps/SilphCo1F.asm` | Saffron warp 7 `(18, 21)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 7 | "Talk to the man blocking the pathway ... he'll give you Up-Grade" | -| 6 | `MAP_SAFFRON_MAGNET_TRAIN_STATION` | `maps/SaffronMagnetTrainStation.asm` | Saffron warp 6 `(8, 3)` | warp 1/2 `(8, 17)` / `(9, 17)` -> Saffron warp 6 | "On the northwest side of town is the Magnet Train ... currently closed" | -| 7 | `MAP_SAFFRON_MART` | `maps/SaffronMart.asm` | Saffron warp 3 `(25, 11)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 3 | "There's a PokeMart if you want to buy or sell items" | -| 8 | `MAP_FIGHTING_DOJO` | `maps/FightingDojo.asm` | Saffron warp 1 `(26, 3)` | warp 1/2 `(4, 11)` / `(5, 11)` -> Saffron warp 1 | "The former fighting gym has a Focus Band you can pick up" | -| 9 | `MAP_SAFFRON_GYM` | `maps/SaffronGym.asm` | Saffron warp 2 `(34, 3)` | warp 1/2 `(8, 17)` / `(9, 17)` -> Saffron warp 2 | "head to the gym on the right ... Sabrina" -> MARSHBADGE | -| 9a | `MAP_SAFFRON_POKECENTER_1F` | `maps/SaffronPokecenter1F.asm` | Saffron warp 4 `(9, 29)` | warp 1/2 `(3, 7)` / `(4, 7)` -> Saffron warp 4 | Optional mid-gym heal trip described in the walkthrough | - -Spillover: the walkthrough's opening line ("Heal at the Pokémon Center") refers to Vermilion City, and its closing text ends inside the Saffron Gym exit - nothing past Saffron is described. `MAP_COPYCATS_HOUSE_1F` (Saffron warp 8, `(9, 11)`) exists on this map but the walkthrough never enters it in this section. - -## 2. Maps - -### MAP_ROUTE_6 - -- Script: `maps/Route6.asm` -- Blocks: `maps/Route6.blk` (`data/maps/blocks.asm:562` `Route6_Blocks`) -- Header: `data/maps/maps.asm:294` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:271` `map_const ROUTE_6, 10, 9` (10x9 blocks = 20x18 cells) -- Connections: `data/maps/attributes.asm:346-348` - north `SaffronCity` (offset -5), south `VermilionCity` (offset -5). No east/west. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 3 | `ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | 1 | -| 2 | 6 | 1 | `ROUTE_6_SAFFRON_GATE` | 3 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 19 | 5 | `BGEVENT_READ` | `Route6UndergroundPathSign` (`Route6UndergroundPathSignText`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE6_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight field 2, unused) | `Route6PokefanMScript` (`4f:581a`) | `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | - -**Scripts of interest** - -- `Route6PokefanMScript` (`maps/Route6.asm:9`, sym `4f:581a`): a bare `jumptextfaceplayer Route6PokefanMText` - "The road is closed until the problem at the POWER PLANT is solved." No flag reads. He stands at `(17, 4)`, directly south of the Underground Path door at `(17, 3)`, so his body is the block, not a script check. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | `constants/event_flags.asm:1299` | set by `maps/PowerPlant.asm:164`; consumed as the object's event flag on `maps/Route6.asm:43` and `maps/Route5.asm:56` | Flag **set** hides the object (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32`). Clear (default) = the man is standing there = Underground Path shut | - -**Items** - none on this map. - -**Trainers** - none on this map. - -**Wild encounters** - -`data/wild/kanto_grass.asm:480` `def_grass_wildmons ROUTE_6`, encounter rates 10/10/10 percent (morn/day/nite). Gold block (`IF DEF(_GOLD)`, lines 483-506): - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L13 `PIDGEY` | L13 `PIDGEY` | L13 `ODDISH` | -| 2 | L13 `BELLSPROUT` | L13 `BELLSPROUT` | L14 `ODDISH` | -| 3 | L14 `PIDGEY` | L14 `PIDGEY` | L13 `BELLSPROUT` | -| 4 | L15 `MAGNEMITE` | L15 `MAGNEMITE` | L15 `MAGNEMITE` | -| 5 | L12 `ABRA` | L12 `ABRA` | L12 `ABRA` | -| 6 | L14 `ABRA` | L14 `ABRA` | L14 `ABRA` | -| 7 | L14 `ABRA` | L14 `ABRA` | L14 `ABRA` | - -Silver (`ELIF DEF(_SILVER)`, lines 508-531) swaps the third slot for `MEOWTH` (L14) morn/day and slots 2/3 for `MEOWTH`/`BELLSPROUT` at nite. - -Water: `data/wild/kanto_water.asm:12` `def_water_wildmons ROUTE_6`, 2 percent - L10 `PSYDUCK`, L5 `PSYDUCK`, L10 `GOLDUCK`. - -Fishing group is `FISHGROUP_POND` (`data/maps/maps.asm:294`). - -### MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE - -- Script: `maps/Route6UndergroundPathEntrance.asm` -- Blocks: shared `maps/UndergroundPathEntrance.blk` (`data/maps/blocks.asm:466`) -- Header: `data/maps/maps.asm:306` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:283` `map_const ROUTE_6_UNDERGROUND_PATH_ENTRANCE, 4, 4` -- Connections: none (indoor) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ROUTE_6` | 1 | -| 2 | 4 | 7 | `ROUTE_6` | 1 | -| 3 | 4 | 3 | `UNDERGROUND_PATH` | 2 | - -No coord events, no bg events, no object events. Nothing gates warp 3 from the inside - the only block is the NPC body on Route 6. - -### MAP_ROUTE_6_SAFFRON_GATE - -- Script: `maps/Route6SaffronGate.asm` -- Blocks: shared `maps/NorthSouthGate.blk` (`data/maps/blocks.asm:210,215`) -- Header: `data/maps/maps.asm:305` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:282` `map_const ROUTE_6_SAFFRON_GATE, 5, 4` -- Scene scripts: one entry, `scene_script Route6SaffronGateNoopScene` with no scene-id argument (`maps/Route6SaffronGate.asm:6`), so no `SCENE_*` constant is generated and the scene is a bare `end`. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `SAFFRON_CITY` | 12 | -| 2 | 5 | 0 | `SAFFRON_CITY` | 13 | -| 3 | 4 | 7 | `ROUTE_6` | 2 | -| 4 | 5 | 7 | `ROUTE_6` | 2 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE6SAFFRONGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route6SaffronGuardScript` (`59:523c`) | -1 (always) | - -**Scripts of interest** - -- `Route6SaffronGuardScript` (`maps/Route6SaffronGate.asm:13`, sym `59:523c`): `faceplayer` / `opentext` / `checkevent EVENT_RETURNED_MACHINE_PART` / `iftrue .ReturnedPart`. Before the Power Plant is fixed he explains the Magnet Train is dead; after, a one-liner. He stands at `(0, 4)`, off the walking lane - he never blocks passage. - -### MAP_SAFFRON_CITY - -- Script: `maps/SaffronCity.asm` -- Blocks: `maps/SaffronCity.blk` (`data/maps/blocks.asm:24`) -- Header: `data/maps/maps.asm:490` -> `TILESET_KANTO`, `TOWN`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:454` `map_const SAFFRON_CITY, 20, 18` (40x36 cells) -- Connections: `data/maps/attributes.asm:350-354` - north `Route5` (+5), south `Route6` (+5), west `Route7` (+9), east `Route8` (+9) -- Spawn / fly: `data/maps/spawn_points.asm:21` `spawn SAFFRON_CITY, 9, 30`; `data/maps/flypoints.asm:26` `db LANDMARK_SAFFRON_CITY, SPAWN_SAFFRON` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 26 | 3 | `FIGHTING_DOJO` | 1 | -| 2 | 34 | 3 | `SAFFRON_GYM` | 1 | -| 3 | 25 | 11 | `SAFFRON_MART` | 2 | -| 4 | 9 | 29 | `SAFFRON_POKECENTER_1F` | 1 | -| 5 | 27 | 29 | `MR_PSYCHICS_HOUSE` | 1 | -| 6 | 8 | 3 | `SAFFRON_MAGNET_TRAIN_STATION` | 2 | -| 7 | 18 | 21 | `SILPH_CO_1F` | 1 | -| 8 | 9 | 11 | `COPYCATS_HOUSE_1F` | 1 | -| 9 | 18 | 3 | `ROUTE_5_SAFFRON_GATE` | 3 | -| 10 | 0 | 24 | `ROUTE_7_SAFFRON_GATE` | 3 | -| 11 | 0 | 25 | `ROUTE_7_SAFFRON_GATE` | 4 | -| 12 | 16 | 33 | `ROUTE_6_SAFFRON_GATE` | 1 | -| 13 | 17 | 33 | `ROUTE_6_SAFFRON_GATE` | 2 | -| 14 | 39 | 22 | `ROUTE_8_SAFFRON_GATE` | 1 | -| 15 | 39 | 23 | `ROUTE_8_SAFFRON_GATE` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 21 | 5 | `BGEVENT_READ` | `SaffronCitySign` | -| 33 | 5 | `BGEVENT_READ` | `SaffronGymSign` | -| 25 | 5 | `BGEVENT_READ` | `FightingDojoSign` | -| 15 | 21 | `BGEVENT_READ` | `SilphCoSign` | -| 25 | 29 | `BGEVENT_READ` | `MrPsychicsHouseSign` | -| 11 | 5 | `BGEVENT_READ` | `SaffronCityMagnetTrainStationSign` | -| 10 | 29 | `BGEVENT_READ` | `SaffronCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 26 | 11 | `BGEVENT_READ` | `SaffronCityMartSign` (`jumpstd MartSignScript`) | - -No hidden items on this map. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SAFFRONCITY_LASS1` | `SPRITE_LASS` | 7 | 14 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityLass1Script` | -1 | -| `SAFFRONCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 30 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityPokefanMScript` | -1 | -| `SAFFRONCITY_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 32 | 7 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityCooltrainerMScript` | -1 | -| `SAFFRONCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 20 | 24 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityCooltrainerFScript` | -1 | -| `SAFFRONCITY_FISHER` | `SPRITE_FISHER` | 27 | 12 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronCityFisherScript` | -1 | -| `SAFFRONCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 15 | 19 | `SPRITEMOVEDATA_WALK_UP_DOWN` (ry 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityYoungster1Script` | -1 | -| `SAFFRONCITY_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 35 | 22 | `SPRITEMOVEDATA_WANDER` (rx 1, ry 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityYoungster2Script` | -1 | -| `SAFFRONCITY_LASS2` | `SPRITE_LASS` | 19 | 8 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronCityLass2Script` | -1 | - -Every Saffron City NPC is `OBJECTTYPE_SCRIPT`; none of them blocks a warp tile. - -**Scripts of interest** - -- `SaffronCityFlypointCallback` (`maps/SaffronCity.asm:17`), registered as `callback MAPCALLBACK_NEWMAP`: `setflag ENGINE_FLYPOINT_SAFFRON` / `endcallback`. Walking in from the Route 6 gate is what unlocks Fly to Saffron - there is no NPC or item involved. -- `SaffronCityLass1Script`, `SaffronCityPokefanMScript`, `SaffronCityFisherScript` all branch on `checkevent EVENT_RETURNED_MACHINE_PART`. Purely dialogue; useful only as a state probe. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_SAFFRON` | `constants/engine_flags.asm:74` | set by `SaffronCityFlypointCallback` | Fly destination unlocked; also the `SPAWN_SAFFRON` blackout target once the player has been here | -| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | set in `maps/PowerPlant.asm:161`; read by four Saffron scripts and `Route6SaffronGuardScript` | Not set in this section. Its absence is what keeps the Magnet Train shut | - -**Items** - none directly on the city map. - -**Trainers** - none. - -**Wild encounters** - none. `SAFFRON_CITY` has no entry in `data/wild/kanto_grass.asm`, `kanto_water.asm`, `fish.asm` or `treemons.asm` (grep over `data/wild/` for `SAFFRON` returns nothing). - -### MAP_MR_PSYCHICS_HOUSE - -- Script: `maps/MrPsychicsHouse.asm` -- Blocks: shared `maps/House1.blk` (`data/maps/blocks.asm:195,201`) -- Header: `data/maps/maps.asm:496` -> `TILESET_HOUSE`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:460` `map_const MR_PSYCHICS_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `SAFFRON_CITY` | 5 | -| 2 | 3 | 7 | `SAFFRON_CITY` | 5 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `MrPsychicsHouseBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `MrPsychicsHouseBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MRPSYCHICSHOUSE_FISHING_GURU` | `SPRITE_FISHING_GURU` | 5 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `MrPsychic` (`61:4b15`) | -1 | - -**Scripts of interest** - -- `MrPsychic` (`maps/MrPsychicsHouse.asm:9`, sym `61:4b15`): - `faceplayer` / `opentext` / `checkevent EVENT_GOT_TM29_PSYCHIC` / `iftrue .AlreadyGotItem` / `writetext MrPsychicText1` / `promptbutton` / `verbosegiveitem TM_PSYCHIC_M` / `iffalse .Done` / `setevent EVENT_GOT_TM29_PSYCHIC` / `.AlreadyGotItem: writetext MrPsychicText2` / `waitbutton` / `.Done: closetext` / `end`. - One `verbosegiveitem`; a full bag drops through `.Done` **without** setting the flag, so a bot must re-talk after making room. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_TM29_PSYCHIC` | `constants/event_flags.asm:226` | read+set by `MrPsychic` | One-shot guard on TM29 | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_PSYCHIC_M` (TM29, `constants/item_constants.asm:250` `add_tm PSYCHIC_M ; dd`) | talk to `MRPSYCHICSHOUSE_FISHING_GURU` at `(5, 3)` | `MrPsychic`, `verbosegiveitem` | `EVENT_GOT_TM29_PSYCHIC` | - -### MAP_SILPH_CO_1F - -- Script: `maps/SilphCo1F.asm` -- Blocks: `maps/SilphCo1F.blk` (`data/maps/blocks.asm:874`) -- Header: `data/maps/maps.asm:498` -> `TILESET_FACILITY`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:462` `map_const SILPH_CO_1F, 8, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `SAFFRON_CITY` | 7 | -| 2 | 3 | 7 | `SAFFRON_CITY` | 7 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILPHCO1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 4 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SilphCoReceptionistScript` | -1 | -| `SILPHCO1F_OFFICER` | `SPRITE_OFFICER` | 13 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SilphCoOfficerScript` (`61:4f81`) | -1 | - -**Scripts of interest** - -- `SilphCoOfficerScript` (`maps/SilphCo1F.asm:13`, sym `61:4f81`): `faceplayer` / `opentext` / `checkevent EVENT_GOT_UP_GRADE` / `iftrue .GotUpGrade` / `writetext SilphCoOfficerText` / `promptbutton` / `verbosegiveitem UP_GRADE` / `iffalse .NoRoom` / `setevent EVENT_GOT_UP_GRADE` / `.GotUpGrade: writetext ...` / `waitbutton` / `.NoRoom: closetext` / `end`. Same full-bag shape as Mr. Psychic. There is no upstairs on this map at all - the "man blocking the stairway" is `(13, 1)`, standing on the only path north; nothing scripted opens it. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_UP_GRADE` | `constants/event_flags.asm:221` | read+set by `SilphCoOfficerScript` | One-shot guard on Up-Grade | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `UP_GRADE` (`constants/item_constants.asm:180`, `$ac`) | talk to `SILPHCO1F_OFFICER` at `(13, 1)` | `SilphCoOfficerScript`, `verbosegiveitem` | `EVENT_GOT_UP_GRADE` | - -### MAP_SAFFRON_MAGNET_TRAIN_STATION - -- Script: `maps/SaffronMagnetTrainStation.asm` -- Blocks: `maps/SaffronMagnetTrainStation.blk` -- Header: `data/maps/maps.asm:497` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:461` `map_const SAFFRON_MAGNET_TRAIN_STATION, 10, 9` -- Scene variable: `data/maps/scenes.asm:17` `scene_var SAFFRON_MAGNET_TRAIN_STATION, wSaffronMagnetTrainStationSceneID` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 8 | 17 | `SAFFRON_CITY` | 6 | -| 2 | 9 | 17 | `SAFFRON_CITY` | 6 | -| 3 | 6 | 5 | `GOLDENROD_MAGNET_TRAIN_STATION` | 4 | -| 4 | 11 | 5 | `GOLDENROD_MAGNET_TRAIN_STATION` | 3 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_SAFFRONMAGNETTRAINSTATION_ARRIVE_FROM_GOLDENROD` (= 0, generated by the `scene_script` macro at `maps/SaffronMagnetTrainStation.asm:9`) | 11 | 6 | `Script_ArriveFromGoldenrod` | Arrival cutscene when riding in from Goldenrod; not reachable in this section | - -**BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SAFFRONMAGNETTRAINSTATION_OFFICER` | `SPRITE_OFFICER` | 9 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationOfficerScript` (`61:4bc2`) | -1 | -| `SAFFRONMAGNETTRAINSTATION_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 10 | 14 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationGymGuideScript` | -1 | -| `SAFFRONMAGNETTRAINSTATION_TEACHER` | `SPRITE_TEACHER` | 6 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationTeacherScript` | `EVENT_SAFFRON_TRAIN_STATION_POPULATION` | -| `SAFFRONMAGNETTRAINSTATION_LASS` | `SPRITE_LASS` | 6 | 10 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationLassScript` | `EVENT_SAFFRON_TRAIN_STATION_POPULATION` | - -**Scripts of interest** - -- `SaffronMagnetTrainStationOfficerScript` (`maps/SaffronMagnetTrainStation.asm:16`, sym `61:4bc2`): `checkevent EVENT_RESTORED_POWER_TO_KANTO` / `iftrue .MagnetTrainToGoldenrod`, else "the MAGNET TRAIN isn't operating now" and `end`. The ride arm additionally runs `yesorno`, `checkitem PASS`, two `applymovement`s, `setval TRUE` / `special MagnetTrain` / `warpcheck` / `newloadmap MAPSETUP_TRAIN`. In this section both `EVENT_RESTORED_POWER_TO_KANTO` and `PASS` are missing, so the officer is a dead end. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RESTORED_POWER_TO_KANTO` | `constants/event_flags.asm:204` | set at `maps/PowerPlant.asm:165`; read by the officer script | Gate on the Magnet Train being operable at all | -| `EVENT_SAFFRON_TRAIN_STATION_POPULATION` | `constants/event_flags.asm:1300` | cleared at `maps/PowerPlant.asm:160` | While **set** (the state in this section) the Teacher and Lass are hidden - the station looks empty on purpose | - -### MAP_SAFFRON_MART - -- Script: `maps/SaffronMart.asm` -- Blocks: shared `maps/Mart.blk` (`data/maps/blocks.asm:333,335`) -- Header: `data/maps/maps.asm:493` -> `TILESET_MART`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:457` `map_const SAFFRON_MART, 6, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `SAFFRON_CITY` | 3 | -| 2 | 3 | 7 | `SAFFRON_CITY` | 3 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SAFFRONMART_CLERK` | `SPRITE_CLERK` | 1 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `SaffronMartClerkScript` | -1 | -| `SAFFRONMART_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 2 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronMartCooltrainerMScript` | -1 | -| `SAFFRONMART_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 1) | `OBJECTTYPE_SCRIPT` | `SaffronMartCooltrainerFScript` | -1 | - -**Scripts of interest** - -- `SaffronMartClerkScript`: `opentext` / `pokemart MARTTYPE_STANDARD, MART_SAFFRON` / `closetext` / `end`. - -**Items** - shop stock, `data/items/marts.asm:362` `MartSaffron`: `GREAT_BALL`, `ULTRA_BALL`, `HYPER_POTION`, `MAX_POTION`, `FULL_HEAL`, `X_ATTACK`, `X_DEFEND`, `FLOWER_MAIL`. - -### MAP_FIGHTING_DOJO - -- Script: `maps/FightingDojo.asm` -- Blocks: `maps/FightingDojo.blk` (`data/maps/blocks.asm:871`) -- Header: `data/maps/maps.asm:491` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:455` `map_const FIGHTING_DOJO, 5, 6` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 11 | `SAFFRON_CITY` | 1 | -| 2 | 5 | 11 | `SAFFRON_CITY` | 1 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 0 | `BGEVENT_READ` | `FightingDojoSign1` | -| 5 | 0 | `BGEVENT_READ` | `FightingDojoSign2` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `FIGHTINGDOJO_BLACK_BELT` | `SPRITE_BLACK_BELT` | 4 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `FightingDojoBlackBelt` | -1 | -| `FIGHTINGDOJO_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `FightingDojoFocusBand` (`61:400b`) | `EVENT_PICKED_UP_FOCUS_BAND` | - -**Scripts of interest** - -- `FightingDojoFocusBand` (`maps/FightingDojo.asm:19`, sym `61:400b`): the two-byte `itemball FOCUS_BAND` struct (macro at `macros/scripts/maps.asm:154`, quantity defaults to 1), not a script. The engine's own item-ball path runs it. -- There are **no trainers** in the Fighting Dojo in Gen 2. `FightingDojoBlackBeltText` explains the Karate King is away in a Johto cave (Rock Tunnel-era content elsewhere), so nothing here fights back. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `FOCUS_BAND` (`constants/item_constants.asm:127`, `$77`) | walk onto / press A at `(3, 1)` | `FightingDojoFocusBand` (`OBJECTTYPE_ITEMBALL`) | `EVENT_PICKED_UP_FOCUS_BAND` (`constants/event_flags.asm:1312`) - once set the ball object is masked | - -### MAP_SAFFRON_GYM - -- Script: `maps/SaffronGym.asm` -- Blocks: `maps/SaffronGym.blk` (`data/maps/blocks.asm:861`) -- Header: `data/maps/maps.asm:492` -> `TILESET_UNDERGROUND`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_GYM`, phone `TRUE` (phone calls suppressed), `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:456` `map_const SAFFRON_GYM, 10, 9` (20x18 cells) -- Connections: none - -**Warps** (`def_warp_events`, `maps/SaffronGym.asm:294-326`) - warps 1-2 are the street doors; warps 3-32 are the 30 teleport pads. Every pad's destination is `SAFFRON_GYM` and the player lands on the destination warp's own tile. - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 8 | 17 | `SAFFRON_CITY` | 2 | -| 2 | 9 | 17 | `SAFFRON_CITY` | 2 | -| 3 | 11 | 15 | `SAFFRON_GYM` | 18 | -| 4 | 19 | 15 | `SAFFRON_GYM` | 19 | -| 5 | 19 | 11 | `SAFFRON_GYM` | 20 | -| 6 | 1 | 11 | `SAFFRON_GYM` | 21 | -| 7 | 5 | 3 | `SAFFRON_GYM` | 22 | -| 8 | 11 | 5 | `SAFFRON_GYM` | 23 | -| 9 | 1 | 15 | `SAFFRON_GYM` | 24 | -| 10 | 19 | 3 | `SAFFRON_GYM` | 25 | -| 11 | 15 | 17 | `SAFFRON_GYM` | 26 | -| 12 | 5 | 17 | `SAFFRON_GYM` | 27 | -| 13 | 5 | 9 | `SAFFRON_GYM` | 28 | -| 14 | 9 | 3 | `SAFFRON_GYM` | 29 | -| 15 | 15 | 9 | `SAFFRON_GYM` | 30 | -| 16 | 15 | 5 | `SAFFRON_GYM` | 31 | -| 17 | 1 | 5 | `SAFFRON_GYM` | 32 | -| 18 | 19 | 17 | `SAFFRON_GYM` | 3 | -| 19 | 19 | 9 | `SAFFRON_GYM` | 4 | -| 20 | 1 | 9 | `SAFFRON_GYM` | 5 | -| 21 | 5 | 5 | `SAFFRON_GYM` | 6 | -| 22 | 11 | 3 | `SAFFRON_GYM` | 7 | -| 23 | 1 | 17 | `SAFFRON_GYM` | 8 | -| 24 | 19 | 5 | `SAFFRON_GYM` | 9 | -| 25 | 15 | 15 | `SAFFRON_GYM` | 10 | -| 26 | 5 | 15 | `SAFFRON_GYM` | 11 | -| 27 | 5 | 11 | `SAFFRON_GYM` | 12 | -| 28 | 9 | 5 | `SAFFRON_GYM` | 13 | -| 29 | 15 | 11 | `SAFFRON_GYM` | 14 | -| 30 | 15 | 3 | `SAFFRON_GYM` | 15 | -| 31 | 1 | 3 | `SAFFRON_GYM` | 16 | -| 32 | 11 | 9 | `SAFFRON_GYM` | 17 | - -**Teleporter graph, resolved to landing coordinates.** Every pair is mutual, so the graph is 15 undirected edges. Room labels are derived from the pad clusters and the object coordinates; they are a reading aid, the coordinates are the ground truth. - -| room (occupant) | pads (x, y) | -|---|---| -| ENTRANCE (doors `(8,17)`/`(9,17)`, guide `(9,14)`, statue `(8,15)`) | `(11,15)` | -| BL (Psychic Franklin `(3,16)`) | `(1,15)` `(5,15)` `(1,17)` `(5,17)` | -| BR (Medium Rebecca `(17,16)`) | `(15,15)` `(19,15)` `(15,17)` `(19,17)` | -| ML | `(1,9)` `(5,9)` `(1,11)` `(5,11)` | -| CENTER (Sabrina `(9,8)`) | `(11,9)` | -| MR | `(15,9)` `(19,9)` `(15,11)` `(19,11)` | -| TL (Medium Doris `(3,4)`) | `(1,3)` `(5,3)` `(1,5)` `(5,5)` | -| TC | `(9,3)` `(11,3)` `(9,5)` `(11,5)` | -| TR (Psychic Jared `(17,4)`) | `(15,3)` `(19,3)` `(15,5)` `(19,5)` | - -| step on pad | warp idx | lands at | room reached | -|---|---|---|---| -| `(11,15)` ENTRANCE | 3 | `(19,17)` | BR | -| `(19,17)` BR | 18 | `(11,15)` | ENTRANCE | -| `(19,15)` BR | 4 | `(19,9)` | MR | -| `(19,9)` MR | 19 | `(19,15)` | BR | -| `(15,17)` BR | 11 | `(5,15)` | BL | -| `(5,15)` BL | 26 | `(15,17)` | BR | -| `(15,15)` BR | 25 | `(19,3)` | TR | -| `(19,3)` TR | 10 | `(15,15)` | BR | -| `(1,15)` BL | 9 | `(19,5)` | TR | -| `(19,5)` TR | 24 | `(1,15)` | BL | -| `(1,17)` BL | 23 | `(11,5)` | TC | -| `(11,5)` TC | 8 | `(1,17)` | BL | -| `(5,17)` BL | 12 | `(5,11)` | ML | -| `(5,11)` ML | 27 | `(5,17)` | BL | -| `(1,9)` ML | 20 | `(19,11)` | MR | -| `(19,11)` MR | 5 | `(1,9)` | ML | -| `(5,9)` ML | 13 | `(9,5)` | TC | -| `(9,5)` TC | 28 | `(5,9)` | ML | -| `(1,11)` ML | 6 | `(5,5)` | TL | -| `(5,5)` TL | 21 | `(1,11)` | ML | -| `(15,9)` MR | 15 | `(15,3)` | TR | -| `(15,3)` TR | 30 | `(15,9)` | MR | -| `(15,11)` MR | 29 | `(9,3)` | TC | -| `(9,3)` TC | 14 | `(15,11)` | MR | -| `(5,3)` TL | 7 | `(11,3)` | TC | -| `(11,3)` TC | 22 | `(5,3)` | TL | -| `(1,3)` TL | 31 | `(15,5)` | TR | -| `(15,5)` TR | 16 | `(1,3)` | TL | -| **`(1,5)` TL** | **17** | **`(11,9)`** | **CENTER (Sabrina)** | -| **`(11,9)` CENTER** | **32** | **`(1,5)`** | **TL** | - -`(1,5)` in the top-left room is the only pad that reaches Sabrina, and `(11,9)` is the only way out of her chamber. This matches the walkthrough's "go to the bottom left teleporter and you'll go straight to the gym leader". - -**Coord events** - none. The gym has no trip-wires; every transition is a warp tile. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 8 | 15 | `BGEVENT_READ` | `SaffronGymStatue` (`61:4170`) | - -**Object events** - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `SAFFRONGYM_SABRINA` | `SPRITE_SABRINA` | 9 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SaffronGymSabrinaScript` (`61:40cf`) | -1 | -| `SAFFRONGYM_GRANNY1` | `SPRITE_GRANNY` | 17 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerMediumRebecca` (`61:410c`) | -1 | -| `SAFFRONGYM_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 3 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPsychicFranklin` (`61:4120`) | -1 | -| `SAFFRONGYM_GRANNY2` | `SPRITE_GRANNY` | 3 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerMediumDoris` (`61:4134`) | -1 | -| `SAFFRONGYM_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 17 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerPsychicJared` (`61:4148`) | -1 | -| `SAFFRONGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 9 | 14 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SaffronGymGuideScript` | -1 | - -All four trainers spin (`SPINRANDOM_FAST`), so their line of sight rotates - a bot cannot assume a fixed approach lane; walking into the room will trigger them. - -**Scripts of interest** - -- `SaffronGymSabrinaScript` (`maps/SaffronGym.asm:14`, sym `61:40cf`): - `faceplayer` / `opentext` / `checkflag ENGINE_MARSHBADGE` / `iftrue .FightDone` / `writetext SabrinaIntroText` / `waitbutton` / `closetext` / `winlosstext SabrinaWinLossText, 0` / `loadtrainer SABRINA, SABRINA1` / `startbattle` / `reloadmapafterbattle` / then a burst of five `setevent`s: `EVENT_BEAT_SABRINA`, `EVENT_BEAT_MEDIUM_REBECCA`, `EVENT_BEAT_MEDIUM_DORIS`, `EVENT_BEAT_PSYCHIC_FRANKLIN`, `EVENT_BEAT_PSYCHIC_JARED` / `opentext` / `writetext ReceivedMarshBadgeText` / `playsound SFX_GET_BADGE` / `waitsfx` / `setflag ENGINE_MARSHBADGE` / `writetext SabrinaMarshBadgeText` / `waitbutton` / `closetext` / `end`. - Two consequences a bot should exploit: **the four gym trainers are auto-cleared when Sabrina falls**, so they are entirely skippable; and the badge is a `setflag` on `ENGINE_MARSHBADGE`, which is also the re-entry guard (`.FightDone`). -- `TrainerMediumRebecca` / `TrainerPsychicFranklin` / `TrainerMediumDoris` / `TrainerPsychicJared` (`maps/SaffronGym.asm:47,58,69,80`): each is a `trainer CLASS, ID, EVENT_BEAT_*, SeenText, BeatenText, 0, .Script` struct (`macros/scripts/maps.asm:142`; field order is group, id, flag, seen, win, loss, after) followed by an `endifjustbattled` / `opentext` / `writetext ...AfterBattleText` / `waitbutton` / `closetext` / `end` after-battle script. -- `SaffronGymStatue` (`maps/SaffronGym.asm:107`, sym `61:4170`): `checkflag ENGINE_MARSHBADGE` / `iftrue .Beaten` / `jumpstd GymStatue1Script`; the beaten arm does `gettrainername STRING_BUFFER_4, SABRINA, SABRINA1` then `jumpstd GymStatue2Script`. Handy as a cheap post-badge assertion. -- `SaffronGymGuideScript` (`maps/SaffronGym.asm:91`): branches on `checkevent EVENT_BEAT_SABRINA`; no items, no flags written. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_MARSHBADGE` | `constants/engine_flags.asm:52` (bit 5 of `wKantoBadges`; Kanto order is Boulder, Cascade, Thunder, Rainbow, Soul, **Marsh**, Volcano, Earth) | `checkflag` in `SaffronGymSabrinaScript` and `SaffronGymStatue`; `setflag` after the win | The section's terminal postcondition | -| `EVENT_BEAT_SABRINA` | `constants/event_flags.asm:720` | set by `SaffronGymSabrinaScript`; read by `SaffronGymGuideScript` | Leader defeated | -| `EVENT_BEAT_MEDIUM_REBECCA` | `constants/event_flags.asm:935` | trainer struct flag; also force-set by the Sabrina script | Rebecca cleared | -| `EVENT_BEAT_MEDIUM_DORIS` | `constants/event_flags.asm:936` | as above | Doris cleared | -| `EVENT_BEAT_PSYCHIC_FRANKLIN` | `constants/event_flags.asm:564` | as above | Franklin cleared | -| `EVENT_BEAT_PSYCHIC_JARED` | `constants/event_flags.asm:573` | as above | Jared cleared | - -**Items** - none in the gym. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SABRINA` / `SABRINA1` | `SABRINA` (`constants/trainer_constants.asm:309`, class 23) | 1 | `SabrinaGroup` `; SABRINA (1)` (`data/trainers/parties.asm:1400-1406`), `TRAINERTYPE_MOVES`: L46 `ESPEON` (Sand-Attack, Quick Attack, Swift, Psychic), L46 `MR__MIME` (Barrier, Reflect, Baton Pass, Psychic), L48 `ALAKAZAM` (Recover, Future Sight, Psychic, Reflect) | `SaffronGymSabrinaScript` | none; leader (`data/trainers/leaders.asm:29`) | -| `MEDIUM` / `REBECCA` | `MEDIUM` | 6 (`constants/trainer_constants.asm:590`) | `MediumGroup` `; MEDIUM (6)`: L35 `DROWZEE`, L35 `HYPNO` | `TrainerMediumRebecca` | none | -| `PSYCHIC_T` / `FRANKLIN` | `PSYCHIC_T` (class 34) | 2 (`constants/trainer_constants.asm:513`) | `PsychicGroup` `; PSYCHIC_T (2)` (`data/trainers/parties.asm:2516`): L37 `KADABRA` | `TrainerPsychicFranklin` | none | -| `MEDIUM` / `DORIS` | `MEDIUM` | 7 (`constants/trainer_constants.asm:591`) | `MediumGroup` `; MEDIUM (7)`: L34 `SLOWPOKE`, L36 `SLOWBRO` | `TrainerMediumDoris` | none | -| `PSYCHIC_T` / `JARED` | `PSYCHIC_T` | 11 (`constants/trainer_constants.asm:522`) | `PsychicGroup` `; PSYCHIC_T (11)` (`data/trainers/parties.asm:2569`): L32 `MR__MIME`, L32 `EXEGGCUTE`, **L35** `EXEGGCUTE` | `TrainerPsychicJared` | none | - -Prize money: `ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) is `base reward x last mon's level`, and `engine/battle/core.asm:2344-2358` pays that quarter out **four times**, so the visible payout is `4 x base x level`. Base rewards: Sabrina 25 (`data/trainers/attributes.asm:211`), Medium 10 (`:343`), Psychic T 8 (`:313`). That reproduces the walkthrough's 4800G / 1400G / 1440G / 1184G / 1120G exactly. Sabrina also carries a `HYPER_POTION` as an AI item (`data/trainers/attributes.asm:210`). - -**Wild encounters** - none (indoor gym). - -### MAP_SAFFRON_POKECENTER_1F - -- Script: `maps/SaffronPokecenter1F.asm` -- Blocks: shared `maps/Pokecenter1F.blk` (`data/maps/blocks.asm:362,364`) -- Header: `data/maps/maps.asm:494` -> `TILESET_POKECENTER`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:458` `map_const SAFFRON_POKECENTER_1F, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `SAFFRON_CITY` | 4 | -| 2 | 4 | 7 | `SAFFRON_CITY` | 4 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** (nurse only listed; the rest are flavour) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SAFFRONPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 | -| `SAFFRONPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 7 | 2 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FTeacherScript` | -1 | -| `SAFFRONPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 8 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FFisherScript` | -1 | -| `SAFFRONPOKECENTER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 1 | 4 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FYoungsterScript` | -1 | - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Underground Path (Route 6 warp 1 at `(17, 3)`) | `maps/Route6.asm:43` - `ROUTE6_POKEFAN_M` object standing at `(17, 4)` with event flag `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`; visibility decided by `CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32` | Nothing the player can do in this section | `maps/PowerPlant.asm:164` `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` (flag set = object masked), which is part of returning the `MACHINE_PART` | -| Magnet Train Saffron -> Goldenrod | `maps/SaffronMagnetTrainStation.asm:19` `checkevent EVENT_RESTORED_POWER_TO_KANTO`, then `checkitem PASS` at `:30` | `EVENT_RESTORED_POWER_TO_KANTO` **and** `PASS` in bag | `maps/PowerPlant.asm:165` sets the event; the `PASS` comes from elsewhere. Both false here, so the officer only prints "isn't operating now" | -| Silph Co. upper floors | `maps/SilphCo1F.asm:69` - `SILPHCO1F_OFFICER` object at `(13, 1)` with event flag `-1` (never hides). No stairs warp exists on `SilphCo1F` at all | Permanently blocked in Gen 2 | Never; this is not a gate that opens | -| Saffron Gym maze | The 30 pad warps at `maps/SaffronGym.asm:296-326`. Pads are teleport panels (`COLL_WARP_PANEL`-class tiles), so they fire on landing, not on a directional press | Navigation only - no badge, item or flag check anywhere in `SaffronGym.asm` | n/a. The single route to Sabrina is `(11,15) -> (19,17) -> ... -> (1,5) -> (11,9)`; see the pad table | -| MARSHBADGE re-fight guard | `maps/SaffronGym.asm:17` `checkflag ENGINE_MARSHBADGE` / `iftrue .FightDone` | Badge not yet held | Beating Sabrina once; afterwards she only talks | -| Gym trainers | Each `trainer` struct's `EVENT_BEAT_*` flag; the trainer only challenges while its flag is clear | Walking into their sight cone (sight 3 for Rebecca/Franklin, 2 for Doris/Jared) | Beating them, **or** beating Sabrina, which sets all four flags at `maps/SaffronGym.asm:27-30` | - -No HM field move is required anywhere in this section - no `engine/overworld/cut.asm`, `surf.asm`, `strength.asm` or `whirlpool.asm` check is reachable from these maps. The Bicycle the walkthrough recommends is convenience only. - -## 4. Bot checklist - -Coordinates are asm cell coordinates on the named map. "Warp N" means the `def_warp_events` index in that map's table. - -1. `MAP_ROUTE_6` - walk north along the route to warp 2 at `(6, 1)`. Precondition: none. Postcondition: on `MAP_ROUTE_6_SAFFRON_GATE` warp 3 `(4, 7)`. - - Optional: read the sign at `(19, 5)`. Do **not** try warp 1 at `(17, 3)`; `ROUTE6_POKEFAN_M` occupies `(17, 4)` while `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` is clear. -2. `MAP_ROUTE_6_SAFFRON_GATE` - walk from `(4, 7)` to warp 1 at `(4, 0)`. Postcondition: on `MAP_SAFFRON_CITY` warp 12 `(16, 33)`; `MAPCALLBACK_NEWMAP` fires and `ENGINE_FLYPOINT_SAFFRON` becomes set. -3. `MAP_SAFFRON_CITY` - walk to warp 5 at `(27, 29)`. Enter `MAP_MR_PSYCHICS_HOUSE`. -4. `MAP_MR_PSYCHICS_HOUSE` - talk to `MRPSYCHICSHOUSE_FISHING_GURU` at `(5, 3)` (approach from `(4, 3)` facing right, he faces left). Precondition: `EVENT_GOT_TM29_PSYCHIC` clear and TM pocket has room. Postcondition: `TM_PSYCHIC_M` in bag, `EVENT_GOT_TM29_PSYCHIC` set. Exit warp 1 `(2, 7)`. -5. `MAP_SAFFRON_CITY` - walk to warp 7 at `(18, 21)`. Enter `MAP_SILPH_CO_1F`. -6. `MAP_SILPH_CO_1F` - talk to `SILPHCO1F_OFFICER` at `(13, 1)`. Precondition: `EVENT_GOT_UP_GRADE` clear, item pocket has room. Postcondition: `UP_GRADE` in bag, `EVENT_GOT_UP_GRADE` set. Exit warp 1 `(2, 7)`. -7. (Optional, dialogue only) `MAP_SAFFRON_CITY` warp 6 at `(8, 3)` -> `MAP_SAFFRON_MAGNET_TRAIN_STATION`; talk to `SAFFRONMAGNETTRAINSTATION_OFFICER` at `(9, 9)`. Expect the "isn't operating now" arm because `EVENT_RESTORED_POWER_TO_KANTO` is clear. Exit warp 1 `(8, 17)`. -8. (Optional) `MAP_SAFFRON_CITY` warp 3 at `(25, 11)` -> `MAP_SAFFRON_MART`; talk to `SAFFRONMART_CLERK` at `(1, 3)` for `MART_SAFFRON`. Exit warp 1 `(2, 7)`. -9. `MAP_SAFFRON_CITY` - warp 1 at `(26, 3)` -> `MAP_FIGHTING_DOJO`. Walk onto / face `FIGHTINGDOJO_POKE_BALL` at `(3, 1)`. Precondition: `EVENT_PICKED_UP_FOCUS_BAND` clear. Postcondition: `FOCUS_BAND` in bag, `EVENT_PICKED_UP_FOCUS_BAND` set (ball object masked from then on). Exit warp 1 `(4, 11)`. -10. (Optional heal) `MAP_SAFFRON_CITY` warp 4 at `(9, 29)` -> `MAP_SAFFRON_POKECENTER_1F`; talk to nurse at `(3, 1)`. Exit warp 1 `(3, 7)`. -11. `MAP_SAFFRON_CITY` - warp 2 at `(34, 3)` -> `MAP_SAFFRON_GYM`, landing on warp 1 `(8, 17)`. -12. `MAP_SAFFRON_GYM` - walk to the pad at `(11, 15)`. Warps to `(19, 17)`. -13. `MAP_SAFFRON_GYM` BR room - battle `SAFFRONGYM_GRANNY1` (Medium Rebecca) at `(17, 16)`, sight 3. Precondition: `EVENT_BEAT_MEDIUM_REBECCA` clear. Postcondition: that flag set. (Skippable: her flag is force-set by the Sabrina script.) -14. `MAP_SAFFRON_GYM` - step on pad `(15, 17)`. Warps to `(5, 15)`. -15. `MAP_SAFFRON_GYM` BL room - battle `SAFFRONGYM_YOUNGSTER1` (Psychic Franklin) at `(3, 16)`, sight 3. Postcondition: `EVENT_BEAT_PSYCHIC_FRANKLIN` set. -16. `MAP_SAFFRON_GYM` - step on pad `(1, 15)`. Warps to `(19, 5)`. -17. `MAP_SAFFRON_GYM` TR room - battle `SAFFRONGYM_YOUNGSTER2` (Psychic Jared) at `(17, 4)`, sight 2. Postcondition: `EVENT_BEAT_PSYCHIC_JARED` set. -18. `MAP_SAFFRON_GYM` - step on pad `(15, 5)`. Warps to `(1, 3)`. -19. `MAP_SAFFRON_GYM` TL room - battle `SAFFRONGYM_GRANNY2` (Medium Doris) at `(3, 4)`, sight 2. Postcondition: `EVENT_BEAT_MEDIUM_DORIS` set. -20. `MAP_SAFFRON_GYM` - step on pad `(1, 5)`. Warps to `(11, 9)`, Sabrina's chamber. -21. `MAP_SAFFRON_GYM` - talk to `SAFFRONGYM_SABRINA` at `(9, 8)` (stand at `(9, 9)` facing up, or `(10, 8)` facing left). Precondition: `ENGINE_MARSHBADGE` clear. Battle `SABRINA`/`SABRINA1`. Postcondition: `EVENT_BEAT_SABRINA`, the four trainer flags, and `ENGINE_MARSHBADGE` all set; +4800G. -22. `MAP_SAFFRON_GYM` exit - `(11, 9)` -> `(1, 5)`; walk right to `(5, 5)` -> `(1, 11)`; walk right to `(5, 11)` -> `(5, 17)`; walk up to `(5, 15)` -> `(15, 17)`; walk right to `(19, 17)` -> `(11, 15)`; walk down to warp 1/2 at `(8, 17)`/`(9, 17)` -> `MAP_SAFFRON_CITY` warp 2 `(34, 3)`. -23. Mid-gym heal-and-return (if step 21 needs a fresh party): exit via step 22, heal at `MAP_SAFFRON_POKECENTER_1F`, re-enter, then `(11,15) -> (19,17)`, up to `(19,15) -> (19,9)`, left to `(15,9) -> (15,3)`, down to `(15,5) -> (1,3)`, down to `(1,5) -> (11,9)`. This is the walkthrough's "1st teleporter, then up, left, down, down" and it checks out against the pad table. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map load, block grid, `COLL_*` collision, connections | `src/world/gen2/Map.lua`, `src/world/gen2/Permissions.lua`, `src/world/gen2/World.lua` | Implemented (generic; Gold maps come from `src/import/RomExtractorGen2.lua`, not hand-ported) | -| Warp panels / teleport pads (the whole gym maze) | `src/world/gen2/Permissions.lua:195` `COLL_WARP_PANEL = 0x7c` in `WARP_FACING_DOWN`, `Permissions.warpFacesDown`; warp taking in `src/world/gen2/World.lua:1566` `World:warpTo` | Implemented (panels warp on landing, which is what all 30 gym pads are) | -| Map-to-self warps (every gym pad targets `SAFFRON_GYM`) | `src/world/gen2/World.lua` warp handling | Implemented, but untested for this map - no driver exercises a same-map warp chain | -| `MAPCALLBACK_NEWMAP` -> `setflag ENGINE_FLYPOINT_SAFFRON` | `src/world/gen2/World.lua:5659` `self:runMapCallback("MAPCALLBACK_NEWMAP")`; fly-point row at `src/world/gen2/FieldMoves.lua:365` (`LANDMARK_SAFFRON_CITY` / `SPAWN_SAFFRON` / flag 59) | Implemented | -| Script opcodes used by every script in this section (`checkevent`, `iftrue`, `iffalse`, `setevent`, `checkflag`, `setflag`, `verbosegiveitem`, `promptbutton`, `writetext`, `waitbutton`, `jumptext`, `jumptextfaceplayer`, `jumpstd`, `gettrainername`, `pokemart`, `yesorno`, `checkitem`, `applymovement`, `special`, `warpcheck`, `newloadmap`, `loadtrainer`, `startbattle`, `reloadmapafterbattle`, `winlosstext`, `endifjustbattled`, `playsound`, `waitsfx`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` (`checkflag` :195, `verbosegiveitem` :490, `pokemart` :597, `jumpstd` :742, `loadtrainer` :806, `startbattle` :817, `winlosstext` :918) | Implemented | -| Object visibility from event flags (the Route 6 blocker, the station's hidden Teacher/Lass, the picked-up Focus Band ball) | `src/world/gen2/Events.lua:1-2` documents and implements "flag SET -> object hidden", matching `CheckObjectFlag` | Implemented | -| Overworld trainer sight + battle handoff (Rebecca, Franklin, Doris, Jared) | `src/world/gen2/Trainers.lua` (eyesight from `home/trainers.asm`, party build) | Implemented | -| Gym leader battle, badge grant, prize money x4 | `src/battle/gen2/Battle.lua`; `src/battle/gen2/Prize.lua:82` `Prize.reward(baseMoney, level)` and `:181` `local quarter = ...` (the four-way payout) | Implemented | -| Item ball pickup (`FOCUS_BAND` in the Fighting Dojo) | Extracted at `src/import/RomExtractorGen2.lua:2968-2969` into `obj.itemball`, but **nothing reads `obj.itemball` at runtime** (grep for `.itemball` in `src/` hits only the extractor). `src/script/gen2/CallAsm.lua:551` explicitly stubs `TryReceiveItem` | **Missing** - the ball object will exist and be masked correctly by its flag, but stepping on it grants nothing | -| Magnet Train ride (`special MagnetTrain`) | `src/core/gen2/MagnetTrain.lua`, `src/ui/gen2/MagnetTrainRide.lua` | Implemented (not exercised in this section - the officer's gate is false here) | -| Mart (`pokemart MARTTYPE_STANDARD, MART_SAFFRON`) | `src/ui/gen2/MartMenu.lua`, `Vm.lua:597` | Implemented | -| Pokecenter heal (`jumpstd PokecenterNurseScript`) | std scripts extracted (`src/import/RomExtractorGen2.lua:2901`), heal hook `src/script/gen2/Specials.lua:451` | Implemented | -| Bicycle | `src/world/gen2/Bike.lua` | Implemented | -| Any Saffron-specific hand-ported content | none | **Missing by design** - the Gen 2 port is data-driven off the ROM; there are no `SAFFRON` symbols anywhere under `src/` except Gen 1 save-conversion tables (`src/save_convert/`), which are unrelated | -| Driver coverage for this section | `tests/drivers/gold_*.lua` (25 drivers: boot, walk, warp, trainer, battle, map callbacks, ...) | **Missing** - no driver reaches Kanto or Saffron; `gold_warp_scene.lua` / `gold_walk_smoke.lua` are Johto-early smoke tests | - -## 6. Unresolved / verify by hand - -- **Psychic Jared's third Pokemon.** The walkthrough lists "Level 32 Exeggcute (672 EXP)" twice. `data/trainers/parties.asm:2569-2574` gives `PSYCHIC_T (11) JARED` as L32 `MR__MIME`, L32 `EXEGGCUTE`, **L35** `EXEGGCUTE`. The asm wins; the walkthrough's second Exeggcute level and its EXP figure are both wrong. -- **EXP values.** None of the per-Pokemon EXP numbers in the walkthrough (765, 1237, 1149, 735, 672, 720, 1264, 1941, 1339, 1912) were checked against `data/pokemon/base_stats/` - they are outside what a bot needs and were not verified. -- **"PokeMart ... upper-right corner of Saffron City".** The mart door is warp 3 at `(25, 11)` on a 40x36-cell map, i.e. north-of-centre and east of the middle, not the corner. The two gyms it says are "further north" are at `(26, 3)` and `(34, 3)`, which is correct. -- **"Silph Co ... the multi-story building above the Pokémon Center".** Silph is warp 7 at `(18, 21)`; the Pokecenter is warp 4 at `(9, 29)`. Silph is north-**east** of it, and `SilphCo1F` has no stairs warp - only the two street doors. There is no upper floor in Gen 2. -- **"You now have 10 badges!"** `ENGINE_MARSHBADGE` is bit 5 of `wKantoBadges` (`constants/engine_flags.asm:52`), so it is the 6th Kanto badge in flag order, not the 10th badge overall. The count of 10 only holds for a player who took Vermilion before Saffron, as this FAQ's chapter order does. Nothing in the asm enforces that order. -- **The walkthrough's mid-maze "left/right (whichever way works) till you find that one trainer again (Franklin)" heal route** is deliberately vague in the source text and was not reduced to a single pad sequence. The precise post-heal route it gives afterwards ("1st teleporter, then up, left, down, down") *does* resolve cleanly and is recorded in bot checklist step 23. -- **The "large man who blocks the entire entrance to the Underground Path"** is described as taunting the player about moving boulders. `Route6PokefanMText` (`maps/Route6.asm:15`) contains only the Power Plant line; no boulder or strength text exists on this map. Treat the boulder remark as FAQ colour. diff --git a/docs/gold-walkthrough/asm-walk/section-21-power-plant-and-cerulean-city-gym.md b/docs/gold-walkthrough/asm-walk/section-21-power-plant-and-cerulean-city-gym.md deleted file mode 100644 index 1e59e55c..00000000 --- a/docs/gold-walkthrough/asm-walk/section-21-power-plant-and-cerulean-city-gym.md +++ /dev/null @@ -1,896 +0,0 @@ -# Section 21 - Power Plant and Cerulean City Gym - -Source: `../section-21-power-plant-and-cerulean-city-gym.txt` -Maps covered: `MAP_ROUTE_5`, `MAP_ROUTE_5_SAFFRON_GATE`, `MAP_ROUTE_5_CLEANSE_TAG_HOUSE`, -`MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE`, `MAP_CERULEAN_CITY`, `MAP_CERULEAN_GYM`, -`MAP_ROUTE_9`, `MAP_ROUTE_10_NORTH`, `MAP_POWER_PLANT`, `MAP_ROUTE_24`, -`MAP_ROUTE_25`, `MAP_BILLS_HOUSE` -Badges / key milestones in this section: CASCADEBADGE (`ENGINE_CASCADEBADGE`) from Misty; -the whole Machine Part chain (`EVENT_MET_MANAGER_AT_POWER_PLANT` -> -`EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` -> Route 24 grunt battle -> -`EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM`); Misty's date on Route 25 clears -`EVENT_TRAINERS_IN_CERULEAN_GYM` and is what actually populates the gym. - -Coordinate note: `warp_event` / `bg_event` / `object_event` / `coord_event` x,y are in -*map cells* (walk tiles), while `map_const NAME, W, H` in `constants/map_constants.asm` -is in *blocks*. Cells = 2x blocks, so `map_const ROUTE_9, 30, 9` is a 60x18 cell map and -`warp_event 48, 15` is inside it. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_5_SAFFRON_GATE` | `maps/Route5SaffronGate.asm` | from `SAFFRON_CITY` warp 9 into gate warps 3/4 (`4,7` / `5,7`) | gate warps 1/2 (`4,0` / `5,0`) -> `ROUTE_5` warps 2/3 | "head north through the route-changing station" | -| 2 | `MAP_ROUTE_5` | `maps/Route5.asm` | gate warps | north connection -> `CERULEAN_CITY` | walk north; optional detour to the old Day Care building | -| 3 | `MAP_ROUTE_5_CLEANSE_TAG_HOUSE` | `maps/Route5CleanseTagHouse.asm` | `ROUTE_5` warp 4 at `10,11` | warps 1/2 at `2,7` / `3,7` -> `ROUTE_5` warp 4 | granny gives CLEANSE_TAG | -| 4 | `MAP_CERULEAN_CITY` | `maps/CeruleanCity.asm` | south connection from `ROUTE_5` | warp 5 at `30,23` -> `CERULEAN_GYM` | first (empty) gym visit | -| 5 | `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | `CERULEAN_CITY` warp 5 | warps 1/2 at `4,15` / `5,15` | gym is empty: `EVENT_TRAINERS_IN_CERULEAN_GYM` is still set from `InitializeEventsScript` | -| 6 | `MAP_ROUTE_9` | `maps/Route9.asm` | east connection from `CERULEAN_CITY` | south connection -> `ROUTE_10_NORTH` | six trainers; needs CUT at the west entrance | -| 7 | `MAP_ROUTE_10_NORTH` | `maps/Route10North.asm` | north connection from `ROUTE_9` | warp 2 at `3,9` -> `POWER_PLANT` | Pokemon Center at warp 1 (`11,1`); Surf the river to the plant | -| 8 | `MAP_POWER_PLANT` | `maps/PowerPlant.asm` | `ROUTE_10_NORTH` warp 2 | warps 1/2 at `2,17` / `3,17` | talk to the MANAGER, then trip the `5,12` coord event on the way out | -| 9 | `MAP_CERULEAN_CITY` | `maps/CeruleanCity.asm` | FLY (`SPAWN_CERULEAN`, `data/maps/spawn_points.asm:17`) | warp 5 -> `CERULEAN_GYM` | "Fly back to Cerulean City" | -| 10 | `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | `CERULEAN_CITY` warp 5 | warps 1/2 | `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` fires on entry | -| 11 | `MAP_ROUTE_24` | `maps/Route24.asm` | north connection from `CERULEAN_CITY` | north connection -> `ROUTE_25` | Rocket grunt on the old Nugget Bridge | -| 12 | `MAP_ROUTE_25` | `maps/Route25.asm` | south connection from `ROUTE_24` | warp 1 at `47,5` -> `BILLS_HOUSE` | six-pack trainers, Kevin's Nugget, Protein, Misty's date coord events | -| 13 | `MAP_BILLS_HOUSE` | `maps/BillsHouse.asm` | `ROUTE_25` warp 1 | warps 1/2 at `2,7` / `3,7` | Bill's grandpa evolution-stone chain | -| 14 | `MAP_CERULEAN_CITY` -> `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | FLY, then warp 5 | - | three Swimmers, hidden MACHINE_PART at `3,8`, Misty | - -Spills into the next section: `maps/Route9.asm` warp 1 at `48,15` leads to `ROCK_TUNNEL_1F`, -and the Machine Part is *returned* to `PowerPlantManager` (which is what unlocks the Magnet -Train and the Underground Path). The walkthrough text stops at Misty; Rock Tunnel / Lavender -and the return trip belong to the neighbouring sections. - -## 2. Maps - -### MAP_ROUTE_5 - -- Script: `maps/Route5.asm` -- Blocks: `maps/Route5.blk` -- Header: `data/maps/maps.asm:489` -> `map Route5, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_5, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:453` -> `map_const ROUTE_5, 10, 9` (group `SAFFRON` = 25, id 1) -- Connections (`data/maps/attributes.asm:356`): north `CERULEAN_CITY` (offset -5), south `SAFFRON_CITY` (offset -5) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 15 | `ROUTE_5_UNDERGROUND_PATH_ENTRANCE` | 1 | -| 2 | 8 | 17 | `ROUTE_5_SAFFRON_GATE` | 1 | -| 3 | 9 | 17 | `ROUTE_5_SAFFRON_GATE` | 2 | -| 4 | 10 | 11 | `ROUTE_5_CLEANSE_TAG_HOUSE` | 1 | - -**Coord events** - none (`def_coord_events` is empty). - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 17 | `BGEVENT_READ` | `Route5UndergroundPathSign` | -| 10 | 11 | `BGEVENT_READ` | `HouseForSaleSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE5_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 16 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route5PokefanMScript` | `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | - -**Scripts of interest** - -- `Route5PokefanMScript` - one `jumptextfaceplayer Route5PokefanMText`: "The road is closed - until the problem at the POWER PLANT is solved." He stands at `17,16`, directly below the - Underground Path door at `17,15`, so the tile is physically blocked. His object row carries - `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`; object events are hidden when their - flag is *set*, and that flag is not in `InitializeEventsScript`, so at this point in the - game it is clear and he is present. `PowerPlantManager` `setevent`s it after the part is - returned (`maps/PowerPlant.asm:164`), removing him. -- `Route5UndergroundPathSign` / `HouseForSaleSign` - plain `jumptext`. Note the "House for - Sale" sign shares the exact tile `10,11` with warp 4 into the Cleanse Tag house. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | `constants/event_flags.asm:1299` | object row `maps/Route5.asm:56` and `maps/Route6.asm:43`; `setevent` at `maps/PowerPlant.asm:164` | clear = NPC present and Underground Path door unreachable; set = NPC gone | - -**Items** - none on the route proper. - -**Wild encounters** - `data/wild/kanto_grass.asm:425` `def_grass_wildmons ROUTE_5`, -10/10/10 percent. Gold morn+day: L13 PIDGEY, L13 BELLSPROUT, L14 PIDGEY, L15 PIDGEY, -L12 ABRA, L14 ABRA, L14 ABRA. Gold nite: L13 ODDISH, L14 ODDISH, L13 BELLSPROUT, -L15 GLOOM, L12 ABRA, L14 ABRA, L14 ABRA. No water table. Fishing group `FISHGROUP_SHORE` -(`data/wild/fish.asm` `.Shore_*`). - ---- - -### MAP_ROUTE_5_SAFFRON_GATE - -- Script: `maps/Route5SaffronGate.asm` -- Blocks: shared, `data/maps/blocks.asm` -- Header: `data/maps/maps.asm:502` -> `map Route5SaffronGate, TILESET_GATE, GATE, LANDMARK_ROUTE_5, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:466` -> `map_const ROUTE_5_SAFFRON_GATE, 5, 4` (group 25, id 14) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_5` | 2 | -| 2 | 5 | 0 | `ROUTE_5` | 3 | -| 3 | 4 | 7 | `SAFFRON_CITY` | 9 | -| 4 | 5 | 7 | `SAFFRON_CITY` | 9 | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE5SAFFRONGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route5SaffronGateOfficerScript` | -1 | - -Nothing here gates progress: the officer is a single `jumptextfaceplayer`, and there are no -coord or bg events. - ---- - -### MAP_ROUTE_5_CLEANSE_TAG_HOUSE - -- Script: `maps/Route5CleanseTagHouse.asm` -- Blocks: shared, `data/maps/blocks.asm:196` (`Route5CleanseTagHouse_Blocks`) -- Header: `data/maps/maps.asm:503` -> `map Route5CleanseTagHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_5, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:467` -> `map_const ROUTE_5_CLEANSE_TAG_HOUSE, 4, 4` (group 25, id 15) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_5` | 4 | -| 2 | 3 | 7 | `ROUTE_5` | 4 | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `HouseForSaleBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `HouseForSaleBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE5CLEANSETAGHOUSE_GRANNY` | `SPRITE_GRANNY` | 2 | 5 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_SCRIPT` | `Route5CleanseTagHouseGrannyScript` | -1 | -| `ROUTE5CLEANSETAGHOUSE_TEACHER` | `SPRITE_TEACHER` | 5 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route5CleanseTagHouseTeacherScript` | -1 | - -**Scripts of interest** - -- `Route5CleanseTagHouseGrannyScript` (`pokegold.sym` `61:57de`) - - `faceplayer` / `opentext` / `checkevent EVENT_GOT_CLEANSE_TAG` / `iftrue .GotCleanseTag`; - otherwise `writetext Route5CleanseTagHouseGrannyText1`, `promptbutton`, - `verbosegiveitem CLEANSE_TAG`, `iffalse .NoRoom`, `setevent EVENT_GOT_CLEANSE_TAG`, - then the "you are protected now" line. Bag-full drops straight to `.NoRoom` without - setting the flag, so a bot must have a free pocket slot or repeat the talk. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `CLEANSE_TAG` | talk to `ROUTE5CLEANSETAGHOUSE_GRANNY` at `2,5` | `Route5CleanseTagHouseGrannyScript`, `verbosegiveitem CLEANSE_TAG` | `EVENT_GOT_CLEANSE_TAG` (`constants/event_flags.asm:218`) | - ---- - -### MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE - -- Script: `maps/Route5UndergroundPathEntrance.asm` -- Header: `data/maps/maps.asm:501` -> `map Route5UndergroundPathEntrance, TILESET_GATE, GATE, LANDMARK_ROUTE_5, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:465` -> `map_const ROUTE_5_UNDERGROUND_PATH_ENTRANCE, 4, 4` (group 25, id 13) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `ROUTE_5` | 1 | -| 2 | 4 | 7 | `ROUTE_5` | 1 | -| 3 | 4 | 3 | `UNDERGROUND_PATH` | 1 | - -Only reachable once the Route 5 Pokefan is gone. Listed here because the walkthrough calls -him out; nothing in this section actually enters it. - ---- - -### MAP_CERULEAN_CITY - -- Script: `maps/CeruleanCity.asm` -- Blocks: `maps/CeruleanCity.blk` -- Header: `data/maps/maps.asm:221` -> `map CeruleanCity, TILESET_KANTO, TOWN, LANDMARK_CERULEAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:203` -> `map_const CERULEAN_CITY, 20, 18` (group `CERULEAN` = 7, id 17) -- Connections (`data/maps/attributes.asm:360`): north `ROUTE_24` (+6), south `ROUTE_5` (+5), west `ROUTE_4` (+5), east `ROUTE_9` (+9) -- Fly / spawn: `data/maps/spawn_points.asm:17` `spawn CERULEAN_CITY, 19, 22`; - `data/maps/flypoints.asm:21` `db LANDMARK_CERULEAN_CITY, SPAWN_CERULEAN` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 15 | `CERULEAN_GYM_BADGE_SPEECH_HOUSE` | 1 | -| 2 | 28 | 17 | `CERULEAN_POLICE_STATION` | 1 | -| 3 | 13 | 19 | `CERULEAN_TRADE_SPEECH_HOUSE` | 1 | -| 4 | 19 | 21 | `CERULEAN_POKECENTER_1F` | 1 | -| 5 | 30 | 23 | `CERULEAN_GYM` | 1 | -| 6 | 25 | 29 | `CERULEAN_MART` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 23 | 23 | `BGEVENT_READ` | `CeruleanCitySign` | -| 27 | 25 | `BGEVENT_READ` | `CeruleanGymSign` | -| 11 | 29 | `BGEVENT_READ` | `CeruleanBikeShopSign` ("The BIKE SHOP has moved to GOLDENROD") | -| 25 | 17 | `BGEVENT_READ` | `CeruleanPoliceSign` | -| 23 | 7 | `BGEVENT_READ` | `CeruleanCapeSign` | -| 14 | 29 | `BGEVENT_READ` | `CeruleanLockedDoor` | -| 20 | 21 | `BGEVENT_READ` | `CeruleanCityPokecenterSign` | -| 26 | 29 | `BGEVENT_READ` | `CeruleanCityMartSign` | -| 2 | 12 | `BGEVENT_ITEM` | `CeruleanCityHiddenBerserkGene` -> `hiddenitem BERSERK_GENE, EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` (`pokegold.sym` `4f:60ad`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CERULEANCITY_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 15 | 23 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `CeruleanCityCooltrainerMScript` | -1 | -| `CERULEANCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 23 | 15 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `CeruleanCitySuperNerdScript` | -1 | -| `CERULEANCITY_SLOWPOKE` | `SPRITE_SLOWPOKE` | 20 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `CeruleanCitySlowbro` | -1 | -| `CERULEANCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 21 | 24 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CeruleanCityCooltrainerFScript` | -1 | -| `CERULEANCITY_FISHER` | `SPRITE_FISHER` | 30 | 26 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `CeruleanCityFisherScript` | -1 | -| `CERULEANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 12 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` (1,0) | `OBJECTTYPE_SCRIPT` | `CeruleanCityYoungsterScript` | -1 | - -**Scripts of interest** - -- `CeruleanCityFlypointCallback` - `MAPCALLBACK_NEWMAP`, `setflag ENGINE_FLYPOINT_CERULEAN` - then `endcallback`. Simply entering the city registers the Fly destination the walkthrough - relies on twice. -- `CeruleanCityCooltrainerMScript` - `checkevent EVENT_RETURNED_MACHINE_PART`; before the - part is returned he is the hint "KANTO's POWER PLANT is toward the end of ROUTE 9". -- `CeruleanCityFisherScript` - `checkevent EVENT_RETURNED_MACHINE_PART` (iftrue -> generic - line), else `checkevent EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` -> "I saw this shady guy - go off toward CERULEAN's CAPE", i.e. the pointer to Route 24/25. -- `CeruleanCityYoungsterScript` - after his first line, if - `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` is clear he plays the ITEMFINDER ping loop - (`SFX_SECOND_PART_OF_ITEMFINDER` / `SFX_TRANSACTION` x4) and `showemote EMOTE_SHOCK`. - That is the in-game hint for the hidden BERSERK_GENE at `2,12`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_CERULEAN` | `constants/engine_flags.asm:70` | `CeruleanCityFlypointCallback` | Fly target unlocked on first entry | -| `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` | `constants/event_flags.asm:202` | set by `CeruleanGymGruntRunsOutScript`; read by `CeruleanCityFisherScript` | grunt cutscene has played | -| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | set by `PowerPlantManager`; read here and in ~10 other maps | post-section state | -| `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` | `constants/event_flags.asm:250` | `CeruleanCityHiddenBerserkGene`, `CeruleanCityYoungsterScript` | hidden item consumed | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BERSERK_GENE` | face the tile and press A (hidden) | `bg_event 2, 12, BGEVENT_ITEM, CeruleanCityHiddenBerserkGene` | `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` | - -**Wild encounters** - no grass table. Water: `data/wild/kanto_water.asm:131` -`def_water_wildmons CERULEAN_CITY`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING. -Fishing group `FISHGROUP_LAKE`. - ---- - -### MAP_CERULEAN_GYM - -- Script: `maps/CeruleanGym.asm` -- Blocks: `maps/CeruleanGym.blk` -- Header: `data/maps/maps.asm:210` -> `map CeruleanGym, TILESET_PORT, INDOOR, LANDMARK_CERULEAN_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` - (the `data/maps/maps.asm:209` comment notes the known bug that you can fish in the gym pool) -- Dimensions: `constants/map_constants.asm:192` -> `map_const CERULEAN_GYM, 5, 8` (group 7, id 6) -- Scenes (`def_scene_scripts`, index order from `macros/scripts/maps.asm` `def_scene_scripts`/`scene_const`): - `SCENE_CERULEANGYM_NOOP` = 0, `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` = 1. - Scene var: `data/maps/scenes.asm:13` `scene_var CERULEAN_GYM, wCeruleanGymSceneID`. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 15 | `CERULEAN_CITY` | 5 | -| 2 | 5 | 15 | `CERULEAN_CITY` | 5 | - -**Coord events** - none. The grunt cutscene runs from the *scene script* -(`CeruleanGymGruntRunsOutScene` -> `sdefer CeruleanGymGruntRunsOutScript`), which fires on -map load while `wCeruleanGymSceneID == SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, not from a tile. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 8 | `BGEVENT_ITEM` | `CeruleanGymHiddenMachinePart` -> `hiddenitem MACHINE_PART, EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` (`pokegold.sym` `54:440e`) | -| 2 | 13 | `BGEVENT_READ` | `CeruleanGymStatue1` | -| 6 | 13 | `BGEVENT_READ` | `CeruleanGymStatue2` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CERULEANGYM_ROCKET` | `SPRITE_ROCKET` | 4 | 10 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert; driven by the scene script) | `EVENT_CERULEAN_GYM_ROCKET` | -| `CERULEANGYM_MISTY` | `SPRITE_MISTY` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeruleanGymMistyScript` | `EVENT_TRAINERS_IN_CERULEAN_GYM` | -| `CERULEANGYM_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 4 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmerfDiana` | `EVENT_TRAINERS_IN_CERULEAN_GYM` | -| `CERULEANGYM_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 1 | 9 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSwimmerfBriana` | `EVENT_TRAINERS_IN_CERULEAN_GYM` | -| `CERULEANGYM_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 8 | 9 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermParker` | `EVENT_TRAINERS_IN_CERULEAN_GYM` | -| `CERULEANGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeruleanGymGuideScript` | `EVENT_TRAINERS_IN_CERULEAN_GYM` | - -**Scripts of interest** - -- `CeruleanGymGruntRunsOutScript` (`pokegold.sym` `54:4336`) - the grunt runs down four - `big_step DOWN`, `playsound SFX_TACKLE`, jumps into the player - (`CeruleanGymGruntRunsIntoYouMovement`: `fix_facing` / `set_sliding` / `jump_step UP`), - `playmusic MUSIC_ROCKET_ENCOUNTER`, three text boxes with a `showemote EMOTE_SHOCK` - between them, then `CeruleanGymGruntRunsOutMovement` (`big_step RIGHT`, `big_step DOWN`), - `playsound SFX_EXIT_BUILDING`, `disappear CERULEANGYM_ROCKET`. State changes, in order: - `setevent EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM`, - `clearevent EVENT_ROUTE_24_ROCKET` (spawns the grunt on Route 24), - `clearevent EVENT_ROUTE_25_MISTY_BOYFRIEND` (spawns Misty + her date on Route 25), - `setscene SCENE_CERULEANGYM_NOOP`, - `setmapscene ROUTE_25, SCENE_ROUTE25_MISTYS_DATE`, - `setmapscene POWER_PLANT, SCENE_POWERPLANT_NOOP`, then - `special RestartMapMusic` and `turnobject PLAYER, DOWN`. -- `CeruleanGymMistyScript` (`pokegold.sym` `54:438a`) - `checkflag ENGINE_CASCADEBADGE`, - `iftrue .FightDone`; else `MistyIntroText`, `winlosstext MistyWinLossText, 0`, - `loadtrainer MISTY, MISTY1`, `startbattle`, `reloadmapafterbattle`, then - `setevent EVENT_BEAT_MISTY` **and** `setevent EVENT_BEAT_SWIMMERF_DIANA`, - `EVENT_BEAT_SWIMMERF_BRIANA`, `EVENT_BEAT_SWIMMERM_PARKER` (beating Misty retroactively - marks the three gym trainers beaten), `ReceivedCascadeBadgeText`, `playsound SFX_GET_BADGE`, - `setflag ENGINE_CASCADEBADGE`. -- `CeruleanGymStatue1` / `CeruleanGymStatue2` - `checkevent EVENT_TRAINERS_IN_CERULEAN_GYM`; - if *false* (trainers present) they fall through to the shared `CeruleanGymStatue` - (`jumpstd GymStatue1Script`, or `GymStatue2Script` with `gettrainername STRING_BUFFER_4, MISTY, MISTY1` - once `ENGINE_CASCADEBADGE` is set). If true (gym empty) they print the "Sorry, I'll be out - for a while - MISTY" / "Since MISTY's out, we'll be away too" notes. -- `CeruleanGymGuideScript` - `checkevent EVENT_BEAT_MISTY` for the post-win line. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_TRAINERS_IN_CERULEAN_GYM` | `constants/event_flags.asm:1297` | set in `engine/events/std_scripts.asm:550` (`InitializeEventsScript`); cleared by `Route25MistyDate1Script` / `Route25MistyDate2Script` | set = Misty, three Swimmers and the guide are all hidden. This is why the first gym visit is empty. | -| `EVENT_CERULEAN_GYM_ROCKET` | `constants/event_flags.asm:1295` | set at `std_scripts.asm:547`; cleared by `PowerPlantManager` (`maps/PowerPlant.asm:146`) | clear = grunt object exists so the scene can run | -| `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` | `constants/event_flags.asm:251` | set at `std_scripts.asm:546`; cleared by `PowerPlantManager` (`maps/PowerPlant.asm:147`) | set = the hidden item at `3,8` yields nothing. Talking to the MANAGER is what arms it. | -| `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` | `constants/event_flags.asm:202` | `CeruleanGymGruntRunsOutScript` | cutscene done | -| `EVENT_BEAT_MISTY` | `constants/event_flags.asm:716` | `CeruleanGymMistyScript` | gym cleared | -| `ENGINE_CASCADEBADGE` | `constants/engine_flags.asm:48` | `CeruleanGymMistyScript` `checkflag` / `setflag` | the badge itself | -| `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` (= 1) | `maps/CeruleanGym.asm:12` via `scene_const` | `setmapscene` at `maps/PowerPlant.asm:148`; cleared to `SCENE_CERULEANGYM_NOOP` at `maps/CeruleanGym.asm:49` | entering the gym with this scene id plays the grunt scene | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MACHINE_PART` | stand at the pool and press A into `3,8` | `bg_event 3, 8, BGEVENT_ITEM, CeruleanGymHiddenMachinePart` | `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` (must be cleared first by the MANAGER) | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SWIMMERF, BRIANA` | `SWIMMERF` | 19 (`parties.asm:1885`) | L35 SEAKING, L35 SEAKING | `TrainerSwimmerfBriana` | none | -| `SWIMMERM, PARKER` | `SWIMMERM` | 21 (`parties.asm:1775`) | L32 HORSEA, L32 HORSEA, L35 SEADRA | `TrainerSwimmermParker` | none | -| `SWIMMERF, DIANA` | `SWIMMERF` | 18 (`parties.asm:1880`) | L37 GOLDUCK | `TrainerSwimmerfDiana` | none | -| `MISTY, MISTY1` | `MISTY` | 1 (`parties.asm:281`, `TRAINERTYPE_MOVES`) | L42 GOLDUCK (SURF, DISABLE, PSYCH_UP, PSYCHIC_M); L42 QUAGSIRE (SURF, AMNESIA, EARTHQUAKE, RAIN_DANCE); L44 LAPRAS (SURF, PERISH_SONG, BLIZZARD, RAIN_DANCE); L47 STARMIE (SURF, CONFUSE_RAY, RECOVER, ICE_BEAM) | `CeruleanGymMistyScript` | leader, no phone | - -Base rewards (`data/trainers/attributes.asm`): SWIMMERF 5, SWIMMERM 2, MISTY 25. Payout is -`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) = base x last mon level, -added four times by the `ld c, 4` loop in `engine/battle/core.asm:2340`, i.e. base x level x 4. -That reproduces every number in the walkthrough (Misty 25 x 47 x 4 = 4700). - ---- - -### MAP_ROUTE_9 - -- Script: `maps/Route9.asm` -- Blocks: `maps/Route9.blk` -- Header: `data/maps/maps.asm:217` -> `map Route9, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_9, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:199` -> `map_const ROUTE_9, 30, 9` (group 7, id 13) -- Connections (`data/maps/attributes.asm:366`): south `ROUTE_10_NORTH` (+20), west `CERULEAN_CITY` (-9) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 48 | 15 | `ROCK_TUNNEL_1F` | 1 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 15 | 7 | `BGEVENT_READ` | `Route9Sign` | -| 10 | 5 | `BGEVENT_ITEM` | `Route9HiddenEther` -> `hiddenitem ETHER, EVENT_ROUTE_9_HIDDEN_ETHER` (`pokegold.sym` `50:407d`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE9_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 23 | 11 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerCamperDean` | -1 | -| `ROUTE9_LASS1` | `SPRITE_LASS` | 35 | 8 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerPicnickerHeidi` | -1 | -| `ROUTE9_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 11 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerCamperSid` | -1 | -| `ROUTE9_LASS2` | `SPRITE_LASS` | 9 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerPicnickerEdna` | -1 | -| `ROUTE9_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 32 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerHikerTim` | -1 | -| `ROUTE9_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 33 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerHikerSidney` | -1 | - -**Scripts of interest** - all six are the plain `trainer CLASS, ID, EVENT_BEAT_*, seen, beaten, 0, .Script` -form with an `endifjustbattled` + one after-battle text. No flags beyond `EVENT_BEAT_*`. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ETHER` | hidden, face `10,5` | `bg_event 10, 5, BGEVENT_ITEM, Route9HiddenEther` | `EVENT_ROUTE_9_HIDDEN_ETHER` (`constants/event_flags.asm:242`) | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `PICNICKER, EDNA` | `PICNICKER` | 14 (`parties.asm:2659`) | L30 NIDORINA, L34 RAICHU | `TrainerPicnickerEdna` | none | -| `CAMPER, SID` | `CAMPER` | 8 (`parties.asm:2744`) | L32 DUGTRIO, L29 PRIMEAPE, L29 POLIWRATH | `TrainerCamperSid` | none | -| `CAMPER, DEAN` | `CAMPER` | 7 (`parties.asm:2738`) | L33 GOLDUCK, L31 SANDSLASH | `TrainerCamperDean` | none | -| `HIKER, SIDNEY` | `HIKER` | 15 (`parties.asm:2210`) | L34 DUGTRIO, L32 ONIX | `TrainerHikerSidney` | none | -| `HIKER, TIM` | `HIKER` | 13 (`parties.asm:2197`) | L31 GRAVELER x3 | `TrainerHikerTim` | none | -| `PICNICKER, HEIDI` | `PICNICKER` | 13 (`parties.asm:2653`) | L32 SKIPLOOM, L32 SKIPLOOM | `TrainerPicnickerHeidi` | none | - -Base rewards: PICNICKER 5, CAMPER 5, HIKER 8 (`data/trainers/attributes.asm`). - -**Wild encounters** - `data/wild/kanto_grass.asm:645` `def_grass_wildmons ROUTE_9`, 10/10/10 percent, -version-split with `IF DEF(_GOLD)`. Gold morn/day: L13 MANKEY, L15 RATTATA, L13 SPEAROW, -L15 RATICATE, L15 FEAROW, L15 PRIMEAPE, L15 PRIMEAPE. Gold nite: L13 MANKEY, L15 RATTATA, -L15 RATICATE, L13 RATTATA, L15 RATICATE, L15 PRIMEAPE, L15 PRIMEAPE. (Silver swaps MANKEY / -PRIMEAPE for RATTATA / RATICATE, which is exactly the "Gold only" note in the walkthrough.) -Water: `data/wild/kanto_water.asm:19`, 4 percent: L15 GOLDEEN, L10 GOLDEEN, L15 SEAKING. -Fishing group `FISHGROUP_LAKE`. No `treemon_maps.asm` row, so no headbutt table. - ---- - -### MAP_ROUTE_10_NORTH - -- Script: `maps/Route10North.asm` -- Blocks: `maps/Route10North.blk` -- Header: `data/maps/maps.asm:218` -> `map Route10North, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_10, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:200` -> `map_const ROUTE_10_NORTH, 10, 9` (group 7, id 14) -- Connections (`data/maps/attributes.asm:389`): north `ROUTE_9` (-20), south `ROUTE_10_SOUTH` (0) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 1 | `ROUTE_10_POKECENTER_1F` | 1 | -| 2 | 3 | 9 | `POWER_PLANT` | 1 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 11 | `BGEVENT_READ` | `PowerPlantSign` ("KANTO POWER PLANT") | -| 12 | 1 | `BGEVENT_READ` | `Route10PokecenterSign` (`jumpstd PokecenterSignScript`) | - -**Object events** - `def_object_events` is empty. No trainers on this map. - -**Wild encounters** - `data/wild/kanto_grass.asm:700` `def_grass_wildmons ROUTE_10_NORTH`, -10/10/10 percent, no version split. -morn: L16 SPEAROW, L17 VOLTORB, L16 RATICATE, L18 FEAROW, L16 QUAGSIRE, L15 ELECTABUZZ, L15 ELECTABUZZ. -day: L16 SPEAROW, L17 VOLTORB, L17 RATICATE, L18 FEAROW, L15 ELECTABUZZ, L17 ELECTABUZZ, L17 ELECTABUZZ. -nite: L16 QUAGSIRE, L17 VOLTORB, L16 RATICATE, L17 QUAGSIRE, L18 RATICATE, L15 ELECTABUZZ, L15 ELECTABUZZ. -Water: `data/wild/kanto_water.asm:26`, 4 percent: L15 GOLDEEN, L10 GOLDEEN, L15 SEAKING. -Fishing group `FISHGROUP_LAKE`. - ---- - -### MAP_POWER_PLANT - -- Script: `maps/PowerPlant.asm` -- Blocks: `maps/PowerPlant.blk` -- Header: `data/maps/maps.asm:214` -> `map PowerPlant, TILESET_FACILITY, INDOOR, LANDMARK_POWER_PLANT, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:196` -> `map_const POWER_PLANT, 10, 9` (group 7, id 10) -- Scenes: `SCENE_POWERPLANT_NOOP` = 0, `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` = 1 - (`maps/PowerPlant.asm:11-12`); scene var `data/maps/scenes.asm:12` - `scene_var POWER_PLANT, wPowerPlantSceneID` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 17 | `ROUTE_10_NORTH` | 2 | -| 2 | 3 | 17 | `ROUTE_10_NORTH` | 2 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` (1) | 5 | 12 | `PowerPlantGuardPhoneScript` | the "shady character in CERULEAN" cutscene; ends with `setscene SCENE_POWERPLANT_NOOP` | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `POWERPLANT_OFFICER1` | `SPRITE_OFFICER` | 4 | 14 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficerScript` | -1 | -| `POWERPLANT_GYM_GUIDE1` | `SPRITE_GYM_GUIDE` | 2 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide1Script` | -1 | -| `POWERPLANT_GYM_GUIDE2` | `SPRITE_GYM_GUIDE` | 6 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide2Script` | -1 | -| `POWERPLANT_OFFICER2` | `SPRITE_OFFICER` | 9 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficer2Script` | -1 | -| `POWERPLANT_GYM_GUIDE3` | `SPRITE_GYM_GUIDE` | 7 | 2 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide4Script` | -1 | -| `POWERPLANT_MANAGER` | `SPRITE_FISHER` | 14 | 10 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantManager` | -1 | - -(The object const list names the fifth guide `POWERPLANT_GYM_GUIDE3` while its script is -called `PowerPlantGymGuide4Script`; that mismatch is in the source, not a transcription slip.) - -**Scripts of interest** - -- `PowerPlantManager` (`pokegold.sym` `54:4dbd`) - the spine of the whole section: - 1. `checkevent EVENT_RETURNED_MACHINE_PART` -> `.ReturnedMachinePart` - 2. `checkitem MACHINE_PART` -> `.FoundMachinePart` - 3. `checkevent EVENT_MET_MANAGER_AT_POWER_PLANT` -> `.MetManager` - 4. first talk: `PowerPlantManagerWhoWouldRuinMyGeneratorText`, then - `setevent EVENT_MET_MANAGER_AT_POWER_PLANT`, - `clearevent EVENT_CERULEAN_GYM_ROCKET`, - `clearevent EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM`, - `setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, - `setscene SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL`. - 5. `.FoundMachinePart` (out of scope for this walkthrough section, listed because it is the - payoff): `takeitem MACHINE_PART`, `setevent EVENT_RETURNED_MACHINE_PART`, - `clearevent EVENT_SAFFRON_TRAIN_STATION_POPULATION`, - `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`, - `setevent EVENT_ROUTE_24_ROCKET`, `setevent EVENT_RESTORED_POWER_TO_KANTO`, - `clearevent EVENT_GOLDENROD_TRAIN_STATION_GENTLEMAN`, then - `verbosegiveitem TM_ZAP_CANNON` guarded by `EVENT_GOT_TM07_ZAP_CANNON`. -- `PowerPlantGuardPhoneScript` - `playsound SFX_CALL`, `showemote EMOTE_SHOCK` on - `POWERPLANT_OFFICER1`, `applymovement POWERPLANT_OFFICER1 PowerPlantOfficer1ApproachGymGuide2Movement` - (RIGHT, RIGHT, UP, UP), two text boxes, then `PowerPlantOfficer1ReturnToPostMovement` - (DOWN, DOWN, LEFT, LEFT, turn_head DOWN) and `setscene SCENE_POWERPLANT_NOOP`. -- The four flavour NPCs all branch only on `EVENT_RETURNED_MACHINE_PART` - (`PowerPlantOfficerScript` additionally on `EVENT_MET_MANAGER_AT_POWER_PLANT`). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_MET_MANAGER_AT_POWER_PLANT` | `constants/event_flags.asm:201` | `PowerPlantManager` | gate for everything downstream | -| `EVENT_CERULEAN_GYM_ROCKET` | `constants/event_flags.asm:1295` | cleared by `PowerPlantManager` | grunt object now exists in the gym | -| `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` | `constants/event_flags.asm:251` | cleared by `PowerPlantManager` | the hidden item is now live | -| `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` (1) | `maps/PowerPlant.asm:12` | `setscene`, then the `5,12` coord event | forces the exit cutscene once | -| `EVENT_RETURNED_MACHINE_PART` / `EVENT_RESTORED_POWER_TO_KANTO` | `constants/event_flags.asm:200` / `:204` | `.FoundMachinePart` | later-section state (Magnet Train, Underground Path, roaming beasts checks in `maps/Route26.asm` etc.) | -| `EVENT_GOT_TM07_ZAP_CANNON` | `constants/event_flags.asm:222` | `.ReturnedMachinePart` | TM07 reward | - -**Items** - `TM_ZAP_CANNON` from the MANAGER, but only on the *return* trip, which the -walkthrough section does not cover. - -**Trainers / wild encounters** - none. - ---- - -### MAP_ROUTE_24 - -- Script: `maps/Route24.asm` -- Blocks: `maps/Route24.blk` -- Header: `data/maps/maps.asm:219` -> `map Route24, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_24, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:201` -> `map_const ROUTE_24, 10, 9` (group 7, id 15) -- Connections (`data/maps/attributes.asm:370`): north `ROUTE_25` (0), south `CERULEAN_CITY` (-6) - -**Warps** - `def_warp_events` is empty. -**Coord events** - empty. -**BG events** - empty. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE24_ROCKET` | `SPRITE_ROCKET` | 8 | 7 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `Route24RocketScript` | `EVENT_ROUTE_24_ROCKET` | - -**Scripts of interest** - -- `Route24RocketScript` (`pokegold.sym` `50:4407`) - `faceplayer`, - `playmusic MUSIC_ROCKET_ENCOUNTER`, `Route24RocketSeenText`, - `winlosstext Route24RocketBeatenText, -1`, `loadtrainer GRUNTM, GRUNTM_31`, `startbattle`, - `dontrestartmapmusic`, `reloadmapafterbattle`, then `Route24RocketAfterBattleText` - (the "MACHINE PART ... hide it I did in GYM of the CERULEAN ... Inside water put it I did" - hint), `special FadeOutMusic`, `Route24RocketDisappearsText`, `special FadeOutToBlack`, - `special ReloadSpritesNoPalettes`, `disappear ROUTE24_ROCKET`, `pause 25`, - `special FadeInFromBlack`, `playmapmusic`. - Note: he is *talked to*, not a sight-line trainer, and there is no `EVENT_BEAT_*` - - `disappear` sets `EVENT_ROUTE_24_ROCKET` and that is the only record of the win. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_24_ROCKET` | `constants/event_flags.asm:1294` | set at `engine/events/std_scripts.asm:548`; cleared by `CeruleanGymGruntRunsOutScript`; set again by `disappear` and by `PowerPlantManager:165` | clear = grunt is standing on the bridge at `8,7` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `GRUNTM, GRUNTM_31` | `GRUNTM` | 31 (`data/trainers/parties.asm:1340`) | L30 GOLBAT | `Route24RocketScript` | none | - -**Wild encounters** - `data/wild/kanto_grass.asm:1061` `def_grass_wildmons ROUTE_24`, 10/10/10 percent. -morn: L8 BELLSPROUT, L10 BELLSPROUT, L9 ABRA, L12 WEEPINBELL, L8 VENONAT, L14 WEEPINBELL, L14 WEEPINBELL. -day: L8 BELLSPROUT, L10 SUNKERN, L9 ABRA, L12 WEEPINBELL, L10 BELLSPROUT, L14 WEEPINBELL, L14 WEEPINBELL. -nite: L8 VENONAT, L10 ODDISH, L9 ABRA, L13 WEEPINBELL, L10 BELLSPROUT, L10 VENOMOTH, L10 VENOMOTH. -Water: `data/wild/kanto_water.asm:75`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING. - ---- - -### MAP_ROUTE_25 - -- Script: `maps/Route25.asm` -- Blocks: `maps/Route25.blk` -- Header: `data/maps/maps.asm:220` -> `map Route25, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_25, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:202` -> `map_const ROUTE_25, 30, 9` (group 7, id 16) -- Connections (`data/maps/attributes.asm:374`): south `ROUTE_24` (0). No other connection - the - route is a dead end at Bill's house. -- Scenes: `SCENE_ROUTE25_NOOP` = 0, `SCENE_ROUTE25_MISTYS_DATE` = 1 (`maps/Route25.asm:16-17`); - scene var `data/maps/scenes.asm:14` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 47 | 5 | `BILLS_HOUSE` | 1 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE25_MISTYS_DATE` (1) | 42 | 6 | `Route25MistyDate1Script` (`pokegold.sym` `50:4781`) | the date cutscene, upper approach | -| `SCENE_ROUTE25_MISTYS_DATE` (1) | 42 | 7 | `Route25MistyDate2Script` | same cutscene, lower approach | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 45 | 5 | `BGEVENT_READ` | `BillsHouseSign` ("SEA COTTAGE / BILL'S HOUSE") | -| 4 | 5 | `BGEVENT_ITEM` | `Route25HiddenPotion` -> `hiddenitem POTION, EVENT_ROUTE_25_HIDDEN_POTION` (`pokegold.sym` `50:48c6`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE25_MISTY` | `SPRITE_MISTY` | 46 | 9 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert) | `EVENT_ROUTE_25_MISTY_BOYFRIEND` | -| `ROUTE25_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 46 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert) | `EVENT_ROUTE_25_MISTY_BOYFRIEND` | -| `ROUTE25_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 12 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSchoolboyDudley` | -1 | -| `ROUTE25_LASS1` | `SPRITE_LASS` | 16 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerLassEllen` | -1 | -| `ROUTE25_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 21 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSchoolboyJoe` | -1 | -| `ROUTE25_LASS2` | `SPRITE_LASS` | 22 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerLassLaura` | -1 | -| `ROUTE25_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 25 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerCamperLloyd` | -1 | -| `ROUTE25_LASS3` | `SPRITE_LASS` | 28 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerLassShannon` | -1 | -| `ROUTE25_SUPER_NERD` | `SPRITE_SUPER_NERD` | 31 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSupernerdPat` | -1 | -| `ROUTE25_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 37 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `TrainerCooltrainermKevin` | -1 | -| `ROUTE25_POKE_BALL` | `SPRITE_POKE_BALL` | 32 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route25Protein` (`itemball PROTEIN`, `pokegold.sym` `50:48c4`) | `EVENT_ROUTE_25_PROTEIN` | - -**Scripts of interest** - -- `Route25MistyDate1Script` / `Route25MistyDate2Script` - identical except for the approach - movement. `showemote EMOTE_HEART` on Misty, `pause 30`, `showemote EMOTE_SHOCK` on the - boyfriend, `applymovement ROUTE25_COOLTRAINER_M1` (one or two `big_step DOWN`), - `disappear ROUTE25_COOLTRAINER_M1`, `playmusic MUSIC_BEAUTY_ENCOUNTER`, Misty walks over - (`Route25MistyApproachesPlayerMovement1` = UP,UP,UP,LEFT,LEFT,LEFT; variant 2 drops one UP), - `writetext Route25MistyDateText` (the "pest" speech that ends with "Come to CERULEAN GYM"), - Misty leaves via `Route25MistyLeavesMovement` (LEFT x5), `disappear ROUTE25_MISTY`, - **`clearevent EVENT_TRAINERS_IN_CERULEAN_GYM`**, `setscene SCENE_ROUTE25_NOOP`, - `special RestartMapMusic`. -- `TrainerCooltrainermKevin` - not a `trainer` macro; a hand-written script. - `checkevent EVENT_BEAT_COOLTRAINERM_KEVIN` -> after-battle text; - `checkevent EVENT_CLEARED_NUGGET_BRIDGE` -> skip the prize; - otherwise `CooltrainermKevinNuggetText`, `verbosegiveitem NUGGET`, `iffalse .NoRoomForNugget`, - `setevent EVENT_CLEARED_NUGGET_BRIDGE`, then `winlosstext CooltrainermKevinBeatenText, 0`, - `loadtrainer COOLTRAINERM, KEVIN`, `startbattle`, `reloadmapafterbattle`, - `setevent EVENT_BEAT_COOLTRAINERM_KEVIN`. - A full bag at the Nugget step aborts before the battle. -- The six "six-pack" trainers are plain `trainer` macros with `EVENT_BEAT_*` flags only. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_25_MISTY_BOYFRIEND` | `constants/event_flags.asm:1296` | set at `engine/events/std_scripts.asm:549`; cleared by `CeruleanGymGruntRunsOutScript` | clear = the pair are standing at `46,9` / `46,10` | -| `SCENE_ROUTE25_MISTYS_DATE` (1) | `maps/Route25.asm:17` | `setmapscene` from `maps/CeruleanGym.asm:50` | arms the two coord events | -| `EVENT_TRAINERS_IN_CERULEAN_GYM` | `constants/event_flags.asm:1297` | cleared here | the gym becomes populated | -| `EVENT_CLEARED_NUGGET_BRIDGE` | `constants/event_flags.asm:215` | `TrainerCooltrainermKevin` | Nugget already handed over | -| `EVENT_BEAT_COOLTRAINERM_KEVIN` | `constants/event_flags.asm:873` | `TrainerCooltrainermKevin` | Kevin battle done | -| `EVENT_ROUTE_25_HIDDEN_POTION` | `constants/event_flags.asm:248` | `Route25HiddenPotion` | hidden Potion taken | -| `EVENT_ROUTE_25_PROTEIN` | `constants/event_flags.asm:1326` | item ball object row | Protein ball taken | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `POTION` | hidden, face `4,5` | `bg_event 4, 5, BGEVENT_ITEM, Route25HiddenPotion` | `EVENT_ROUTE_25_HIDDEN_POTION` | -| `NUGGET` | talk to Kevin at `37,8` | `TrainerCooltrainermKevin`, `verbosegiveitem NUGGET` | `EVENT_CLEARED_NUGGET_BRIDGE` | -| `PROTEIN` | item ball at `32,4` | `object_event ... OBJECTTYPE_ITEMBALL, 0, Route25Protein` | `EVENT_ROUTE_25_PROTEIN` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `SCHOOLBOY, DUDLEY` | `SCHOOLBOY` | 7 (`parties.asm:448`) | L35 ODDISH | `TrainerSchoolboyDudley` | none | -| `LASS, ELLEN` | `LASS` | 11 (`parties.asm:703`) | L30 WIGGLYTUFF, L34 GRANBULL | `TrainerLassEllen` | none | -| `SCHOOLBOY, JOE` | `SCHOOLBOY` | 8 (`parties.asm:453`) | L33 TANGELA, L33 VAPOREON | `TrainerSchoolboyJoe` | none | -| `LASS, LAURA` | `LASS` | 7 (`parties.asm:676`) | L28 GLOOM, L31 PIDGEOTTO, L31 BELLOSSOM | `TrainerLassLaura` | none | -| `CAMPER, LLOYD` | `CAMPER` | 6 (`parties.asm:2733`) | L34 NIDOKING | `TrainerCamperLloyd` | none | -| `LASS, SHANNON` | `LASS` | 8 (`parties.asm:683`) | L29 PARAS, L29 PARAS, L32 PARASECT | `TrainerLassShannon` | none | -| `SUPER_NERD, PAT` | `SUPER_NERD` | 8 (`parties.asm:2009`) | L36 PORYGON | `TrainerSupernerdPat` | none | -| `COOLTRAINERM, KEVIN` | `COOLTRAINERM` | 17 (`parties.asm:847`) | L38 RHYHORN, L35 CHARMELEON, L35 WARTORTLE | `TrainerCooltrainermKevin` | none | - -Base rewards: SCHOOLBOY 8, LASS 6, CAMPER 5, SUPER_NERD 8, COOLTRAINERM 12. - -**Wild encounters** - `data/wild/kanto_grass.asm:1089` `def_grass_wildmons ROUTE_25`, 10/10/10 percent. -morn: L8 PIDGEY, L10 BELLSPROUT, L8 VENONAT, L9 ABRA, L10 PIDGEOTTO, L14 WEEPINBELL, L14 WEEPINBELL. -day: L8 PIDGEY, L10 BELLSPROUT, L10 PIDGEY, L9 ABRA, L12 PIDGEOTTO, L14 WEEPINBELL, L14 WEEPINBELL. -nite: L8 VENONAT, L10 ODDISH, L10 VENOMOTH, L9 ABRA, L14 WEEPINBELL, L10 BELLSPROUT, L10 BELLSPROUT. -Water: `data/wild/kanto_water.asm:82`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING. - ---- - -### MAP_BILLS_HOUSE - -- Script: `maps/BillsHouse.asm` -- Blocks: shared, `data/maps/blocks.asm:163` (`BillsHouse_Blocks`) -- Header: `data/maps/maps.asm:215` -> `map BillsHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_25, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:197` -> `map_const BILLS_HOUSE, 4, 4` (group 7, id 11) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_25` | 1 | -| 2 | 3 | 7 | `ROUTE_25` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `BILLSHOUSE_GRAMPS` | `SPRITE_GRAMPS` | 2 | 3 | `SPRITEMOVEDATA_STANDING_UP` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `BillsGrandpa` (`pokegold.sym` `54:547c`) | -1 | - -**Scripts of interest** - -- `BillsGrandpa` - a five-step chain, each step gated on the previous. Order in the asm: - 1. LICKITUNG -> `EVENT_SHOWED_LICKITUNG_TO_BILLS_GRANDPA` -> `verbosegiveitem EVERSTONE` - (`EVENT_GOT_EVERSTONE_FROM_BILLS_GRANDPA`) - 2. ODDISH -> `EVENT_SHOWED_ODDISH_TO_BILLS_GRANDPA` -> `verbosegiveitem LEAF_STONE` - (`EVENT_GOT_LEAF_STONE_FROM_BILLS_GRANDPA`) - 3. STARYU -> `EVENT_SHOWED_STARYU_TO_BILLS_GRANDPA` -> `verbosegiveitem WATER_STONE` - (`EVENT_GOT_WATER_STONE_FROM_BILLS_GRANDPA`) - 4. GROWLITHE (Gold) / VULPIX (Silver, selected by `checkver` / `iftrue .AskVulpix`) -> - `EVENT_SHOWED_GROWLITHE_VULPIX_TO_BILLS_GRANDPA` -> `verbosegiveitem FIRE_STONE` - (`EVENT_GOT_FIRE_STONE_FROM_BILLS_GRANDPA`) - 5. PICHU -> `EVENT_SHOWED_PICHU_TO_BILLS_GRANDPA` -> `verbosegiveitem THUNDERSTONE` - (`EVENT_GOT_THUNDERSTONE_FROM_BILLS_GRANDPA`, which also ends the chain) - Mechanics: `writetext ... AskToSeeMonText`, `yesorno`, `scall .ExcitedToSee`, - `special BillsGrandfather` (party pick; `iffalse` = cancelled), `ifnotequal <SPECIES>, .WrongPokemon`. - `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` (`constants/event_flags.asm:5`) is set after each - successful hand-over, so he only accepts **one mon per visit** - a bot must leave and - re-enter the house between stones. -- First talk sets `EVENT_MET_BILLS_GRANDPA` (`constants/event_flags.asm:457`). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `EVERSTONE` | show LICKITUNG | `BillsGrandpa` | `EVENT_GOT_EVERSTONE_FROM_BILLS_GRANDPA` | -| `LEAF_STONE` | show ODDISH | `BillsGrandpa` | `EVENT_GOT_LEAF_STONE_FROM_BILLS_GRANDPA` | -| `WATER_STONE` | show STARYU | `BillsGrandpa` | `EVENT_GOT_WATER_STONE_FROM_BILLS_GRANDPA` | -| `FIRE_STONE` | show GROWLITHE (Gold) / VULPIX (Silver) | `BillsGrandpa` | `EVENT_GOT_FIRE_STONE_FROM_BILLS_GRANDPA` | -| `THUNDERSTONE` | show PICHU | `BillsGrandpa` | `EVENT_GOT_THUNDERSTONE_FROM_BILLS_GRANDPA` (`:467`) | - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Cut tree at the Route 9 west entrance (and the tree above Super Nerd Pat on Route 25) | `engine/events/overworld.asm:117` `CutFunction` -> `.CheckAble` (`ld de, ENGINE_HIVEBADGE` / `CheckBadge`, then `CheckMapForSomethingToCut` -> `engine/overworld/tile_events.asm:76` `CheckCutCollision`). The trees themselves are block data in `maps/Route9.blk` / `maps/Route25.blk`, not object events. | HIVEBADGE + a party mon with CUT | already held (Johto badge 2) | -| Surfing the Route 9 / Route 10 river to reach the Power Plant door at `3,9` | `engine/events/overworld.asm:322` `SurfFunction` `.TrySurf` (`ld de, ENGINE_FOGBADGE` / `CheckBadge`, then `GetTilePermission` == `WATER_TILE`); the overworld A-press path is `engine/events/overworld.asm:469` `TrySurfOW` with the same `ENGINE_FOGBADGE` `CheckEngineFlag` | FOGBADGE + SURF | already held (Johto badge 6) | -| "Fly back to Cerulean City" | `engine/events/overworld.asm:529` `FlyFunction` (`ld de, ENGINE_STORMBADGE` at `:545`), destination row `data/maps/flypoints.asm:21` | STORMBADGE + FLY + `ENGINE_FLYPOINT_CERULEAN` set by `CeruleanCityFlypointCallback` | flypoint flag set on first entry to Cerulean | -| Cerulean Gym is empty on the first visit | `maps/CeruleanGym.asm:379-383` object rows all carry `EVENT_TRAINERS_IN_CERULEAN_GYM`, set at `engine/events/std_scripts.asm:550` | - | `clearevent` in `Route25MistyDate1Script` / `Route25MistyDate2Script` (`maps/Route25.asm:48` / `:74`) | -| Rocket grunt does not exist in the gym | `maps/CeruleanGym.asm:378` object row `EVENT_CERULEAN_GYM_ROCKET`, set at `std_scripts.asm:547`; the scene is `SCENE_CERULEANGYM_NOOP` by default | - | first talk to `PowerPlantManager` (`clearevent` + `setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, `maps/PowerPlant.asm:146-148`) | -| Rocket grunt does not exist on Route 24 | `maps/Route24.asm:129` object row `EVENT_ROUTE_24_ROCKET`, set at `std_scripts.asm:548` | - | `clearevent` in `CeruleanGymGruntRunsOutScript` (`maps/CeruleanGym.asm:47`) | -| Misty + date do not exist on Route 25, and the two coord events are inert | `maps/Route25.asm:445-446` object rows `EVENT_ROUTE_25_MISTY_BOYFRIEND`; coord rows require `SCENE_ROUTE25_MISTYS_DATE` | - | `clearevent` + `setmapscene ROUTE_25, SCENE_ROUTE25_MISTYS_DATE` in `CeruleanGymGruntRunsOutScript` (`maps/CeruleanGym.asm:48`, `:50`) | -| Hidden MACHINE_PART at Cerulean Gym `3,8` yields nothing | `maps/CeruleanGym.asm:373` -> `CeruleanGymHiddenMachinePart`, flag set at `std_scripts.asm:546` | - | `clearevent EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` in `PowerPlantManager` (`maps/PowerPlant.asm:147`) | -| Route 5 Underground Path door at `17,15` physically blocked | `maps/Route5.asm:56` NPC standing on `17,16` | - | `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` in `PowerPlantManager` `.FoundMachinePart` (`maps/PowerPlant.asm:164`) - i.e. **after** this section | -| Bill's grandpa gives one stone per visit | `maps/BillsHouse.asm` `.JustShowedSomething` on `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` | - | leave and re-enter the map | - -## 4. Bot checklist - -Ordered, machine-actionable. `(g,m)` is `(map group, map id)` from `constants/map_constants.asm`. - -1. `ROUTE_5_SAFFRON_GATE (25,14)`: walk onto warp 1/2 (`4,0` / `5,0`) -> `ROUTE_5`. Pre: none. Post: none. -2. `ROUTE_5 (25,1)`: optional - walk to warp 4 (`10,11`), enter `ROUTE_5_CLEANSE_TAG_HOUSE (25,15)`, talk to `ROUTE5CLEANSETAGHOUSE_GRANNY` at `2,5`. Pre: `EVENT_GOT_CLEANSE_TAG` clear + free bag slot. Post: `EVENT_GOT_CLEANSE_TAG` set, CLEANSE_TAG in bag. Exit via warp 1/2 (`2,7`/`3,7`). -3. `ROUTE_5`: walk north across the connection into `CERULEAN_CITY (7,17)`. Post: `ENGINE_FLYPOINT_CERULEAN` set by `MAPCALLBACK_NEWMAP`. -4. `CERULEAN_CITY`: optional - face `2,12` and press A for BERSERK_GENE. Pre: `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` clear. -5. `CERULEAN_CITY`: heal at warp 4 (`19,21`, `CERULEAN_POKECENTER_1F`). Optional but the walkthrough does it twice. -6. `CERULEAN_CITY`: warp 5 (`30,23`) -> `CERULEAN_GYM (7,6)`. Expect an empty room (`EVENT_TRAINERS_IN_CERULEAN_GYM` set). Read `2,13` / `6,13` for the two notes. Exit via `4,15`/`5,15`. -7. `CERULEAN_CITY`: exit east into `ROUTE_9 (7,13)`. Pre: HIVEBADGE + CUT for the entrance tree. -8. `ROUTE_9`: battle in walkthrough order - `TrainerPicnickerEdna` (`9,10`), `TrainerCamperSid` (`11,2`), `TrainerCamperDean` (`23,11`), `TrainerHikerSidney` (`33,15`), `TrainerHikerTim` (`32,3`), `TrainerPicnickerHeidi` (`35,8`). Post: `EVENT_BEAT_*` per trainer. Optional hidden ETHER at `10,5`. -9. `ROUTE_9`: cross the south connection into `ROUTE_10_NORTH (7,14)`. Optional heal at warp 1 (`11,1`). -10. `ROUTE_10_NORTH`: Surf (pre: FOGBADGE + SURF) down the river and step onto warp 2 (`3,9`) -> `POWER_PLANT (7,10)`. -11. `POWER_PLANT`: walk to `POWERPLANT_MANAGER` at `14,10` and talk. Pre: `EVENT_MET_MANAGER_AT_POWER_PLANT` clear. Post: `EVENT_MET_MANAGER_AT_POWER_PLANT` set, `EVENT_CERULEAN_GYM_ROCKET` cleared, `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` cleared, `CERULEAN_GYM` scene = 1, `POWER_PLANT` scene = 1. -12. `POWER_PLANT`: walk over `5,12` on the way to the exit. Post: `PowerPlantGuardPhoneScript` runs, `POWER_PLANT` scene back to 0. Then exit via warp 1/2 (`2,17`/`3,17`). -13. FLY to `CERULEAN_CITY` (`SPAWN_CERULEAN` = `19,22`). Pre: STORMBADGE + FLY + `ENGINE_FLYPOINT_CERULEAN`. -14. `CERULEAN_CITY`: warp 5 -> `CERULEAN_GYM`. On load the scene script fires; hold through the movement/text (four A presses: intro, big-mistake, bye, plus the emote pauses). Post: `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` set, `EVENT_ROUTE_24_ROCKET` cleared, `EVENT_ROUTE_25_MISTY_BOYFRIEND` cleared, `ROUTE_25` scene = 1, `CERULEAN_GYM` scene = 0. -15. `CERULEAN_CITY`: exit north into `ROUTE_24 (7,15)`. Talk to `ROUTE24_ROCKET` at `8,7`. Battle `GRUNTM, GRUNTM_31` (L30 GOLBAT). Post: `disappear` sets `EVENT_ROUTE_24_ROCKET`. -16. `ROUTE_24`: north connection into `ROUTE_25 (7,16)`. -17. `ROUTE_25`: optional hidden POTION - face `4,5`, press A. Pre: `EVENT_ROUTE_25_HIDDEN_POTION` clear. -18. `ROUTE_25`: battle `TrainerSchoolboyDudley` (`12,8`), `TrainerLassEllen` (`16,11`), `TrainerSchoolboyJoe` (`21,8`), `TrainerLassLaura` (`22,6`), `TrainerCamperLloyd` (`25,4`), `TrainerLassShannon` (`28,11`), `TrainerSupernerdPat` (`31,7`). -19. `ROUTE_25`: talk to `ROUTE25_COOLTRAINER_M2` at `37,8`. Pre: free bag slot. Post: NUGGET, `EVENT_CLEARED_NUGGET_BRIDGE`, then battle `COOLTRAINERM, KEVIN` and `EVENT_BEAT_COOLTRAINERM_KEVIN`. -20. `ROUTE_25`: CUT the tree, walk onto the item ball at `32,4` for PROTEIN. Post: `EVENT_ROUTE_25_PROTEIN`. -21. `ROUTE_25`: walk east onto `42,6` (or `42,7`). Pre: `ROUTE_25` scene = `SCENE_ROUTE25_MISTYS_DATE`. Post: date cutscene, `EVENT_TRAINERS_IN_CERULEAN_GYM` cleared, `ROUTE_25` scene = 0. -22. `ROUTE_25`: warp 1 (`47,5`) -> `BILLS_HOUSE (7,11)`. Talk to `BILLSHOUSE_GRAMPS` at `2,3`. One stone per map load; loop enter/talk/exit until the wanted stones are collected. Post: `EVENT_MET_BILLS_GRANDPA`, then the `EVENT_GOT_*_FROM_BILLS_GRANDPA` chain. -23. FLY to `CERULEAN_CITY`, heal at warp 4, then warp 5 -> `CERULEAN_GYM`. -24. `CERULEAN_GYM`: battle `TrainerSwimmerfBriana` (`1,9`), then face `3,8` and press A for MACHINE_PART. Pre: `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` clear + free bag slot. Post: flag set, MACHINE_PART in bag. -25. `CERULEAN_GYM`: battle `TrainerSwimmermParker` (`8,9`), `TrainerSwimmerfDiana` (`4,6`). -26. `CERULEAN_GYM`: talk to `CERULEANGYM_MISTY` at `5,3`. Pre: `ENGINE_CASCADEBADGE` clear. Post: `EVENT_BEAT_MISTY`, `EVENT_BEAT_SWIMMERF_DIANA`, `EVENT_BEAT_SWIMMERF_BRIANA`, `EVENT_BEAT_SWIMMERM_PARKER`, `ENGINE_CASCADEBADGE` set, +4700 money. - -Follow-up (next section): carry MACHINE_PART back to `POWERPLANT_MANAGER` at `14,10` to set -`EVENT_RETURNED_MACHINE_PART` / `EVENT_RESTORED_POWER_TO_KANTO` and collect TM07 ZAP CANNON. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Kanto map headers, warps, coord/bg/object event tables extracted from ROM | `src/import/RomExtractorGen2.lua` (`OBJECTTYPE_ITEMBALL, OBJECTTYPE_TRAINER = 1, 2` at :71; branch at :2960-2968) | implemented - generic, all map groups, nothing Kanto-specific needed | -| Coord-event trip-wires (Power Plant `5,12`, Route 25 `42,6`/`42,7`) with the scene id filter | `src/world/gen2/World.lua:5006` `World:tryCoordScript` | implemented | -| Scene scripts on map load + `sdefer` (`CeruleanGymGruntRunsOutScene`) | `src/world/gen2/World.lua` `World:trySceneScript`; `src/script/gen2/Vm.lua:92` | partial - `sdefer` is run immediately rather than queued until the map settles (comment at `Vm.lua:93` says so). Cosmetic ordering risk on the gym grunt scene. | -| `setscene` / `setmapscene` / `checkmapscene` | `src/script/gen2/Vm.lua:274`, `:279`, `:284` | implemented, including the `0xff` "no scene var" sentinel | -| `checkevent` / `setevent` / `clearevent` and `checkflag` / `setflag` (`ENGINE_CASCADEBADGE`, `ENGINE_FLYPOINT_CERULEAN`) | `src/script/gen2/Vm.lua:198-229`, `engineFlags` store at `:1661` | implemented - generic id-keyed stores, no per-flag work needed | -| Hidden items (`BGEVENT_ITEM`): MACHINE_PART, ETHER, POTION, BERSERK_GENE | `src/world/gen2/HiddenItems.lua` | implemented (the header notes this was previously unreachable because `World:bgEventAt` only answered `BGEVENT_READ`) | -| Item ball (`Route25Protein`) | `src/import/RomExtractorGen2.lua:2968` (`OBJECTTYPE_ITEMBALL`) | implemented | -| Trainer sight lines / `OBJECTTYPE_TRAINER` sight radius | `src/world/gen2/Trainers.lua:98` `Trainers.sees` | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `dontrestartmapmusic` / `endifjustbattled` | `src/script/gen2/Vm.lua` | implemented | -| Prize money = base x level x 4 (Misty 4700G etc.) | `src/battle/gen2/Prize.lua:12` (documents the `ld c, 4` loop), `Prize.reward` at `:82` | implemented, incl. Amulet Coin and Mom's savings split | -| CUT gate on HIVEBADGE + cut-tree collision | `src/world/gen2/FieldMoves.lua:440` `badgeGate` / `:452`; `src/world/gen2/Permissions.lua:130` `CUT_TREE`, `:152` `CheckCutCollision` set | implemented | -| SURF gate on FOGBADGE | `src/world/gen2/FieldMoves.lua:482` | implemented | -| FLY gate on STORMBADGE + flypoint table | `src/world/gen2/FieldMoves.lua:342` `FieldMoves.FLYPOINTS`, `:501`; `src/world/gen2/World.lua:4284` (`_FlyMap`) | implemented | -| `special BillsGrandfather` (party pick for the stone chain) | `src/script/gen2/Specials.lua:1267` | implemented | -| `special FadeOutMusic` / `FadeOutToBlack` / `FadeInFromBlack` / `ReloadSpritesNoPalettes` / `RestartMapMusic` (Route 24 grunt exit, gym grunt scene) | `src/script/gen2/Specials.lua:999`, `:1001`, `:1025`, `:1059`, `:1066` | implemented | -| `showemote`, `applymovement` (incl. `big_step`, `jump_step`, `fix_facing`, `set_sliding`, `slow_step`), `turnobject`, `disappear`/`appear` | `src/script/gen2/Vm.lua`, `src/script/gen2/Movement.lua` | implemented | -| Kanto Gen 2 wild tables (`kanto_grass`/`kanto_water`, Gold/Silver split on Route 9) | extracted by `src/import/RomExtractorGen2.lua`; consumed by `src/battle/gen2/Encounter.lua` | implemented - version split comes from the ROM, so it is correct by construction | -| End-to-end driver coverage for this stretch | `tests/drivers/gold_*.lua` (25 drivers: boot, walk, warp, trainer, battle, roamers, ice path, etc.) | missing - no Kanto / Cerulean / Power Plant driver exists | - -## 6. Unresolved / verify by hand - -- **Cut tree positions.** The walkthrough says "Cut the tree at the entrance" (Route 9) and - "Cut the tree above Pat" (Route 25). Cut trees are block data in `maps/Route9.blk` / - `maps/Route25.blk`, not events, so no coordinate for them appears anywhere in the asm text. - A bot needs the decoded block map (or the port's `Permissions.isCutTree` over the loaded - collision) to find them. The Protein ball at `32,4` sitting two cells above Pat at `31,7` - is consistent with the walkthrough's claim, but the tree cell itself is unverified here. -- **Kevin's battle is omitted from the walkthrough.** The text says only "that guy will give - you a Nugget". `TrainerCooltrainermKevin` gives the NUGGET and then immediately battles you - with L38 RHYHORN / L35 CHARMELEON / L35 WARTORTLE. Treat the walkthrough as incomplete here, - not the asm. -- **Wild lists are abridged in the walkthrough.** Route 24 also has VENONAT / SUNKERN / - ODDISH / VENOMOTH, and Route 25 also has PIDGEY / PIDGEOTTO / VENONAT / WEEPINBELL / - ODDISH / VENOMOTH, none of which the FAQ lists. Route 5's own table (PIDGEY / BELLSPROUT / - ABRA / ODDISH / GLOOM) is not listed at all. -- **"Surf down the river ... and you'll be at the Power Plant."** The Power Plant door is - `warp_event 3, 9` on `ROUTE_10_NORTH`, and the water table for both `ROUTE_9` and - `ROUTE_10_NORTH` exists, so surfing is clearly the route - but the exact water path across - the Route 9 / Route 10 North connection boundary is block data and was not traced. -- **EXP values.** Every per-mon EXP number in the walkthrough (e.g. "Level 42 Golduck - (1566 EXP)") depends on the player's own level and participation; nothing in - `data/trainers/parties.asm` carries them. Money values were all reproduced from - `data/trainers/attributes.asm` base reward x last-mon level x 4 and check out. -- **"the 11th gym badge".** The asm has no badge counter tied to this; `ENGINE_CASCADEBADGE` - is just one bit in `constants/engine_flags.asm:48`. The ordinal is walkthrough prose. -- **Route 5 "Day Care Center from Red, Blue and Yellow".** The building is - `ROUTE_5_CLEANSE_TAG_HOUSE` and its sign at `10,11` reads "House for Sale... Nobody lives - here." Nothing in Gen 2 calls it a Day Care; that identification is the FAQ's own history - note. Also note the sign and the door share the tile `10,11`, so an A-press there may hit - either depending on facing - worth verifying against the port's bg-event-vs-warp priority. -- **`POWERPLANT_GYM_GUIDE3` / `PowerPlantGymGuide4Script` naming mismatch** in - `maps/PowerPlant.asm` (there is no `PowerPlantGymGuide3Script`). Harmless, but a - label-driven bot should not assume the const and script numbers line up. diff --git a/docs/gold-walkthrough/asm-walk/section-22-rock-tunnel-and-celadon-city-gym.md b/docs/gold-walkthrough/asm-walk/section-22-rock-tunnel-and-celadon-city-gym.md deleted file mode 100644 index 3e1d035f..00000000 --- a/docs/gold-walkthrough/asm-walk/section-22-rock-tunnel-and-celadon-city-gym.md +++ /dev/null @@ -1,1277 +0,0 @@ -# Section 22 - Rock Tunnel and Celadon City Gym - -Source: `../section-22-rock-tunnel-and-celadon-city-gym.txt` -(the FAQ numbers this chapter "28 > Rock Tunnel and Celadon City Gym"; the file -index is 22) - -Maps covered: `MAP_POWER_PLANT`, `MAP_ROCK_TUNNEL_1F`, `MAP_ROCK_TUNNEL_B1F`, -`MAP_ROUTE_10_SOUTH`, `MAP_LAVENDER_TOWN`, `MAP_LAV_RADIO_TOWER_1F`, -`MAP_SOUL_HOUSE`, `MAP_ROUTE_8`, `MAP_ROUTE_8_SAFFRON_GATE`, -`MAP_SAFFRON_CITY`, `MAP_COPYCATS_HOUSE_1F`, `MAP_COPYCATS_HOUSE_2F`, -`MAP_VERMILION_CITY`, `MAP_POKEMON_FAN_CLUB`, `MAP_SAFFRON_MAGNET_TRAIN_STATION`, -`MAP_ROUTE_7_SAFFRON_GATE`, `MAP_ROUTE_7`, `MAP_CELADON_CITY`, -`MAP_CELADON_MANSION_1F`, `MAP_CELADON_MANSION_2F`, `MAP_CELADON_MANSION_3F`, -`MAP_CELADON_MANSION_ROOF`, `MAP_CELADON_MANSION_ROOF_HOUSE`, -`MAP_CELADON_DEPT_STORE_1F`..`6F`, `MAP_CELADON_GAME_CORNER`, -`MAP_CELADON_GAME_CORNER_PRIZE_ROOM`, `MAP_CELADON_CAFE`, `MAP_CELADON_GYM` - -Badges / key milestones in this section: - -- TM07 Zap Cannon from the Power Plant MANAGER (reward for `EVENT_RETURNED_MACHINE_PART`) -- EXPN CARD from the Lavender radio director (`ENGINE_EXPN_CARD`) -- LOST_ITEM -> PASS chain (Copycat / Pokemon Fan Club), unlocking the Magnet Train -- TM03 Curse (Celadon Mansion roof house, night only) -- Leftovers (Celadon Cafe trash can) -- **RAINBOWBADGE** + TM19 Giga Drain from ERIKA (`ENGINE_RAINBOWBADGE`) - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `ROUTE_9` / `ROUTE_10_NORTH` | `maps/Route10North.asm` | Route 9 south connection (`data/maps/attributes.asm` `connection south, Route10North, ROUTE_10_NORTH, 20`) | warp 2 at (3, 9) -> `POWER_PLANT` 1 | Surf east from Route 9 to the Power Plant. **Route 9 / Route 10 North belong to the previous section; only the hop is listed here.** | -| 1 | `POWER_PLANT` | `maps/PowerPlant.asm` | warp 1/2 at (2,17)/(3,17) from `ROUTE_10_NORTH` 2 | same warps back | Talk to `PowerPlantManager` (object at 14,10) for **TM07 Zap Cannon** | -| 2 | `ROUTE_10_NORTH` | `maps/Route10North.asm` | Power Plant warp | south connection to `ROUTE_10_SOUTH`, or `ROCK_TUNNEL_1F` via Route 9 warp 1 | Walk back to the Rock Tunnel mouth | -| 3 | `ROCK_TUNNEL_1F` | `maps/RockTunnel1F.asm` | warp 1 at (15,3) from `ROUTE_9` 1 | warp 2 at (11,25) -> `ROUTE_10_SOUTH` 1 | Flash through the tunnel; TM47 Steel Wing + Elixer here | -| 4 | `ROCK_TUNNEL_B1F` | `maps/RockTunnelB1F.asm` | 1F warps 3/4/5/6 | back up to 1F | Iron, PP Up, Revive, hidden Max Potion | -| 5 | `ROUTE_10_SOUTH` | `maps/Route10South.asm` | warp 1 at (6,1) from `ROCK_TUNNEL_1F` 2 | south connection to `LAVENDER_TOWN` | Hiker Jim + Pokefanm Robert | -| 6 | `LAVENDER_TOWN` | `maps/LavenderTown.asm` | north connection from `ROUTE_10_SOUTH` | warp 7 at (14,5) -> `LAV_RADIO_TOWER_1F` 1; west connection to `ROUTE_8` | Sets `ENGINE_FLYPOINT_LAVENDER`; radio tower / Soul House / Name Rater | -| 7 | `LAV_RADIO_TOWER_1F` | `maps/LavRadioTower1F.asm` | Lavender warp 7 | warps 1/2 at (2,7)/(3,7) back | Director (`LAVRADIOTOWER1F_GENTLEMAN`) gives the **EXPN CARD** | -| 8 | `ROUTE_8` | `maps/Route8.asm` | east connection from `LAVENDER_TOWN` | warps 1/2 at (4,4)/(4,5) -> `ROUTE_8_SAFFRON_GATE` 3/4 | Five trainers (2 Super Nerds, 3 Bikers) + PRZCUREBERRY tree | -| 9 | `SAFFRON_CITY` | `maps/SaffronCity.asm` | `ROUTE_8_SAFFRON_GATE` warps 1/2 | warp 8 at (9,11) -> `COPYCATS_HOUSE_1F` 1 | Heal, then visit the Copycat | -| 10 | `COPYCATS_HOUSE_1F` -> `2F` | `maps/CopycatsHouse1F.asm`, `maps/CopycatsHouse2F.asm` | 1F warp 3 at (2,0) -> `COPYCATS_HOUSE_2F` 1 | 2F warp 1 at (3,0) back | First `Copycat` talk sets `EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM` | -| 11 | `VERMILION_CITY` | `maps/VermilionCity.asm` | Fly | warp 3 at (7,13) -> `POKEMON_FAN_CLUB` 1 | The CLEFAIRY-doll guy hands over `LOST_ITEM` | -| 12 | `POKEMON_FAN_CLUB` | `maps/PokemonFanClub.asm` | Vermilion warp 3 | warps 1/2 at (2,7)/(3,7) back | `PokemonFanClubClefairyGuyScript` -> `giveitem LOST_ITEM` | -| 13 | `SAFFRON_CITY` -> `COPYCATS_HOUSE_2F` | as above | Fly + warps | - | Return `LOST_ITEM`, receive `PASS` | -| 14 | `ROUTE_7_SAFFRON_GATE` | `maps/Route7SaffronGate.asm` | `SAFFRON_CITY` warps 10/11 at (0,24)/(0,25) | warps 1/2 at (0,4)/(0,5) -> `ROUTE_7` 1/2 | The "Pokedex as ID" guard | -| 15 | `ROUTE_7` | `maps/Route7.asm` | gate warps 1/2 at (15,6)/(15,7) | west connection to `CELADON_CITY` | No trainers; sealed Underground Path signs | -| 16 | `CELADON_CITY` | `maps/CeladonCity.asm` | east connection from `ROUTE_7` (`connection west, CeladonCity, CELADON_CITY, -5`) | nine warps, see below | Sets `ENGINE_FLYPOINT_CELADON` | -| 17 | `CELADON_MANSION_1F/2F/3F/ROOF/ROOF_HOUSE` | `maps/CeladonMansion*.asm` | city warps 2 (16,9) and 3/4 (16,3)/(17,3) | roof house warps | **TM03 Curse** (night), Game Freak dev room | -| 18 | `CELADON_DEPT_STORE_1F`..`6F` | `maps/CeladonDeptStore*.asm` | city warp 1 at (4,9) | store warps / elevator | Mart inventories | -| 19 | `CELADON_GAME_CORNER` / `..._PRIZE_ROOM` | `maps/CeladonGameCorner.asm`, `maps/CeladonGameCornerPrizeRoom.asm` | city warps 6 (18,19) and 7 (23,19) | back to city | Slots, then TM/mon prize counters | -| 20 | `CELADON_CAFE` | `maps/CeladonCafe.asm` | city warp 9 at (25,29) | warps 1/2 at (6,7)/(7,7) back | **Leftovers** from `bg_event 7, 1` trash can | -| 21 | `CELADON_GYM` | `maps/CeladonGym.asm` | city warp 8 at (10,29) | warps 1/2 at (4,17)/(5,17) back | Four trainers then **ERIKA** | - -Spillover: the walkthrough's next beat (leaving Celadon westward toward Cycling -Road / Fuchsia) belongs to the following section and is not covered here. - ---- - -## 2. Maps - -### MAP_POWER_PLANT - -- Script: `maps/PowerPlant.asm` -- Blocks: `maps/PowerPlant.blk` -- Header: `data/maps/maps.asm:214` -> `TILESET_FACILITY, INDOOR, LANDMARK_POWER_PLANT, MUSIC_VIRIDIAN_CITY, FALSE (no phone), PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:196` `map_const POWER_PLANT, 10, 9` (group `CERULEAN`, id 10) -- Attributes / border: `data/maps/attributes.asm:523` `map_attributes PowerPlant, POWER_PLANT, $00`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 17 | `ROUTE_10_NORTH` | 2 | -| 2 | 3 | 17 | `ROUTE_10_NORTH` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` | 5 | 12 | `PowerPlantGuardPhoneScript` | Officer 1 gets the Cerulean call cutscene, then `setscene SCENE_POWERPLANT_NOOP` | - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `POWERPLANT_OFFICER1` | `SPRITE_OFFICER` | 4 | 14 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficerScript` | -1 | -| `POWERPLANT_GYM_GUIDE1` | `SPRITE_GYM_GUIDE` | 2 | 9 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide1Script` | -1 | -| `POWERPLANT_GYM_GUIDE2` | `SPRITE_GYM_GUIDE` | 6 | 11 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide2Script` | -1 | -| `POWERPLANT_OFFICER2` | `SPRITE_OFFICER` | 9 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficer2Script` | -1 | -| `POWERPLANT_GYM_GUIDE3` | `SPRITE_GYM_GUIDE` | 7 | 2 | `WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide4Script` | -1 | -| `POWERPLANT_MANAGER` | `SPRITE_FISHER` | 14 | 10 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantManager` | -1 | - -**Scripts of interest** - -- `PowerPlantManager` (`54:4dbd`). Branch order is: `checkevent EVENT_RETURNED_MACHINE_PART` -> `.ReturnedMachinePart`; else `checkitem MACHINE_PART` -> `.FoundMachinePart`; else `checkevent EVENT_MET_MANAGER_AT_POWER_PLANT` -> `.MetManager`; else the first-meeting text which does `setevent EVENT_MET_MANAGER_AT_POWER_PLANT`, `clearevent EVENT_CERULEAN_GYM_ROCKET`, `clearevent EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM`, `setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, `setscene SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL`. - `.FoundMachinePart` does `takeitem MACHINE_PART`, `setevent EVENT_RETURNED_MACHINE_PART`, `clearevent EVENT_SAFFRON_TRAIN_STATION_POPULATION`, `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`, `setevent EVENT_ROUTE_24_ROCKET`, `setevent EVENT_RESTORED_POWER_TO_KANTO`, `clearevent EVENT_GOLDENROD_TRAIN_STATION_GENTLEMAN`, then falls through to `.ReturnedMachinePart`. - `.ReturnedMachinePart` gates on `EVENT_GOT_TM07_ZAP_CANNON`; otherwise `verbosegiveitem TM_ZAP_CANNON` and `setevent EVENT_GOT_TM07_ZAP_CANNON`. - - **Bot note:** the section as written assumes `EVENT_RETURNED_MACHINE_PART` is - already set from the previous section. If it is, the TM is one `A` press on - the manager. If the part is still in the bag, the same talk both takes the - part and awards the TM in one conversation. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | set by `PowerPlantManager.FoundMachinePart`; read by every other NPC on this map, `LavRadioTower1FGentlemanScript`, `Copycat`, `CopycatsHouse1FPokefanFScript`, `PokemonFanClubClefairyGuyScript`, `Route7SaffronGuardScript`, `SaffronMagnetTrainStationGymGuideScript` | the master gate for this whole chapter | -| `EVENT_MET_MANAGER_AT_POWER_PLANT` | `constants/event_flags.asm:201` | set/read by `PowerPlantManager` | first-meeting bookkeeping | -| `EVENT_RESTORED_POWER_TO_KANTO` | `constants/event_flags.asm:204` | set by `PowerPlantManager.FoundMachinePart`; read by `SaffronMagnetTrainStationOfficerScript` | Magnet Train power gate | -| `EVENT_GOT_TM07_ZAP_CANNON` | `constants/event_flags.asm:222` | `PowerPlantManager` | one-time TM | -| `SCENE_POWERPLANT_NOOP` / `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` | scene ids in `PowerPlant_MapScripts` `def_scene_scripts` | `setscene` in `PowerPlantManager`, coord event at (5,12) | scene 1 makes the (5,12) trip-wire live | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_ZAP_CANNON` (TM07) | talk to `POWERPLANT_MANAGER` at (14,10) | `PowerPlantManager` -> `verbosegiveitem TM_ZAP_CANNON` | `EVENT_GOT_TM07_ZAP_CANNON` | - -**Trainers** - none. - -**Wild encounters** - none (INDOOR, no `def_grass_wildmons` entry). - ---- - -### MAP_ROCK_TUNNEL_1F - -- Script: `maps/RockTunnel1F.asm` -- Blocks: `maps/RockTunnel1F.blk` -- Header: `data/maps/maps.asm:156` -> `TILESET_DARK_CAVE, CAVE, LANDMARK_ROCK_TUNNEL, MUSIC_MT_MOON, TRUE (phone/"can be called from"), PALETTE_DARK, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:143` `map_const ROCK_TUNNEL_1F, 15, 18` (group `DUNGEONS`, id 78) -> 30 x 36 cells -- Attributes: `data/maps/attributes.asm:490` `map_attributes RockTunnel1F, ROCK_TUNNEL_1F, $09`; no connections -- `PALETTE_DARK` is what makes `FlashFunction` legal here (see section 3) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 3 | `ROUTE_9` | 1 | -| 2 | 11 | 25 | `ROUTE_10_SOUTH` | 1 | -| 3 | 5 | 3 | `ROCK_TUNNEL_B1F` | 3 | -| 4 | 15 | 9 | `ROCK_TUNNEL_B1F` | 2 | -| 5 | 27 | 3 | `ROCK_TUNNEL_B1F` | 4 | -| 6 | 27 | 13 | `ROCK_TUNNEL_B1F` | 1 | - -**Coord events** - none (`def_coord_events` empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 24 | 4 | `BGEVENT_ITEM` | `RockTunnel1FHiddenXAccuracy` -> `hiddenitem X_ACCURACY, EVENT_ROCK_TUNNEL_1F_HIDDEN_X_ACCURACY` | -| 21 | 15 | `BGEVENT_ITEM` | `RockTunnel1FHiddenXDefend` -> `hiddenitem X_DEFEND, EVENT_ROCK_TUNNEL_1F_HIDDEN_X_DEFEND` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROCKTUNNEL1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 4 | 18 | `STILL` | `OBJECTTYPE_ITEMBALL` | `RockTunnel1FElixer` (`itemball ELIXER`) | `EVENT_ROCK_TUNNEL_1F_ELIXER` | -| `ROCKTUNNEL1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 10 | 15 | `STILL` | `OBJECTTYPE_ITEMBALL` | `RockTunnel1FTMSteelWing` (`itemball TM_STEEL_WING`, `42:58df`) | `EVENT_ROCK_TUNNEL_1F_TM_STEEL_WING` | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `ELIXER` | ball at (4,18) | `RockTunnel1FElixer` | `EVENT_ROCK_TUNNEL_1F_ELIXER` | -| `TM_STEEL_WING` (TM47) | ball at (10,15) | `RockTunnel1FTMSteelWing` | `EVENT_ROCK_TUNNEL_1F_TM_STEEL_WING` | -| `X_ACCURACY` | hidden, face (24,4) | `bg_event` `BGEVENT_ITEM` | `EVENT_ROCK_TUNNEL_1F_HIDDEN_X_ACCURACY` | -| `X_DEFEND` | hidden, face (21,15) | `bg_event` `BGEVENT_ITEM` | `EVENT_ROCK_TUNNEL_1F_HIDDEN_X_DEFEND` | - -**Trainers** - none. - -**Wild encounters** - `data/wild/kanto_grass.asm`, `def_grass_wildmons ROCK_TUNNEL_1F`, -rates `6 percent, 6 percent, 6 percent` (morn/day/nite). All three time slots are -identical: - -``` -db 10, CUBONE -db 10, GEODUDE -db 12, MACHOP -db 8, ZUBAT -db 14, MACHOKE -db 13, CUBONE -db 13, CUBONE -``` - ---- - -### MAP_ROCK_TUNNEL_B1F - -- Script: `maps/RockTunnelB1F.asm` -- Blocks: `maps/RockTunnelB1F.blk` -- Header: `data/maps/maps.asm:157` -> `TILESET_DARK_CAVE, CAVE, LANDMARK_ROCK_TUNNEL, MUSIC_MT_MOON, TRUE, PALETTE_DARK, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:144` `map_const ROCK_TUNNEL_B1F, 15, 18` -- Attributes: `data/maps/attributes.asm:491` `map_attributes RockTunnelB1F, ROCK_TUNNEL_B1F, $09`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `ROCK_TUNNEL_1F` | 6 | -| 2 | 17 | 9 | `ROCK_TUNNEL_1F` | 4 | -| 3 | 23 | 3 | `ROCK_TUNNEL_1F` | 3 | -| 4 | 25 | 23 | `ROCK_TUNNEL_1F` | 5 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 14 | `BGEVENT_ITEM` | `RockTunnelB1FHiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_ROCK_TUNNEL_B1F_HIDDEN_MAX_POTION` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROCKTUNNELB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 25 | `STILL` | `OBJECTTYPE_ITEMBALL` | `RockTunnelB1FIron` (`42:5931`) | `EVENT_ROCK_TUNNEL_B1F_IRON` | -| `ROCKTUNNELB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 17 | `STILL` | `OBJECTTYPE_ITEMBALL` | `RockTunnelB1FPPUp` (`42:5933`) | `EVENT_ROCK_TUNNEL_B1F_PP_UP` | -| `ROCKTUNNELB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 15 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `RockTunnelB1FRevive` (`42:5935`) | `EVENT_ROCK_TUNNEL_B1F_REVIVE` | - -**Wild encounters** - `data/wild/kanto_grass.asm`, `def_grass_wildmons ROCK_TUNNEL_B1F`, -rates `6 percent` in all three slots, and all three slots identical: - -``` -db 12, CUBONE -db 12, GEODUDE -db 16, ONIX -db 10, ZUBAT -db 14, MAROWAK -db 14, KANGASKHAN -db 14, KANGASKHAN -``` - -(Kangaskhan is a B1F-only slot 6/7 encounter - the walkthrough lists it under -"Rock Tunnel" generally.) - ---- - -### MAP_ROUTE_10_SOUTH - -- Script: `maps/Route10South.asm` -- Blocks: `maps/Route10South.blk` -- Header: `data/maps/maps.asm:380` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_10, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:351` `map_const ROUTE_10_SOUTH, 10, 9` (group `LAVENDER`, id 3) -- Connections (`data/maps/attributes.asm`, `map_attributes Route10South, ROUTE_10_SOUTH, $2c`): - `connection north, Route10North, ROUTE_10_NORTH, 0`, `connection south, LavenderTown, LAVENDER_TOWN, 0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 1 | `ROCK_TUNNEL_1F` | 2 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 3 | `BGEVENT_READ` | `Route10Sign` (`jumptext Route10SignText`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE10SOUTH_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 17 | 3 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerHikerJim` (`50:574b`) | -1 | -| `ROUTE10SOUTH_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 4 | 10 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerPokefanmRobert` (`50:575f`) | -1 | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `HIKER`, `JIM` | Hiker (`constants/trainer_constants.asm:453`) | JIM | `"JIM@", TRAINERTYPE_NORMAL` / `db 35, MACHAMP` (line 110) | `TrainerHikerJim`, flag `EVENT_BEAT_HIKER_JIM` (`constants/event_flags.asm:834`) | no | -| `POKEFANM`, `ROBERT` | Pokefanm (`:601`) | ROBERT | `"ROBERT@", TRAINERTYPE_ITEM` / `db 33, QUAGSIRE, BERRY` (line 2971) | `TrainerPokefanmRobert`, flag `EVENT_BEAT_POKEFANM_ROBERT` (`:726`) | no | - -Class base rewards: Hiker `db 8`, Pokefanm `db 20` (`data/trainers/attributes.asm`). - -**Wild encounters** - **none**. There is no `def_grass_wildmons ROUTE_10_SOUTH` -entry in `data/wild/kanto_grass.asm` (the grass patch is on `ROUTE_10_NORTH`). - ---- - -### MAP_LAVENDER_TOWN - -- Script: `maps/LavenderTown.asm` -- Blocks: `maps/LavenderTown.blk` -- Header: `data/maps/maps.asm:381` -> `TILESET_KANTO, TOWN, LANDMARK_LAVENDER_TOWN, MUSIC_LAVENDER_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:352` `map_const LAVENDER_TOWN, 10, 9` -- Connections (`map_attributes LavenderTown, LAVENDER_TOWN, $2c`): - `connection north, Route10South, ROUTE_10_SOUTH, 0`, - `connection south, Route12, ROUTE_12, 0`, - `connection west, Route8, ROUTE_8, 0` - -**Callbacks**: `callback MAPCALLBACK_NEWMAP, LavenderTownFlypointCallback` -> -`setflag ENGINE_FLYPOINT_LAVENDER` (`constants/engine_flags.asm:73`). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `LAVENDER_POKECENTER_1F` | 1 | -| 2 | 5 | 9 | `MR_FUJIS_HOUSE` | 1 | -| 3 | 3 | 13 | `LAVENDER_SPEECH_HOUSE` | 1 | -| 4 | 7 | 13 | `LAVENDER_NAME_RATER` | 1 | -| 5 | 1 | 5 | `LAVENDER_MART` | 2 | -| 6 | 13 | 11 | `SOUL_HOUSE` | 1 | -| 7 | 14 | 5 | `LAV_RADIO_TOWER_1F` | 1 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 3 | `BGEVENT_READ` | `LavenderTownSign` | -| 15 | 7 | `BGEVENT_READ` | `KantoRadioStationSign` | -| 3 | 9 | `BGEVENT_READ` | `VolunteerPokemonHouseSign` | -| 15 | 13 | `BGEVENT_READ` | `SoulHouseSign` | -| 6 | 5 | `BGEVENT_READ` | `LavenderPokecenterSignText` (`jumpstd PokecenterSignScript`) | -| 2 | 5 | `BGEVENT_READ` | `LavenderMartSignText` (`jumpstd MartSignScript`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LAVENDERTOWN_POKEFAN_M` | `SPRITE_POKEFAN_M` | 12 | 7 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `LavenderTownPokefanMScript` | -1 | -| `LAVENDERTOWN_TEACHER` | `SPRITE_TEACHER` | 2 | 15 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `LavenderTownTeacherScript` | -1 | -| `LAVENDERTOWN_GRAMPS` | `SPRITE_GRAMPS` | 14 | 12 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `LavenderTownGrampsScript` | -1 | -| `LAVENDERTOWN_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 11 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `LavenderTownYoungsterScript` | -1 | - -**Notes for the walkthrough's Lavender claims** - -- Mr. Fuji is `MrFuji`, `object_event 4, 2, SPRITE_GRAMPS` in `maps/SoulHouse.asm:85` - (warp 6 of Lavender Town), so the walkthrough is right that he is in the Soul - House. `MR_FUJIS_HOUSE` (warp 2, `maps/MrFujisHouse.asm`) is the *Volunteer - Pokemon House* and contains no Fuji object. -- Name Rater is warp 4 -> `LAVENDER_NAME_RATER` (`maps/LavenderNameRater.asm`). - -**Wild encounters** - none in town. - ---- - -### MAP_LAV_RADIO_TOWER_1F - -- Script: `maps/LavRadioTower1F.asm` -- Header: `data/maps/maps.asm:389` -> `TILESET_RADIO_TOWER, INDOOR, LANDMARK_LAV_RADIO_TOWER, MUSIC_LAVENDER_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:360` `map_const LAV_RADIO_TOWER_1F, 10, 4` -- Attributes: `data/maps/attributes.asm:620` `map_attributes LavRadioTower1F, LAV_RADIO_TOWER_1F, $00` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `LAVENDER_TOWN` | 7 | -| 2 | 3 | 7 | `LAVENDER_TOWN` | 7 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 0 | `BGEVENT_READ` | `LavRadioTower1FDirectory` | -| 5 | 0 | `BGEVENT_READ` | `LavRadioTower1FPokeFluteSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `LAVRADIOTOWER1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 6 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `LavRadioTower1FReceptionistScript` | -1 | -| `LAVRADIOTOWER1F_OFFICER` | `SPRITE_OFFICER` | 15 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `LavRadioTower1FOfficerScript` | -1 | -| `LAVRADIOTOWER1F_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 1 | 3 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `LavRadioTower1FSuperNerd1Script` | -1 | -| `LAVRADIOTOWER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 9 | 1 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `LavRadioTower1FGentlemanScript` (`5d:479f`) | -1 | -| `LAVRADIOTOWER1F_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 14 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `LavRadioTower1FSuperNerd2Script` | -1 | - -**Scripts of interest** - -- `LavRadioTower1FGentlemanScript`: `checkflag ENGINE_EXPN_CARD` -> already got - it; else `checkevent EVENT_RETURNED_MACHINE_PART` -> `.ReturnedMachinePart`, - which prints the thank-you, `getstring STRING_BUFFER_4, .expncardname` - (`db "EXPN CARD@"`), `scall .receiveitem` (`jumpstd ReceiveItemScript`) and - `setflag ENGINE_EXPN_CARD`. Note this is a **flag**, not a bag item - nothing - enters the inventory. -- The upstairs floors are blocked by dialogue only (`LavRadioTower1FOfficerScript` - is flavour text); the map has no warp to 2F. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_EXPN_CARD` | `constants/engine_flags.asm:7` | set by `LavRadioTower1FGentlemanScript`; read by the Pokegear radio | Kanto radio channels unlock | -| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | read here | precondition for the gift | - ---- - -### MAP_ROUTE_8 - -- Script: `maps/Route8.asm` -- Blocks: `maps/Route8.blk` -- Header: `data/maps/maps.asm:378` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_8, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:349` `map_const ROUTE_8, 20, 9` -- Connections (`map_attributes Route8, ROUTE_8, $2c`): - `connection west, SaffronCity, SAFFRON_CITY, -9`, `connection east, LavenderTown, LAVENDER_TOWN, 0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 4 | `ROUTE_8_SAFFRON_GATE` | 3 | -| 2 | 4 | 5 | `ROUTE_8_SAFFRON_GATE` | 4 | - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 7 | `BGEVENT_READ` | `Route8UndergroundPathSign` | -| 10 | 5 | `BGEVENT_READ` | `Route8LockedDoor` ("It's locked...") | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE8_BIKER1` | `SPRITE_BIKER` | 10 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5 | `TrainerBikerDwayne` | -1 | -| `ROUTE8_BIKER2` | `SPRITE_BIKER` | 10 | 9 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5 | `TrainerBikerHarris` | -1 | -| `ROUTE8_BIKER3` | `SPRITE_BIKER` | 10 | 10 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5 | `TrainerBikerZeke` | -1 | -| `ROUTE8_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 20 | 6 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerSupernerdSam` | -1 | -| `ROUTE8_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 27 | 9 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSupernerdTom` | -1 | -| `ROUTE8_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 33 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route8FruitTree` (`fruittree FRUITTREE_ROUTE_8`, `50:53ed`) | -1 | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `PRZCUREBERRY` | face the tree object at (33,5) and press A | `fruittree FRUITTREE_ROUTE_8` (`constants/script_constants.asm:233`), item from `data/items/fruit_trees.asm:30` `db PRZCUREBERRY ; ROUTE_8` | daily fruit-tree flag (`TryResetFruitTrees`), **not** a one-shot event flag | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `SUPER_NERD`, `TOM` | Super Nerd (`:417`) | TOM | `db 32, MAGNEMITE` x3 (parties.asm:2002) | `TrainerSupernerdTom`, `EVENT_BEAT_SUPER_NERD_TOM` (`:925`) | no | -| `SUPER_NERD`, `SAM` | Super Nerd (`:416`) | SAM | `db 34, GRIMER` / `db 34, MUK` (parties.asm:1996) | `TrainerSupernerdSam`, `EVENT_BEAT_SUPER_NERD_SAM` (`:924`) | no | -| `BIKER`, `ZEKE` | Biker (`:463`) | ZEKE | `db 32, KOFFING` x2 (parties.asm:2272) | `TrainerBikerZeke`, `EVENT_BEAT_BIKER_ZEKE` (`:557`) | no | -| `BIKER`, `HARRIS` | Biker (`:462`) | HARRIS | `db 34, FLAREON` (parties.asm:2267) | `TrainerBikerHarris`, `EVENT_BEAT_BIKER_HARRIS` (`:556`) | no | -| `BIKER`, `DWAYNE` | Biker (`:461`) | DWAYNE | `db 27/28/29/30, KOFFING` (parties.asm:2259) | `TrainerBikerDwayne`, `EVENT_BEAT_BIKER_DWAYNE` (`:555`) | no | - -Class base rewards: Super Nerd `db 8`, Biker `db 8` (`data/trainers/attributes.asm`). - -**Wild encounters** - `data/wild/kanto_grass.asm`, `def_grass_wildmons ROUTE_8`, -rates `10 percent` in all slots. Gold branch (`IF DEF(_GOLD)`): - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 17 PIDGEOTTO | 17 PIDGEOTTO | 17 NOCTOWL | -| 2 | 19 PIDGEOTTO | 19 PIDGEOTTO | 20 HAUNTER | -| 3 | 15 ABRA | 15 ABRA | 15 ABRA | -| 4 | 18 GROWLITHE | 18 GROWLITHE | 19 NOCTOWL | -| 5 | 17 PIDGEOTTO | 17 PIDGEOTTO | 18 GROWLITHE | -| 6 | 15 KADABRA | 15 KADABRA | 15 KADABRA | -| 7 | 15 KADABRA | 15 KADABRA | 15 KADABRA | - ---- - -### MAP_SAFFRON_CITY (transit + Copycat) - -- Script: `maps/SaffronCity.asm` -- Header: `data/maps/maps.asm:490` -> `TILESET_KANTO, TOWN, LANDMARK_SAFFRON_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:454` `map_const SAFFRON_CITY, 20, 18` -- Connections: `connection north, Route5`, `connection south, Route6`, - `connection west, Route7, ROUTE_7, 9`, `connection east, Route8, ROUTE_8, 9` - -**Warps used by this section** (full table in `maps/SaffronCity.asm:269`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 6 | 8 | 3 | `SAFFRON_MAGNET_TRAIN_STATION` | 2 | -| 8 | 9 | 11 | `COPYCATS_HOUSE_1F` | 1 | -| 10 | 0 | 24 | `ROUTE_7_SAFFRON_GATE` | 3 | -| 11 | 0 | 25 | `ROUTE_7_SAFFRON_GATE` | 4 | -| 14 | 39 | 22 | `ROUTE_8_SAFFRON_GATE` | 1 | -| 15 | 39 | 23 | `ROUTE_8_SAFFRON_GATE` | 2 | - -**Magnet train gate** (`maps/SaffronMagnetTrainStation.asm`, -`SaffronMagnetTrainStationOfficerScript`): `checkevent EVENT_RESTORED_POWER_TO_KANTO` --> if clear, "train isn't operating"; if set, yes/no then `checkitem PASS` -> -if absent, "you don't have a PASS"; if present, movement + `setval TRUE` + -`special MagnetTrain` + `newloadmap MAPSETUP_TRAIN`. - ---- - -### MAP_COPYCATS_HOUSE_1F - -- Script: `maps/CopycatsHouse1F.asm` -- Header: `data/maps/maps.asm:499` -> `TILESET_PLAYERS_HOUSE, INDOOR, LANDMARK_SAFFRON_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:463` `map_const COPYCATS_HOUSE_1F, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `SAFFRON_CITY` | 8 | -| 2 | 3 | 7 | `SAFFRON_CITY` | 8 | -| 3 | 2 | 0 | `COPYCATS_HOUSE_2F` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `COPYCATSHOUSE1F_POKEFAN_M` | `SPRITE_POKEFAN_M` | 2 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse1FPokefanMScript` | -1 | -| `COPYCATSHOUSE1F_POKEFAN_F` | `SPRITE_POKEFAN_F` | 5 | 4 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse1FPokefanFScript` (branches on `EVENT_RETURNED_MACHINE_PART`) | -1 | -| `COPYCATSHOUSE1F_CLEFAIRY` | `SPRITE_CLEFAIRY` | 6 | 6 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse1FBlisseyScript` | -1 | - ---- - -### MAP_COPYCATS_HOUSE_2F - -- Script: `maps/CopycatsHouse2F.asm` -- Header: `data/maps/maps.asm:500` -> `TILESET_PLAYERS_HOUSE, INDOOR, LANDMARK_SAFFRON_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:464` `map_const COPYCATS_HOUSE_2F, 5, 3` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 0 | `COPYCATS_HOUSE_1F` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `CopycatsHouse2FBookshelf` (`jumpstd PictureBookshelfScript`) | -| 1 | 1 | `BGEVENT_READ` | `CopycatsHouse2FBookshelf` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `COPYCATSHOUSE2F_COPYCAT` | `SPRITE_COPYCAT` | 4 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Copycat` (`61:5235`) | -1 | -| `COPYCATSHOUSE2F_DODRIO` | `SPRITE_MOLTRES` | 6 | 4 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `CopycatsDodrio` | -1 | -| `COPYCATSHOUSE2F_FAIRYDOLL` | `SPRITE_FAIRY` | 6 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse2FDoll` | `EVENT_COPYCATS_HOUSE_2F_DOLL` | -| `COPYCATSHOUSE2F_MONSTERDOLL` | `SPRITE_MONSTER` | 2 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse2FDoll` | -1 | -| `COPYCATSHOUSE2F_BIRDDOLL` | `SPRITE_BIRD` | 7 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CopycatsHouse2FDoll` | -1 | - -**Scripts of interest** - `Copycat`, in check order: - -1. `checkevent EVENT_GOT_PASS_FROM_COPYCAT` -> `.GotPass` (post-quest chatter) -2. `checkevent EVENT_RETURNED_LOST_ITEM_TO_COPYCAT` -> `.TryGivePassAgain` - (the bag-was-full retry path) -3. `checkitem LOST_ITEM` -> `.ReturnLostItem`: `takeitem LOST_ITEM`, - `setevent EVENT_RETURNED_LOST_ITEM_TO_COPYCAT`, - `clearevent EVENT_COPYCATS_HOUSE_2F_DOLL` (the doll object reappears on her - shelf), then falls into `.GivePass` -4. `.GivePass`: `verbosegiveitem PASS`, `iffalse .Cancel`, - `setevent EVENT_GOT_PASS_FROM_COPYCAT` -5. Otherwise: spin-around movement, `variablesprite SPRITE_COPYCAT, SPRITE_CHRIS` - + `special LoadUsedSpritesGFX` (the mimicry gag), then - `checkevent EVENT_RETURNED_MACHINE_PART` -> `.TalkAboutLostItem`, which sets - **`EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM`** - -Step 5 is mandatory before the Fan Club will hand over the doll. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM` | `constants/event_flags.asm:206` | set by `Copycat.TalkAboutLostItem`; read by `PokemonFanClubClefairyGuyScript.MetCopycat` | unlocks the doll in Vermilion | -| `EVENT_RETURNED_LOST_ITEM_TO_COPYCAT` | `:207` | `Copycat` | doll handed back | -| `EVENT_GOT_PASS_FROM_COPYCAT` | `:208` | `Copycat` | PASS awarded | -| `EVENT_COPYCATS_HOUSE_2F_DOLL` | `:1301` | cleared by `Copycat.ReturnLostItem` | object visibility flag for the FAIRY doll at (6,1) | - ---- - -### MAP_POKEMON_FAN_CLUB - -- Script: `maps/PokemonFanClub.asm` -- Header: `data/maps/maps.asm:300` -> `TILESET_HOUSE, INDOOR, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:277` `map_const POKEMON_FAN_CLUB, 5, 4` (group `VERMILION`, id 7) -- Attributes: `data/maps/attributes.asm:569` `map_attributes PokemonFanClub, POKEMON_FAN_CLUB, $00` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `VERMILION_CITY` | 3 | -| 2 | 3 | 7 | `VERMILION_CITY` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 0 | `BGEVENT_READ` | `PokemonFanClubListenSign` | -| 9 | 0 | `BGEVENT_READ` | `PokemonFanClubBraggingSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `POKEMONFANCLUB_CHAIRMAN` | `SPRITE_GENTLEMAN` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubChairmanScript` (RARE_CANDY) | -1 | -| `POKEMONFANCLUB_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 4 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubReceptionistScript` | -1 | -| `POKEMONFANCLUB_CLEFAIRY_GUY` | `SPRITE_FISHER` | 2 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubClefairyGuyScript` (`59:437b`) | -1 | -| `POKEMONFANCLUB_TEACHER` | `SPRITE_TEACHER` | 7 | 2 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubTeacherScript` | -1 | -| `POKEMONFANCLUB_FAIRY` | `SPRITE_FAIRY` | 2 | 4 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubClefairyDollScript` | `EVENT_VERMILION_FAN_CLUB_DOLL` | -| `POKEMONFANCLUB_ODDISH` | `SPRITE_ODDISH` | 7 | 3 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `PokemonFanClubBayleefScript` | -1 | - -**Scripts of interest** - `PokemonFanClubClefairyGuyScript` (`59:437b`): -`checkevent EVENT_GOT_LOST_ITEM_FROM_FAN_CLUB` -> done; else -`checkevent EVENT_RETURNED_MACHINE_PART` -> `.FoundClefairyDoll`, which then -`checkevent EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM` -> `.MetCopycat` -(`59:439b`): `giveitem LOST_ITEM`, `iffalse .NoRoom`, -`disappear POKEMONFANCLUB_FAIRY`, `itemnotify`, -`setevent EVENT_GOT_LOST_ITEM_FROM_FAN_CLUB`. - -Note this uses `giveitem`, not `verbosegiveitem`; the "received # DOLL" line is -written by the script itself (`PokemonFanClubPlayerReceivedDollText`). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `LOST_ITEM` | talk to `POKEMONFANCLUB_CLEFAIRY_GUY` at (2,3) | `PokemonFanClubClefairyGuyScript.MetCopycat` | `EVENT_GOT_LOST_ITEM_FROM_FAN_CLUB` (`constants/event_flags.asm:209`) | -| `RARE_CANDY` | listen to the CHAIRMAN at (3,1), answer YES | `PokemonFanClubChairmanScript` | `EVENT_LISTENED_TO_FAN_CLUB_PRESIDENT` (`:211`) | - ---- - -### MAP_ROUTE_7_SAFFRON_GATE - -- Script: `maps/Route7SaffronGate.asm` -- Header: `data/maps/maps.asm:436` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_7, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:404` `map_const ROUTE_7_SAFFRON_GATE, 5, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ROUTE_7` | 1 | -| 2 | 0 | 5 | `ROUTE_7` | 2 | -| 3 | 9 | 4 | `SAFFRON_CITY` | 10 | -| 4 | 9 | 5 | `SAFFRON_CITY` | 11 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE7SAFFRONGATE_OFFICER` | `SPRITE_OFFICER` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route7SaffronGuardScript` | -1 | - -**Scripts of interest** - `Route7SaffronGuardScript` is `faceplayer` + a -`checkevent EVENT_RETURNED_MACHINE_PART` two-way text switch. The "checks your -Pokedex as ID" line is `Route7SaffronGuardSeriousText`, flavour only. **There is -no coord event and no blocking movement here** - the guard does not gate the -gate. - ---- - -### MAP_ROUTE_7 - -- Script: `maps/Route7.asm` -- Blocks: `maps/Route7.blk` -- Header: `data/maps/maps.asm:412` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_7, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:380` `map_const ROUTE_7, 10, 9` (group `CELADON`, id 1) -- Connections (`map_attributes Route7, ROUTE_7, $0f`): - `connection west, CeladonCity, CELADON_CITY, -5`, `connection east, SaffronCity, SAFFRON_CITY, -9` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 6 | `ROUTE_7_SAFFRON_GATE` | 1 | -| 2 | 15 | 7 | `ROUTE_7_SAFFRON_GATE` | 2 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 11 | `BGEVENT_READ` | `Route7UndergroundPathSign` (the "sealed indefinitely" flyer) | -| 6 | 9 | `BGEVENT_READ` | `Route7LockedDoor` | - -**Object events** - `def_object_events` is empty. The walkthrough is correct -that Route 7 has no trainers. - -**Wild encounters** - `data/wild/kanto_grass.asm`, `def_grass_wildmons ROUTE_7`, -rates `10 percent`. Gold branch: - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 17 RATTATA | 17 RATTATA | 17 RATTATA | -| 2 | 17 SPEAROW | 17 SPEAROW | 17 MURKROW | -| 3 | 18 GROWLITHE | 18 GROWLITHE | 18 GROWLITHE | -| 4 | 19 RATICATE | 19 RATICATE | 19 RATICATE | -| 5 | 19 RATTATA | 19 RATTATA | 19 MURKROW | -| 6 | 15 RATTATA | 15 GROWLITHE | 15 HOUNDOUR | -| 7 | 15 RATTATA | 15 GROWLITHE | 15 HOUNDOUR | - -(The Silver branch under `ELIF DEF(_SILVER)` is MEOWTH / VULPIX / PERSIAN.) - ---- - -### MAP_CELADON_CITY - -- Script: `maps/CeladonCity.asm` -- Blocks: `maps/CeladonCity.blk` (20 x 18 blocks, 360 bytes) -- Header: `data/maps/maps.asm:415` -> `TILESET_KANTO, TOWN, LANDMARK_CELADON_CITY, MUSIC_CELADON_CITY, FALSE, PALETTE_AUTO, FISHGROUP_NONE` -- Dimensions: `constants/map_constants.asm:383` `map_const CELADON_CITY, 20, 18` -> 40 x 36 cells -- Connections (`map_attributes CeladonCity, CELADON_CITY, $0f`): - `connection west, Route16, ROUTE_16, 9`, `connection east, Route7, ROUTE_7, 5` - -**Callbacks**: `callback MAPCALLBACK_NEWMAP, CeladonCityFlypointCallback` -> -`setflag ENGINE_FLYPOINT_CELADON` (`constants/engine_flags.asm:75`). - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 9 | `CELADON_DEPT_STORE_1F` | 1 | -| 2 | 16 | 9 | `CELADON_MANSION_1F` | 1 | -| 3 | 16 | 3 | `CELADON_MANSION_1F` | 3 | -| 4 | 17 | 3 | `CELADON_MANSION_1F` | 3 | -| 5 | 29 | 9 | `CELADON_POKECENTER_1F` | 1 | -| 6 | 18 | 19 | `CELADON_GAME_CORNER` | 1 | -| 7 | 23 | 19 | `CELADON_GAME_CORNER_PRIZE_ROOM` | 1 | -| 8 | 10 | 29 | `CELADON_GYM` | 1 | -| 9 | 25 | 29 | `CELADON_CAFE` | 1 | - -Warps 3/4 at y=3 are the "hidden back door" the Youngster at (18,13) mentions. - -**Coord events** - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 23 | 21 | `BGEVENT_READ` | `CeladonCitySign` | -| 11 | 31 | `BGEVENT_READ` | `CeladonGymSign` (ERIKA, "The Nature-Loving Princess") | -| 6 | 9 | `BGEVENT_READ` | `CeladonCityDeptStoreSign` | -| 13 | 9 | `BGEVENT_READ` | `CeladonCityMansionSign` | -| 19 | 21 | `BGEVENT_READ` | `CeladonCityGameCornerSign` | -| 29 | 21 | `BGEVENT_READ` | `CeladonCityTrainerTips` | -| 30 | 9 | `BGEVENT_READ` | `CeladonCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 37 | 21 | `BGEVENT_ITEM` | `CeladonCityHiddenPpUp` (`4e:5b37`) -> `hiddenitem PP_UP, EVENT_CELADON_CITY_HIDDEN_PP_UP` | - -The walkthrough's "head right to a dead end, press A and you'll get a random PP -Up" is this bg event at (37, 21). It is a fixed `PP_UP`, not random. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONCITY_FISHER` | `SPRITE_FISHER` | 26 | 11 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `CeladonCityFisherScript` | -1 | -| `CELADONCITY_POLIWAG` | `SPRITE_POLIWAG` | 27 | 11 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `CeladonCityPoliwrath` | -1 | -| `CELADONCITY_TEACHER1` | `SPRITE_TEACHER` | 20 | 24 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `CeladonCityTeacher1Script` | -1 | -| `CELADONCITY_GRAMPS1` | `SPRITE_GRAMPS` | 14 | 16 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeladonCityGramps1Script` | -1 | -| `CELADONCITY_GRAMPS2` | `SPRITE_GRAMPS` | 8 | 31 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `CeladonCityGramps2Script` ("Only girls are allowed here!") | -1 | -| `CELADONCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 18 | 13 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `CeladonCityYoungster1Script` | -1 | -| `CELADONCITY_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 24 | 33 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `CeladonCityYoungster2Script` | -1 | -| `CELADONCITY_TEACHER2` | `SPRITE_TEACHER` | 6 | 14 | `WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `CeladonCityTeacher2Script` | -1 | -| `CELADONCITY_LASS` | `SPRITE_LASS` | 7 | 22 | `WALK_UP_DOWN` (0,2) | `OBJECTTYPE_SCRIPT` | `CeladonCityLassScript` | -1 | - -The old man outside the gym is `CELADONCITY_GRAMPS2` at (8, 31). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `PP_UP` | face (37,21), press A | `CeladonCityHiddenPpUp` bg event | `EVENT_CELADON_CITY_HIDDEN_PP_UP` (`constants/event_flags.asm:253`) | - -**Wild encounters** - none (`FISHGROUP_NONE`, no grass entry). - ---- - -### MAP_CELADON_MANSION_1F / 2F / 3F / ROOF / ROOF_HOUSE - -- Scripts: `maps/CeladonMansion1F.asm`, `2F`, `3F`, `maps/CeladonMansionRoof.asm`, `maps/CeladonMansionRoofHouse.asm` -- Headers: `data/maps/maps.asm:423-427` -> all `TILESET_MANSION` except the roof - house (`TILESET_HOUSE`), all `INDOOR, LANDMARK_CELADON_CITY, MUSIC_CELADON_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:391-395` -> 1F/2F/3F/ROOF `4, 5`; ROOF_HOUSE `4, 4` -- Attributes: `data/maps/attributes.asm:636-640` (roof is `$01`, the rest `$00`) - -**Warps - 1F** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 9 | `CELADON_CITY` | 2 | -| 2 | 7 | 9 | `CELADON_CITY` | 2 | -| 3 | 3 | 0 | `CELADON_CITY` | 3 | -| 4 | 0 | 0 | `CELADON_MANSION_2F` | 1 | -| 5 | 7 | 0 | `CELADON_MANSION_2F` | 4 | - -**Warps - 2F** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `CELADON_MANSION_1F` | 4 | -| 2 | 1 | 0 | `CELADON_MANSION_3F` | 2 | -| 3 | 6 | 0 | `CELADON_MANSION_3F` | 3 | -| 4 | 7 | 0 | `CELADON_MANSION_1F` | 5 | - -**Warps - 3F** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 0 | `CELADON_MANSION_ROOF` | 1 | -| 2 | 1 | 0 | `CELADON_MANSION_2F` | 2 | -| 3 | 6 | 0 | `CELADON_MANSION_2F` | 3 | -| 4 | 7 | 0 | `CELADON_MANSION_ROOF` | 2 | - -**Warps - ROOF** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 1 | 1 | `CELADON_MANSION_3F` | 1 | -| 2 | 6 | 1 | `CELADON_MANSION_3F` | 4 | -| 3 | 2 | 5 | `CELADON_MANSION_ROOF_HOUSE` | 1 | - -**Warps - ROOF_HOUSE** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `CELADON_MANSION_ROOF` | 3 | -| 2 | 3 | 7 | `CELADON_MANSION_ROOF` | 3 | - -**Object events - ROOF_HOUSE** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONMANSIONROOFHOUSE_PHARMACIST` | `SPRITE_PHARMACIST` | 3 | 2 | `STANDING_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `CeladonMansionRoofHousePharmacistScript` (`5e:5083`) | -1 | - -**Scripts of interest** - `CeladonMansionRoofHousePharmacistScript`: -`checkevent EVENT_GOT_TM03_CURSE` -> `.GotCurse`; else intro text, then -**`checktime NITE`**; if false, "come back after sunset" and end; if true, -the Cycling Road story, `verbosegiveitem TM_CURSE`, `setevent EVENT_GOT_TM03_CURSE`. - -**Object events - 3F** (the Game Freak dev room the walkthrough mentions): - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONMANSION3F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 3 | 6 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `GameFreakGameDesignerScript` (diploma at full dex, `setevent EVENT_ENABLE_DIPLOMA_PRINTING`) | -1 | -| `CELADONMANSION3F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 3 | 4 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GameFreakGraphicArtistScript` | -1 | -| `CELADONMANSION3F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 0 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `GameFreakProgrammerScript` | -1 | -| `CELADONMANSION3F_FISHER` | `SPRITE_FISHER` | 0 | 4 | `STANDING_UP` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `GameFreakCharacterDesignerScript` | -1 | - -**Object events - 1F** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONMANSION1F_GRANNY` | `SPRITE_GRANNY` | 1 | 5 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `CeladonMansionManager` | -1 | -| `CELADONMANSION1F_GROWLITHE1` | `SPRITE_GROWLITHE` | 2 | 6 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `CeladonMansion1FMeowth` | -1 | -| `CELADONMANSION1F_CLEFAIRY` | `SPRITE_CLEFAIRY` | 3 | 4 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `CeladonMansion1FClefairy` | -1 | -| `CELADONMANSION1F_GROWLITHE2` | `SPRITE_GROWLITHE` | 4 | 4 | `POKEMON` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `CeladonMansion1FNidoranF` | -1 | - -2F has `def_object_events` empty; its three bg events (PC at (0,3), meeting-room -sign at (5,8), bookshelf at (2,3)) are the whole floor. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_CURSE` (TM03) | talk to the pharmacist at (3,2) in `CELADON_MANSION_ROOF_HOUSE` **at night** | `CeladonMansionRoofHousePharmacistScript` | `EVENT_GOT_TM03_CURSE` (`constants/event_flags.asm:217`) | - ---- - -### MAP_CELADON_DEPT_STORE_1F..6F - -- Scripts: `maps/CeladonDeptStore1F.asm` .. `6F.asm`, `maps/CeladonDeptStoreElevator.asm` -- Headers: `data/maps/maps.asm:416-422` -> `TILESET_MART, INDOOR, LANDMARK_CELADON_CITY, MUSIC_CELADON_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:384-390` -> each floor `8, 4`; elevator `2, 2` - -**Warps - 1F** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 7 | `CELADON_CITY` | 1 | -| 2 | 8 | 7 | `CELADON_CITY` | 1 | -| 3 | 15 | 0 | `CELADON_DEPT_STORE_2F` | 2 | -| 4 | 2 | 0 | `CELADON_DEPT_STORE_ELEVATOR` | 1 | - -**Marts** (`data/items/marts.asm`, referenced by `pokemart MARTTYPE_STANDARD, MART_*`) - -| floor | mart label | contents | -|---|---|---| -| 2F clerk A | `MartCeladon2F1` (line 290) | POTION, SUPER_POTION, HYPER_POTION, MAX_POTION, REVIVE, SUPER_REPEL, MAX_REPEL | -| 2F clerk B | `MartCeladon2F2` (line 301) | POKE_BALL, GREAT_BALL, ULTRA_BALL, ESCAPE_ROPE, FULL_HEAL, ANTIDOTE, BURN_HEAL, ICE_HEAL, AWAKENING, PARLYZ_HEAL | -| 3F | `MartCeladon3F` (line 315) | TM_HIDDEN_POWER (TM10), TM_SUNNY_DAY (TM11), TM_PROTECT (TM17), TM_RAIN_DANCE (TM18), TM_SANDSTORM (TM37) | -| 4F | `MartCeladon4F` (line 324) | POKE_DOLL, LOVELY_MAIL, SURF_MAIL | -| 5F clerk A | `MartCeladon5F1` (line 331) | HP_UP, PROTEIN, IRON, CARBOS, CALCIUM | -| 5F clerk B | `MartCeladon5F2` (line 344) | X_ACCURACY, GUARD_SPEC, DIRE_HIT, X_ATTACK, X_DEFEND, X_SPEED, X_SPECIAL | -| 6F | none | four `BGEVENT_UP` vending machines at (8,1), (9,1), (10,1), (11,1) -> `CeladonDeptStore6FVendingMachine` | - -This matches the walkthrough's floor list exactly. - ---- - -### MAP_CELADON_GAME_CORNER / MAP_CELADON_GAME_CORNER_PRIZE_ROOM - -- Scripts: `maps/CeladonGameCorner.asm`, `maps/CeladonGameCornerPrizeRoom.asm` -- Headers: `data/maps/maps.asm:430-431` -> `TILESET_GAME_CORNER, INDOOR, LANDMARK_CELADON_CITY`; Game Corner uses `MUSIC_GAME_CORNER`, the prize room `MUSIC_CELADON_CITY` -- Dimensions: `constants/map_constants.asm:398-399` -> `CELADON_GAME_CORNER 10, 7`; `CELADON_GAME_CORNER_PRIZE_ROOM 3, 3` - -**Warps - Game Corner** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 14 | 13 | `CELADON_CITY` | 6 | -| 2 | 15 | 13 | `CELADON_CITY` | 6 | - -**Warps - Prize Room** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 5 | `CELADON_CITY` | 7 | -| 2 | 3 | 5 | `CELADON_CITY` | 7 | - -**BG events - Prize Room** - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 1 | `BGEVENT_READ` | `CeladonGameCornerPrizeRoomTMVendor` | -| 4 | 1 | `BGEVENT_READ` | `CeladonGameCornerPrizeRoomPokemonVendor` | - -**Object events - Prize Room** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONGAMECORNERPRIZEROOM_GENTLEMAN` | `SPRITE_GENTLEMAN` | 0 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeladonGameCornerPrizeRoomGentlemanScript` | -1 | -| `CELADONGAMECORNERPRIZEROOM_PHARMACIST` | `SPRITE_PHARMACIST` | 4 | 4 | `WALK_UP_DOWN` (0,1) | `OBJECTTYPE_SCRIPT` | `CeladonGameCornerPrizeRoomPharmacistScript` | -1 | - -**Prize prices** - `EQU` constants at the top of `maps/CeladonGameCornerPrizeRoom.asm`: - -| prize | constant | coins | -|---|---|---| -| `TM_DOUBLE_TEAM` (TM32) | `CELADONGAMECORNERPRIZEROOM_TM32_COINS` | 1500 | -| `TM_PSYCHIC_M` (TM29) | `CELADONGAMECORNERPRIZEROOM_TM29_COINS` | 3500 | -| `TM_HYPER_BEAM` (TM15) | `CELADONGAMECORNERPRIZEROOM_TM15_COINS` | 7500 | -| `MR__MIME` @ L15 | `CELADONGAMECORNERPRIZEROOM_MR_MIME_COINS` | 3333 | -| `EEVEE` @ L15 | `CELADONGAMECORNERPRIZEROOM_EEVEE_COINS` | 6666 | -| `PORYGON` @ L20 | `CELADONGAMECORNERPRIZEROOM_PORYGON_COINS` | 9999 | - -Both vendors open with `checkitem COIN_CASE` / `iffalse CeladonPrizeRoom_NoCoinCase`. -The mon counter additionally does `readvar VAR_PARTYCOUNT` / -`ifequal PARTY_LENGTH, CeladonPrizeRoom_notenoughroom` before the purchase, then -`setval <species>` + `special GameCornerPrizeMonCheckDex` + `givepoke`. - ---- - -### MAP_CELADON_CAFE - -- Script: `maps/CeladonCafe.asm` -- Header: `data/maps/maps.asm:433` -> `TILESET_GAME_CORNER, INDOOR, LANDMARK_CELADON_CITY, MUSIC_CELADON_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:401` `map_const CELADON_CAFE, 6, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 6 | 7 | `CELADON_CITY` | 9 | -| 2 | 7 | 7 | `CELADON_CITY` | 9 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 0 | `BGEVENT_READ` | `EatathonContestPoster` | -| 7 | 1 | `BGEVENT_READ` | `CeladonCafeTrashcan` (`5e:648c`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONCAFE_SUPER_NERD` | `SPRITE_SUPER_NERD` | 9 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CeladonCafeChef` | -1 | -| `CELADONCAFE_FISHER1` | `SPRITE_FISHER` | 4 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CeladonCafeFisher1` | -1 | -| `CELADONCAFE_FISHER2` | `SPRITE_FISHER` | 1 | 7 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `CeladonCafeFisher2` | -1 | -| `CELADONCAFE_FISHER3` | `SPRITE_FISHER` | 1 | 2 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `CeladonCafeFisher3` | -1 | -| `CELADONCAFE_TEACHER` | `SPRITE_TEACHER` | 4 | 3 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CeladonCafeTeacher` (`checkitem COIN_CASE` branch) | -1 | - -**Scripts of interest** - `CeladonCafeTrashcan` (`maps/CeladonCafe.asm:91`): -`checkevent EVENT_FOUND_LEFTOVERS_IN_CELADON_CAFE` -> `.TrashEmpty` -(`jumpstd TrashCanScript`); else `giveitem LEFTOVERS`, `iffalse .PackFull`, -`getitemname STRING_BUFFER_3, LEFTOVERS`, `writetext FoundLeftoversText`, -`itemnotify`, `setevent EVENT_FOUND_LEFTOVERS_IN_CELADON_CAFE`. Note the flag is -only set on success, so a full pack is retryable. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `LEFTOVERS` | face (7,1) and press A | `CeladonCafeTrashcan` bg event | `EVENT_FOUND_LEFTOVERS_IN_CELADON_CAFE` (`constants/event_flags.asm:249`) | - ---- - -### MAP_CELADON_GYM - -- Script: `maps/CeladonGym.asm` -- Blocks: `maps/CeladonGym.blk` -- Header: `data/maps/maps.asm:432` -> `TILESET_TRAIN_STATION, INDOOR, LANDMARK_CELADON_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:400` `map_const CELADON_GYM, 5, 9` -> 10 x 18 cells -- Attributes: `data/maps/attributes.asm:645` `map_attributes CeladonGym, CELADON_GYM, $00`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `CELADON_CITY` | 8 | -| 2 | 5 | 17 | `CELADON_CITY` | 8 | - -**Coord events** - none (`def_coord_events` empty). There is no scripted -lock-in; the maze is pure geometry. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `CeladonGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `CeladonGymStatue` | - -`CeladonGymStatue` is `checkflag ENGINE_RAINBOWBADGE` -> `.Beaten` -(`gettrainername STRING_BUFFER_4, ERIKA, ERIKA1` + `jumpstd GymStatue2Script`), -else `jumpstd GymStatue1Script`. A cheap post-badge assertion for a bot. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CELADONGYM_ERIKA` | `SPRITE_ERIKA` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeladonGymErikaScript` (`5e:5e0b`) | -1 | -| `CELADONGYM_LASS1` | `SPRITE_LASS` | 7 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerLassMichelle` | -1 | -| `CELADONGYM_LASS2` | `SPRITE_LASS` | 2 | 8 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerPicnickerTanya` | -1 | -| `CELADONGYM_BEAUTY` | `SPRITE_BEAUTY` | 3 | 5 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerBeautyJulia` | -1 | -| `CELADONGYM_TWIN1` | `SPRITE_TWIN` | 4 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerTwinsJoAndZoe1` | -1 | -| `CELADONGYM_TWIN2` | `SPRITE_TWIN` | 5 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerTwinsJoAndZoe2` | -1 | - -**Scripts of interest** - `CeladonGymErikaScript`: - -``` -faceplayer / opentext -checkflag ENGINE_RAINBOWBADGE -> iftrue .FightDone -writetext ErikaBeforeBattleText / waitbutton / closetext -winlosstext ErikaBeatenText, 0 -loadtrainer ERIKA, ERIKA1 -startbattle -reloadmapafterbattle -setevent EVENT_BEAT_ERIKA -setevent EVENT_BEAT_LASS_MICHELLE -setevent EVENT_BEAT_PICNICKER_TANYA -setevent EVENT_BEAT_BEAUTY_JULIA -setevent EVENT_BEAT_TWINS_JO_AND_ZOE -writetext PlayerReceivedRainbowBadgeText / playsound SFX_GET_BADGE / waitsfx -setflag ENGINE_RAINBOWBADGE -.FightDone: -checkevent EVENT_GOT_TM19_GIGA_DRAIN -> iftrue .GotGigaDrain -writetext ErikaExplainTMText / promptbutton -verbosegiveitem TM_GIGA_DRAIN / iffalse .GotGigaDrain -setevent EVENT_GOT_TM19_GIGA_DRAIN -``` - -Two things a bot must know: beating Erika **retroactively sets all four minor -trainers' beaten flags**, so a rematch sweep is impossible after the leader; and -the TM is handed out in the same conversation, so a full bag silently skips it -and you must talk to her again. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_RAINBOWBADGE` | `constants/engine_flags.asm:50` | set by `CeladonGymErikaScript`; read by `CeladonGymStatue` | the badge itself | -| `EVENT_BEAT_ERIKA` | `constants/event_flags.asm:718` | set by `CeladonGymErikaScript` | leader cleared | -| `EVENT_BEAT_LASS_MICHELLE` | `:810` | `TrainerLassMichelle` / Erika script | gym trainer | -| `EVENT_BEAT_PICNICKER_TANYA` | `:654` | `TrainerPicnickerTanya` / Erika script | gym trainer | -| `EVENT_BEAT_BEAUTY_JULIA` | `:702` | `TrainerBeautyJulia` / Erika script | gym trainer | -| `EVENT_BEAT_TWINS_JO_AND_ZOE` | `:612` | both twin objects share this one flag | beating either twin clears both | -| `EVENT_GOT_TM19_GIGA_DRAIN` | `:219` | `CeladonGymErikaScript` | one-time TM | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_GIGA_DRAIN` (TM19) | talk to ERIKA after the battle | `CeladonGymErikaScript` -> `verbosegiveitem TM_GIGA_DRAIN` | `EVENT_GOT_TM19_GIGA_DRAIN` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `TWINS`, `JOANDZOE1` | Twins (`:625`) | JOANDZOE1 | `"JO & ZOE@", TRAINERTYPE_NORMAL` / `35 VICTREEBEL`, `35 VILEPLUME` (line 3090) | `TrainerTwinsJoAndZoe1` | no | -| `TWINS`, `JOANDZOE2` | Twins (`:626`) | JOANDZOE2 | `"JO & ZOE@", TRAINERTYPE_NORMAL` / `35 VILEPLUME`, `35 VICTREEBEL` (line 3096) | `TrainerTwinsJoAndZoe2` | no | -| `PICNICKER`, `TANYA` | Picnicker (`:544`) | TANYA | `37 EXEGGUTOR` (line 2688) | `TrainerPicnickerTanya` | no | -| `LASS`, `MICHELLE` | Lass (`:173`) | MICHELLE | `32 SKIPLOOM`, `33 HOPPIP`, `34 JUMPLUFF` (line 690) | `TrainerLassMichelle` | no | -| `BEAUTY`, `JULIA` | Beauty (`:241`) | JULIA | `32 PARAS`, `32 EXEGGCUTE`, `35 PARASECT` (line 1062) | `TrainerBeautyJulia` | no | -| `ERIKA`, `ERIKA1` | Erika (`:107`) | ERIKA1 | `TRAINERTYPE_MOVES` (line 331): `42 TANGELA` VINE_WHIP/BIND/GIGA_DRAIN/SLEEP_POWDER; `41 JUMPLUFF` MEGA_DRAIN/LEECH_SEED/COTTON_SPORE/GIGA_DRAIN; `46 VICTREEBEL` SUNNY_DAY/SYNTHESIS/ACID/RAZOR_LEAF; `46 BELLOSSOM` SUNNY_DAY/SYNTHESIS/PETAL_DANCE/SOLARBEAM | `CeladonGymErikaScript` | leader, no rematch here | - -Erika's DVs: `data/trainers/dvs.asm:25` `dn 7, 8, 8, 8`. -Erika's class attributes (`data/trainers/attributes.asm:125`): -`db HYPER_POTION, NO_ITEM ; items`, `db 25 ; base reward`, -`AI_BASIC|AI_SETUP|AI_SMART|AI_AGGRESSIVE|AI_CAUTIOUS|AI_STATUS|AI_RISKY`, -`CONTEXT_USE | SWITCH_SOMETIMES`. - -**Party order note**: `loadtrainer` sends mons out in the order written in -`parties.asm` - **Tangela, Jumpluff, Victreebel, Bellossom**. The walkthrough -lists Tangela / Victreebel / Bellossom / Jumpluff, which is not the lead order. - -**Wild encounters** - none. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Rock Tunnel is pitch black | `data/maps/maps.asm:156-157` (`PALETTE_DARK`) + `engine/events/overworld.asm:271` `FlashFunction.CheckUseFlash` | `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET` | use HM05 Flash from the party menu inside the tunnel; without the badge the routine returns `JUMPTABLE_EXIT` and nothing happens | -| Surf to reach the Power Plant / cross Route 10 water | `engine/events/overworld.asm:322` `SurfFunction`, badge check at `:340` `ld de, ENGINE_FOGBADGE` | `ENGINE_FOGBADGE` + a party mon that knows Surf | already held by this point in the walkthrough | -| Cut (any cut tree) | `engine/events/overworld.asm:133` `ld de, ENGINE_HIVEBADGE` in `CutFunction.CheckAble`, then `CheckMapForSomethingToCut` -> `CheckCutCollision` against `data/collision/field_move_blocks.asm` `CutTreeBlockPointers.kanto` (`$32/$33/$34/$35/$60` trees, `$0b` grass) | `ENGINE_HIVEBADGE` + Cut | not actually required in Celadon City - see below | -| TM07 Zap Cannon | `maps/PowerPlant.asm` `PowerPlantManager` | `EVENT_RETURNED_MACHINE_PART` (or `MACHINE_PART` in the bag) | return the part | -| EXPN CARD | `maps/LavRadioTower1F.asm` `LavRadioTower1FGentlemanScript` | `EVENT_RETURNED_MACHINE_PART` | same | -| `LOST_ITEM` from the Fan Club | `maps/PokemonFanClub.asm` `PokemonFanClubClefairyGuyScript.MetCopycat` | `EVENT_RETURNED_MACHINE_PART` **and** `EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM` | talk to the Copycat once after returning the machine part | -| `PASS` from the Copycat | `maps/CopycatsHouse2F.asm` `Copycat.ReturnLostItem` | `LOST_ITEM` in the bag | hand it over | -| Magnet Train ride | `maps/SaffronMagnetTrainStation.asm` `SaffronMagnetTrainStationOfficerScript` | `EVENT_RESTORED_POWER_TO_KANTO` then `checkitem PASS` | both set by the machine-part / Copycat chains | -| TM03 Curse | `maps/CeladonMansionRoofHouse.asm` `CeladonMansionRoofHousePharmacistScript` | `checktime NITE` | come back after sunset | -| Game Corner prizes | `maps/CeladonGameCornerPrizeRoom.asm` both vendor scripts | `checkitem COIN_CASE`, then `checkcoins <price>`; mon counter also `readvar VAR_PARTYCOUNT != PARTY_LENGTH` | buy/win coins, keep a party slot free | -| RAINBOWBADGE | `maps/CeladonGym.asm` `CeladonGymErikaScript` | none beyond reaching Erika at (5,3) | win the battle | - -**Celadon Gym is NOT Cut-gated in pokegold.** `maps/CeladonCity.blk` contains a -single kanto cut-tree block (`$60`) at block (14,17) = cell (28,34), in the -bottom-right of the map, nowhere near the gym. The gym door is the `DOOR` -quadrant of block `$12` at block (5,14) = cell (10,29) -(`data/tilesets/kanto_collision.asm` `tilecoll WALL, WALL, DOOR, WALL ; 12`), -and the cell directly below it, block (5,15) = `$79` -(`tilecoll FLOOR, FLOOR, FLOOR, WALL ; 79`), is walkable floor. Nothing between -the city and the gym requires Cut. - ---- - -## 4. Bot checklist - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `ROUTE_10_NORTH` | cell (3,9) | walk onto warp 2 | Surf usable (`ENGINE_FOGBADGE`) | in `POWER_PLANT` | -| 2 | `POWER_PLANT` | object `POWERPLANT_MANAGER` (14,10) | talk (face UP from (14,11)) | `EVENT_RETURNED_MACHINE_PART` or `MACHINE_PART` held | `EVENT_GOT_TM07_ZAP_CANNON`, `EVENT_RESTORED_POWER_TO_KANTO` | -| 3 | `POWER_PLANT` | cell (2,17) | walk onto warp 1 | - | in `ROUTE_10_NORTH` | -| 4 | `ROCK_TUNNEL_1F` | party menu | use FLASH | `ENGINE_ZEPHYRBADGE`, mon knows Flash | dark palette lifted | -| 5 | `ROCK_TUNNEL_1F` | cell (10,15) | walk onto item ball | `EVENT_ROCK_TUNNEL_1F_TM_STEEL_WING` clear | TM47 in bag, flag set | -| 6 | `ROCK_TUNNEL_1F` | cell (4,18) | walk onto item ball | `EVENT_ROCK_TUNNEL_1F_ELIXER` clear | ELIXER in bag | -| 7 | `ROCK_TUNNEL_1F` | cell (15,9) / (5,3) / (27,3) / (27,13) | warps 4/3/5/6 into B1F | - | in `ROCK_TUNNEL_B1F` | -| 8 | `ROCK_TUNNEL_B1F` | cells (7,25), (6,17), (15,2) | walk onto item balls | respective flags clear | IRON, PP_UP, REVIVE | -| 9 | `ROCK_TUNNEL_B1F` | cell (4,14) | face and press A | `EVENT_ROCK_TUNNEL_B1F_HIDDEN_MAX_POTION` clear | MAX_POTION | -| 10 | `ROCK_TUNNEL_1F` | cell (11,25) | warp 2 | - | in `ROUTE_10_SOUTH` at warp 1 (6,1) | -| 11 | `ROUTE_10_SOUTH` | object at (17,3) | battle `TrainerHikerJim` (sight 4, faces LEFT) | `EVENT_BEAT_HIKER_JIM` clear | flag set | -| 12 | `ROUTE_10_SOUTH` | object at (4,10) | battle `TrainerPokefanmRobert` (sight 2, faces LEFT) | `EVENT_BEAT_POKEFANM_ROBERT` clear | flag set | -| 13 | `ROUTE_10_SOUTH` | south edge | walk south | - | in `LAVENDER_TOWN`, `ENGINE_FLYPOINT_LAVENDER` set | -| 14 | `LAVENDER_TOWN` | cell (14,5) | warp 7 | - | in `LAV_RADIO_TOWER_1F` | -| 15 | `LAV_RADIO_TOWER_1F` | object at (9,1) | talk | `EVENT_RETURNED_MACHINE_PART` set, `ENGINE_EXPN_CARD` clear | `ENGINE_EXPN_CARD` set | -| 16 | `LAVENDER_TOWN` | west edge | walk west | - | in `ROUTE_8` | -| 17 | `ROUTE_8` | (27,9), (20,6), (10,10), (10,9), (10,8) | battle Tom, Sam, Zeke, Harris, Dwayne | respective `EVENT_BEAT_*` clear | flags set | -| 18 | `ROUTE_8` | object at (33,5) | face and press A | daily tree flag clear | `PRZCUREBERRY` | -| 19 | `ROUTE_8` | cell (4,4) | warp 1 | - | `ROUTE_8_SAFFRON_GATE` -> `SAFFRON_CITY` | -| 20 | `SAFFRON_CITY` | cell (9,11) | warp 8 | - | `COPYCATS_HOUSE_1F` | -| 21 | `COPYCATS_HOUSE_1F` | cell (2,0) | warp 3 | - | `COPYCATS_HOUSE_2F` | -| 22 | `COPYCATS_HOUSE_2F` | object `COPYCATSHOUSE2F_COPYCAT` (4,3) | talk | `EVENT_RETURNED_MACHINE_PART` set | `EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM` | -| 23 | - | Fly to Vermilion | fly | `ENGINE_FLYPOINT_VERMILION` | in `VERMILION_CITY` | -| 24 | `VERMILION_CITY` | cell (7,13) | warp 3 | - | `POKEMON_FAN_CLUB` | -| 25 | `POKEMON_FAN_CLUB` | object at (2,3) | talk (face RIGHT from (1,3)) | step 22 done, bag has room | `LOST_ITEM`, `EVENT_GOT_LOST_ITEM_FROM_FAN_CLUB`, FAIRY object disappears | -| 26 | - | Fly to Saffron, re-enter Copycat 2F | fly + warps | - | back at (4,3) | -| 27 | `COPYCATS_HOUSE_2F` | Copycat | talk | `LOST_ITEM` held | `PASS`, `EVENT_GOT_PASS_FROM_COPYCAT`, `EVENT_RETURNED_LOST_ITEM_TO_COPYCAT` | -| 28 | `SAFFRON_CITY` | cell (0,24) | warp 10 | - | `ROUTE_7_SAFFRON_GATE` | -| 29 | `ROUTE_7_SAFFRON_GATE` | cell (0,4) | warp 1 | - | `ROUTE_7` (guard is not a blocker) | -| 30 | `ROUTE_7` | west edge | walk west | - | `CELADON_CITY`, `ENGINE_FLYPOINT_CELADON` set | -| 31 | `CELADON_CITY` | cell (16,3) | warp 3 (mansion back door) | - | `CELADON_MANSION_1F` at warp 3 | -| 32 | `CELADON_MANSION_1F/2F/3F` | warps 4 -> 2 -> 1 | climb to the roof | - | `CELADON_MANSION_ROOF` | -| 33 | `CELADON_MANSION_ROOF` | cell (2,5) | warp 3 | - | `CELADON_MANSION_ROOF_HOUSE` | -| 34 | `CELADON_MANSION_ROOF_HOUSE` | object at (3,2) | talk | `checktime NITE` true, `EVENT_GOT_TM03_CURSE` clear | TM03, flag set | -| 35 | `CELADON_CITY` | cell (25,29) | warp 9 | - | `CELADON_CAFE` | -| 36 | `CELADON_CAFE` | cell (7,1) | face UP and press A | `EVENT_FOUND_LEFTOVERS_IN_CELADON_CAFE` clear, bag room | `LEFTOVERS`, flag set | -| 37 | `CELADON_CITY` | cell (37,21) | face and press A | `EVENT_CELADON_CITY_HIDDEN_PP_UP` clear | `PP_UP` | -| 38 | `CELADON_CITY` | cell (10,29) | warp 8 | no field move needed | `CELADON_GYM` | -| 39 | `CELADON_GYM` | (4,10) and (5,10) | battle both twins | `EVENT_BEAT_TWINS_JO_AND_ZOE` clear (shared) | flag set after the first | -| 40 | `CELADON_GYM` | (2,8) | battle `TrainerPicnickerTanya` | `EVENT_BEAT_PICNICKER_TANYA` clear | flag set | -| 41 | `CELADON_GYM` | (7,8) | battle `TrainerLassMichelle` | `EVENT_BEAT_LASS_MICHELLE` clear | flag set | -| 42 | `CELADON_GYM` | (3,5) | battle `TrainerBeautyJulia` | `EVENT_BEAT_BEAUTY_JULIA` clear | flag set | -| 43 | `CELADON_GYM` | object `CELADONGYM_ERIKA` (5,3) | talk from (5,4) facing UP | `ENGINE_RAINBOWBADGE` clear | battle, then `ENGINE_RAINBOWBADGE`, `EVENT_BEAT_ERIKA`, all four minor flags | -| 44 | `CELADON_GYM` | Erika again if the bag was full | talk | `EVENT_GOT_TM19_GIGA_DRAIN` clear | `TM_GIGA_DRAIN` | -| 45 | `CELADON_GYM` | cell (4,17) | warp 1 | - | back in `CELADON_CITY` | - ---- - -## 5. Port coverage - -The Gen 2 port is data-driven: `src/import/RomExtractorGen2.lua` reads every map -header, `def_warp_events` / `def_coord_events` / `def_bg_events` / -`def_object_events` table and script pointer straight out of the ROM -(`RomExtractorGen2:readMapEvents`, line 782), so per-map coverage is not a -per-map Lua file. What follows is coverage of the *mechanics* this section -needs. - -| Beat | Port file | Status | -|---|---|---| -| Map blocks, collision, warps, connections | `src/world/gen2/Map.lua`, `src/world/gen2/Permissions.lua` | implemented (generic) | -| Warp / object / bg / coord event extraction | `src/import/RomExtractorGen2.lua:782` `readMapEvents` | implemented | -| Script VM (`checkevent`, `setevent`, `checkflag`, `setflag`, `verbosegiveitem`, `giveitem`, `takeitem`, `loadtrainer`, `startbattle`, `winlosstext`, `applymovement`, `checktime`, `showemote`, `variablesprite`, `gettrainername`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` | implemented | -| Item balls (`OBJECTTYPE_ITEMBALL`) | `src/world/gen2/Events.lua`, `src/world/gen2/Npc.lua` | implemented | -| Hidden items (`BGEVENT_ITEM` -> `hiddenitem`) | `src/world/gen2/HiddenItems.lua` | implemented (the header notes `World:bgEventAt` previously only answered `BGEVENT_READ`) | -| Fruit trees (`fruittree FRUITTREE_ROUTE_8`) | `src/core/gen2/Apricorns.lua` (`tryResetFruitTrees`, `treePicked`), VM `fruittree` branch `src/script/gen2/Vm.lua:1191` | implemented | -| Overworld trainers: eyesight, `trainer` struct, party build | `src/world/gen2/Trainers.lua` | implemented | -| Field moves Cut / Flash / Surf, badge order, `CutTreeBlockPointers` | `src/world/gen2/FieldMoves.lua` (`CUT = "HIVE"`, `FLASH = "ZEPHYR"`, `SURF = "FOG"`, `cutFromMenu`, `flashFromMenu`, `surfFromMenu`) | implemented | -| Badge storage incl. RAINBOWBADGE | `src/inventory/Badges.lua:12`, `src/ui/gen2/TrainerCard.lua:67` | implemented | -| Game Corner prize counters (TM + mon, coin case, party-full check) | `src/ui/gen2/PrizeMenu.lua`, `special GameCornerPrizeMonCheckDex` in `src/script/gen2/Specials.lua:964` | implemented | -| Slot machine (Celadon Game Corner floor) | `src/ui/gen2/SlotMachine.lua` | implemented | -| Magnet Train ride | `src/core/gen2/MagnetTrain.lua`, `src/ui/gen2/MagnetTrainRide.lua` | implemented | -| Pokegear radio / EXPN card channel gating | `src/ui/gen2/Pokegear.lua:810`, `:1095` | implemented | -| Marts (`pokemart MARTTYPE_STANDARD`) | `src/ui/gen2/MartMenu.lua` | implemented | -| Elevator (dept store) | `src/ui/gen2/ElevatorMenu.lua` | implemented | -| `special LoadUsedSpritesGFX` (the Copycat mimicry sprite swap) | `src/script/gen2/Specials.lua:1033` | implemented | -| `special Diploma` (mansion 3F) | `src/ui/gen2/Diploma.lua`, `src/script/gen2/Specials.lua:1853` | implemented; `special PrintDiploma` is a stub | -| Headless driver coverage for this stretch | `tests/drivers/gold_*.lua` | **missing** - all 25 gold drivers are Johto-side (`gold_walk_smoke.lua` runs New Bark -> Route 29); nothing exercises Rock Tunnel, Route 8/7, Celadon, or the Erika fight | - ---- - -## 6. Unresolved / verify by hand - -1. **"Head to the southwest part of town with a Pokemon that can use the HM01 - Cut and you'll arrive at Erika's gym."** Not supported by the asm. There is - exactly one kanto cut-tree block in `maps/CeladonCity.blk` (`$60` at block - (14,17) = cell (28,34)) and it is not on the path to the gym. The gym door is - block `$12` at (5,14), approached from walkable block `$79` at (5,15). This - looks like a carry-over from Red/Blue. Verify in-game before writing a Cut - step into a driver. -2. **Route 7 wild list.** The walkthrough gives Pidgeotto / Vulpix / Meowth. - `data/wild/kanto_grass.asm` `def_grass_wildmons ROUTE_7` in the `IF DEF(_GOLD)` - branch is Rattata / Spearow / Growlithe / Raticate / Murkrow / Houndour; - Meowth and Vulpix only appear in the `_SILVER` branch, and Pidgeotto appears - on Route 8, not Route 7. Treat the FAQ list as Silver-flavoured. -3. **Rock Tunnel item list.** The walkthrough lists Iron, PP Up, Revive and TM47 - and omits the `ELIXER` ball at `ROCK_TUNNEL_1F` (4,18), the hidden - `X_ACCURACY` at 1F (24,4), the hidden `X_DEFEND` at 1F (21,15) and the hidden - `MAX_POTION` at B1F (4,14). -4. **Erika's held items.** The walkthrough says "three Full Restores". - `data/trainers/attributes.asm:125` gives the Erika class - `db HYPER_POTION, NO_ITEM`. The count of AI item uses is engine behaviour, not - a party field; the item is a Hyper Potion, not a Full Restore. -5. **Erika's lead order.** `parties.asm:331` orders Tangela, Jumpluff, - Victreebel, Bellossom; the walkthrough's strategy section is ordered Tangela, - Victreebel, Bellossom, Jumpluff. -6. **Bellossom's Synthesis "heals half its health every turn".** `Synthesis`'s - actual healing fraction is weather-dependent in the move effect - (`engine/battle/effect_commands.asm`); not pinned down here. -7. **"Rail Pass" vs "Magnet Train Pass".** The walkthrough describes two - separate rewards ("Rail Pass" first, then "Magnet Train Pass"). In the asm - there is one item, `PASS`, given once by `Copycat.GivePass`; the "rail PASS" - wording is just `CopycatText_Male_2` / `CopycatText_Male_3` flavour. -8. **"The Saffron City guard will check your Pokedex as ID."** - `Route7SaffronGuardScript` has no `checkitem`, no coord event and no blocking - movement - it is a two-branch text switch on `EVENT_RETURNED_MACHINE_PART`. - The gate is passable regardless. -9. **"You can play slots in the middle of town"** - the slot machines' - `def_object_events` / `def_bg_events` rows in `maps/CeladonGameCorner.asm` - were not transcribed here (only its two warps); if a bot needs to sit at a - specific machine, read that file. -10. **Exact experience and prize-money numbers** quoted by the walkthrough - (e.g. "1447 EXP", "1120G") were not verified; they are computed at runtime - from base stats and `data/trainers/attributes.asm` base rewards, not stored - as table values. diff --git a/docs/gold-walkthrough/asm-walk/section-23-routes-11-19-and-fuchsia-city-gym.md b/docs/gold-walkthrough/asm-walk/section-23-routes-11-19-and-fuchsia-city-gym.md deleted file mode 100644 index 35b5b495..00000000 --- a/docs/gold-walkthrough/asm-walk/section-23-routes-11-19-and-fuchsia-city-gym.md +++ /dev/null @@ -1,1508 +0,0 @@ -# Section 23 - Routes 11-19 and Fuchsia City Gym - -Source: `../section-23-routes-11-19-and-fuchsia-city-gym.txt` -Maps covered: `MAP_ROUTE_12`, `MAP_ROUTE_11`, `MAP_ROUTE_12_SUPER_ROD_HOUSE`, -`MAP_ROUTE_13`, `MAP_ROUTE_14`, `MAP_ROUTE_15`, `MAP_ROUTE_15_FUCHSIA_GATE`, -`MAP_FUCHSIA_CITY`, `MAP_FUCHSIA_POKECENTER_1F`, `MAP_ROUTE_16`, -`MAP_ROUTE_16_GATE`, `MAP_ROUTE_16_FUCHSIA_SPEECH_HOUSE`, `MAP_ROUTE_17`, -`MAP_ROUTE_17_ROUTE_18_GATE`, `MAP_ROUTE_18`, `MAP_FUCHSIA_GYM` - -Badges / key milestones in this section: - -- `ENGINE_SOULBADGE` (Kanto badge 5) from `FuchsiaGymJanineScript` in `maps/FuchsiaGym.asm` -- `EVENT_GOT_TM06_TOXIC` -> `TM_TOXIC` (item id `$c5`, `constants/item_constants.asm:226`) -- `EVENT_GOT_SUPER_ROD` -> `SUPER_ROD` from `maps/Route12SuperRodHouse.asm` -- `ENGINE_FLYPOINT_FUCHSIA` armed on first entry to `MAP_FUCHSIA_CITY` -- Cycling Road forced-bike state (`ENGINE_ALWAYS_ON_BIKE`, `ENGINE_DOWNHILL`) - -Despite the section title, the walkthrough text never enters `MAP_ROUTE_19` -(see "Unresolved"). - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_12` | `maps/Route12.asm` | Fly to Lavender Town, walk south (`LavenderTown` south connection, `data/maps/attributes.asm:339`) | west connection to `ROUTE_11` (offset 9) | Fisher Kyle, Fisher Martin | -| 2 | `MAP_ROUTE_11` | `maps/Route11.asm` | east connection from `ROUTE_12` (offset -9) | back east to `ROUTE_12` | Herman / Owen / Jason / Fidel, Berry + hidden Revive; Snorlax dead end at the west connection | -| 3 | `MAP_ROUTE_12` | `maps/Route12.asm` | east connection from `ROUTE_11` | warp 1 at (11, 33) | Fisher Stephen, then the Super Rod house | -| 4 | `MAP_ROUTE_12_SUPER_ROD_HOUSE` | `maps/Route12SuperRodHouse.asm` | `Route12` warp 1 | warps 1/2 back to `ROUTE_12` warp 1 | Super Rod | -| 5 | `MAP_ROUTE_12` | `maps/Route12.asm` | house warp | south connection to `ROUTE_13` (offset -20) | Fisher Barney, cut tree, Calcium ball | -| 6 | `MAP_ROUTE_13` | `maps/Route13.asm` | north connection from `ROUTE_12` (offset 20) | south connection to `ROUTE_14` (offset 0) | Perry, Bret, Joshua, Alex, Kenny | -| 7 | `MAP_ROUTE_14` | `maps/Route14.asm` | north connection from `ROUTE_13` | west connection to `ROUTE_15` (offset 9) | Trevor, Kim's trade, Carter, Roy; cut trees | -| 8 | `MAP_ROUTE_15` | `maps/Route15.asm` | east connection from `ROUTE_14` (offset -9) | warps 1/2 at (2, 4)/(2, 5) | PP Up, then six schoolboys/teachers | -| 9 | `MAP_ROUTE_15_FUCHSIA_GATE` | `maps/Route15FuchsiaGate.asm` | `Route15` warps 1/2 | warps 1/2 to `FUCHSIA_CITY` warps 8/9 | pass-through gate, no check | -| 10 | `MAP_FUCHSIA_CITY` | `maps/FuchsiaCity.asm` | gate warps 8/9 at (37, 22)/(37, 23) | Fly out (`ENGINE_FLYPOINT_FUCHSIA` now set) | Burnt Berry tree, closed Safari Zone, Pokecenter | -| 11 | `MAP_ROUTE_16` | `maps/Route16.asm` | Fly to Celadon, west connection (offset 9 from `CeladonCity`) | warps 2/3 at (14, 6)/(14, 7) | optional Cut tree; Cycling Road entrance | -| 12 | `MAP_ROUTE_16_GATE` | `maps/Route16Gate.asm` | `Route16` warps 2/3 | warps 1/2 at (0, 4)/(0, 5) -> `ROUTE_16` warps 4/5 | BICYCLE check coord event | -| 13 | `MAP_ROUTE_17` | `maps/Route17.asm` | `Route16` north connection (offset 0) | warps 1/2 at (17, 82)/(17, 83) | Riley, Glenn, Joel, Charles; forced downhill bike | -| 14 | `MAP_ROUTE_17_ROUTE_18_GATE` | `maps/Route17Route18Gate.asm` | `Route17` warps 1/2 | warps 3/4 at (9, 4)/(9, 5) -> `ROUTE_18` warps 1/2 | BICYCLE check coord event | -| 15 | `MAP_ROUTE_18` | `maps/Route18.asm` | gate warps 3/4 | east connection to `FUCHSIA_CITY` (offset -7) | Bird Keeper Bob, Bird Keeper Boris | -| 16 | `MAP_FUCHSIA_CITY` | `maps/FuchsiaCity.asm` | west connection from `ROUTE_18` (offset 7) | warp 5 at (19, 27), then warp 3 at (8, 27) | heal, then gym | -| 17 | `MAP_FUCHSIA_GYM` | `maps/FuchsiaGym.asm` | `FuchsiaCity` warp 3 | warps 1/2 at (4, 17)/(5, 17) | four Janine impostors + Janine, SOULBADGE, TM06 | - -Spill-over note: the walkthrough's own Route 12 pass reaches the west connection -of `MAP_ROUTE_11` and stops at the Vermilion Snorlax. `MAP_VERMILION_CITY` and -the Snorlax event belong to a different section; only the blocker is documented -here (section 3). - ---- - -## 2. Maps - -### MAP_ROUTE_12 - -- Script: `maps/Route12.asm` -- Blocks: `maps/Route12.blk` -- Header: `data/maps/maps.asm:379` -> `map Route12, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_12, MUSIC_ROUTE_12, FALSE, PALETTE_AUTO, FISHGROUP_QWILFISH_NO_SWARM` -- Dimensions: `constants/map_constants.asm:350` -> `map_const ROUTE_12, 10, 27` (10x27 blocks = 20x54 walk cells) -- Attributes/connections: `data/maps/attributes.asm:328-331` -> `map_attributes Route12, ROUTE_12, $43`; north `LavenderTown` (0), south `Route13` (-20), west `Route11` (9) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 33 | `ROUTE_12_SUPER_ROD_HOUSE` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 27 | `BGEVENT_READ` | `Route12Sign` | -| 13 | 9 | `BGEVENT_READ` | `FishingSpotSign` | -| 14 | 13 | `BGEVENT_ITEM` | `Route12HiddenElixer` -> `hiddenitem ELIXER, EVENT_ROUTE_12_HIDDEN_ELIXER` | - -**Object events** (`def_object_events`) - `object_const_def` starts at 2 (`macros/scripts/maps.asm:9`), so `ROUTE12_FISHER1` = 2 ... `ROUTE12_POKE_BALL2` = 7 - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE12_FISHER1` (2) | `SPRITE_FISHER` | 5 | 15 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerFisherMartin` | -1 | -| `ROUTE12_FISHER2` (3) | `SPRITE_FISHER` | 15 | 28 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerFisherStephen` | -1 | -| `ROUTE12_FISHER3` (4) | `SPRITE_FISHER` | 13 | 39 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerFisherBarney` | -1 | -| `ROUTE12_FISHER4` (5) | `SPRITE_FISHER` | 6 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerFisherKyle` | -1 | -| `ROUTE12_POKE_BALL1` (6) | `SPRITE_POKE_BALL` | 5 | 43 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route12Calcium` (`itemball CALCIUM`) | `EVENT_ROUTE_12_CALCIUM` | -| `ROUTE12_POKE_BALL2` (7) | `SPRITE_POKE_BALL` | 5 | 51 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route12Nugget` (`itemball NUGGET`) | `EVENT_ROUTE_12_NUGGET` | - -**Cut trees** (block-level, not objects) - -Scanned `maps/Route12.blk` against the `TILESET_KANTO` rows of -`data/collision/field_move_blocks.asm` (`CutTreeBlockPointers.kanto`), quadrant -taken from `data/tilesets/kanto_collision.asm`: - -| block (bx, by) | block id | cut quadrant | walk cell to face | -|---|---|---|---| -| (3, 22) | `$35` | top-right | (7, 44) | -| (3, 24) | `$33` | bottom-right | (7, 49) | - -The (7, 44) tree is the one guarding the Calcium ball at (5, 43). - -**Scripts of interest** - -- `TrainerFisherKyle` / `TrainerFisherMartin` / `TrainerFisherStephen` / - `TrainerFisherBarney` - each is a bare `trainer FISHER, <NAME>, EVENT_BEAT_*, - <seen>, <beaten>, 0, .Script`; `.Script` is `endifjustbattled / opentext / - writetext <after> / waitbutton / closetext / end`. No flags beyond the beat - event, no items, no movement. -- `Route12Calcium` / `Route12Nugget` - `itemball` rows; the object's own event - flag is what makes them one-shot. -- `Route12HiddenElixer` - `hiddenitem ELIXER, EVENT_ROUTE_12_HIDDEN_ELIXER`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_FISHER_KYLE` | `constants/event_flags.asm` | `TrainerFisherKyle` | trainer already beaten | -| `EVENT_BEAT_FISHER_MARTIN` | `constants/event_flags.asm` | `TrainerFisherMartin` | trainer already beaten | -| `EVENT_BEAT_FISHER_STEPHEN` | `constants/event_flags.asm` | `TrainerFisherStephen` | trainer already beaten | -| `EVENT_BEAT_FISHER_BARNEY` | `constants/event_flags.asm` | `TrainerFisherBarney` | trainer already beaten | -| `EVENT_ROUTE_12_CALCIUM` | `constants/event_flags.asm:1323` | itemball object | ball consumed | -| `EVENT_ROUTE_12_NUGGET` | `constants/event_flags.asm:1324` | itemball object | ball consumed | -| `EVENT_ROUTE_12_HIDDEN_ELIXER` | `constants/event_flags.asm:243` | `Route12HiddenElixer` | hidden item taken | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `CALCIUM` | itemball behind the (7, 44) cut tree | `Route12Calcium` at object (5, 43) | `EVENT_ROUTE_12_CALCIUM` | -| `NUGGET` | itemball at (5, 51) | `Route12Nugget` | `EVENT_ROUTE_12_NUGGET` | -| `ELIXER` | hidden, face (14, 13) | `bg_event 14, 13, BGEVENT_ITEM` | `EVENT_ROUTE_12_HIDDEN_ELIXER` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `KYLE` | `FISHER` | Fisher (4) | L28 Seaking, L31 Poliwhirl, L31 Seaking | `TrainerFisherKyle` | none | -| `MARTIN` | `FISHER` | Fisher (13) | L32 Remoraid, L32 Remoraid | `TrainerFisherMartin` | none | -| `STEPHEN` | `FISHER` | Fisher (14) | L25 Magikarp, L25 Magikarp, L31 Qwilfish, L31 Tentacruel | `TrainerFisherStephen` | none | -| `BARNEY` | `FISHER` | Fisher (15) | L30 Gyarados x3 | `TrainerFisherBarney` | none | - -All four are `TRAINERTYPE_NORMAL` (no custom moves/items). `FISHER` base reward -is 10 (`data/trainers/attributes.asm:223`). - -**Wild encounters** - -- Grass: **none**. `ROUTE_12` has no `def_grass_wildmons` row in - `data/wild/kanto_grass.asm`. -- Water (`data/wild/kanto_water.asm:33`): `def_water_wildmons ROUTE_12`, - 6% rate - L25 Tentacool, L25 Quagsire, L25 Tentacruel (no time split; water - tables are single-slot-set). -- Fishing group `FISHGROUP_QWILFISH_NO_SWARM` (`data/wild/fish.asm:177-193`, - the `.Qwilfish_NoSwarm_*` labels alias `.Qwilfish_*`): - - Old: Magikarp 10 (70%+1), Magikarp 10 (85%+1), Tentacool 10 - - Good: Magikarp 20 (35%), Tentacool 20 (70%), Tentacool 20 (90%+1), `time_group 20` = Tentacool 20 day / Tentacool 20 nite - - Super: Tentacool 40 (40%), `time_group 21` = Tentacool 40, Magikarp 40 (90%+1), Qwilfish 40 - ---- - -### MAP_ROUTE_11 - -- Script: `maps/Route11.asm` -- Blocks: `maps/Route11.blk` -- Header: `data/maps/maps.asm:295` -> `map Route11, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_11, MUSIC_ROUTE_12, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:272` -> `map_const ROUTE_11, 20, 9` (40x18 cells) -- Attributes/connections: `data/maps/attributes.asm:333-336` -> `$0f`; west `VermilionCity` (0), east `Route12` (-9) - -**Warps** (`def_warp_events`) - -None. - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 7 | `BGEVENT_READ` | `Route11Sign` | -| 32 | 5 | `BGEVENT_ITEM` | `Route11HiddenRevive` -> `hiddenitem REVIVE, EVENT_ROUTE_11_HIDDEN_REVIVE` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE11_YOUNGSTER1` (2) | `SPRITE_YOUNGSTER` | 22 | 14 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerYoungsterOwen` | -1 | -| `ROUTE11_YOUNGSTER2` (3) | `SPRITE_YOUNGSTER` | 15 | 9 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerYoungsterJason` | -1 | -| `ROUTE11_YOUNGSTER3` (4) | `SPRITE_YOUNGSTER` | 29 | 7 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerPsychicHerman` | -1 | -| `ROUTE11_YOUNGSTER4` (5) | `SPRITE_YOUNGSTER` | 7 | 4 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPsychicFidel` | -1 | -| `ROUTE11_FRUIT_TREE` (6) | `SPRITE_FRUIT_TREE` | 32 | 2 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route11FruitTree` (`fruittree FRUITTREE_ROUTE_11`) | -1 | - -Note the two `PSYCHIC_T` trainers wear `SPRITE_YOUNGSTER`, not a psychic sprite. - -**Scripts of interest** - -- `Route11FruitTree` - single `fruittree FRUITTREE_ROUTE_11`. - `data/items/fruit_trees.asm` gives index 24 (`$18`) = `BERRY`. Fruit trees - are day-tracked, not one-shot events. -- `Route11HiddenRevive` - `hiddenitem REVIVE, EVENT_ROUTE_11_HIDDEN_REVIVE`, - faced from cell (32, 5), directly below the fruit tree. -- The four trainer scripts are the plain `endifjustbattled` pattern. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_YOUNGSTER_OWEN` | `constants/event_flags.asm` | `TrainerYoungsterOwen` | trainer beaten | -| `EVENT_BEAT_YOUNGSTER_JASON` | `constants/event_flags.asm` | `TrainerYoungsterJason` | trainer beaten | -| `EVENT_BEAT_PSYCHIC_HERMAN` | `constants/event_flags.asm` | `TrainerPsychicHerman` | trainer beaten | -| `EVENT_BEAT_PSYCHIC_FIDEL` | `constants/event_flags.asm` | `TrainerPsychicFidel` | trainer beaten | -| `EVENT_ROUTE_11_HIDDEN_REVIVE` | `constants/event_flags.asm:245` | `Route11HiddenRevive` | hidden item taken | -| `FRUITTREE_ROUTE_11` | `constants/script_constants.asm:230` | `Route11FruitTree` | fruit tree slot 24 | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BERRY` | talk to the tree object at (32, 2) | `Route11FruitTree` / `data/items/fruit_trees.asm` | none (daily tree, not an event flag) | -| `REVIVE` | hidden, face (32, 5) | `bg_event 32, 5, BGEVENT_ITEM` | `EVENT_ROUTE_11_HIDDEN_REVIVE` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `HERMAN` | `PSYCHIC_T` | Psychic (3) | L30 Exeggcute, L30 Exeggcute, L30 Exeggutor | `TrainerPsychicHerman` | none | -| `OWEN` | `YOUNGSTER` | Youngster (11) | L35 Growlithe | `TrainerYoungsterOwen` | none | -| `JASON` | `YOUNGSTER` | Youngster (12) | L33 Sandslash, L33 Crobat | `TrainerYoungsterJason` | none | -| `FIDEL` | `PSYCHIC_T` | Psychic (4) | L34 Xatu | `TrainerPsychicFidel` | none | - -Base rewards: `YOUNGSTER` 4 (`attributes.asm:133`), `PSYCHIC_T` 8 (`attributes.asm:313`). - -**Wild encounters** - -`data/wild/kanto_grass.asm:728` `def_grass_wildmons ROUTE_11`, 10%/10%/10%. -Morn, day and nite are the same seven slots: -L14 Drowzee, L15 Rattata, L15 Magnemite, L16 Drowzee, L16 Hypno, L16 Hypno, L16 Hypno. - -No water table, no `def_grass_wildmons` time split. The walkthrough's species -list omits Magnemite. - ---- - -### MAP_ROUTE_12_SUPER_ROD_HOUSE - -- Script: `maps/Route12SuperRodHouse.asm` -- Header: `data/maps/maps.asm:391` -> `map Route12SuperRodHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_12, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:362` -> `map_const ROUTE_12_SUPER_ROD_HOUSE, 4, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_12` | 1 | -| 2 | 3 | 7 | `ROUTE_12` | 1 | - -**Coord events** / **BG events** - -None. (`SuperRodHouseBookshelf` exists in the file but is marked -`; unreferenced` - no bg_event points at it.) - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE12SUPERRODHOUSE_FISHING_GURU` (2) | `SPRITE_FISHING_GURU` | 5 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route12SuperRodHouseFishingGuruScript` | -1 | - -**Scripts of interest** - -`Route12SuperRodHouseFishingGuruScript` (`5d:4dc7` in -`pokegold-symbols/pokegold.sym`): - -``` -faceplayer / opentext -checkevent EVENT_GOT_SUPER_ROD -iftrue .GotSuperRod -writetext OfferSuperRodText -yesorno -iffalse .Refused ; "No" -> DontWantSuperRodText, closetext -writetext GiveSuperRodText -promptbutton -verbosegiveitem SUPER_ROD -iffalse .NoRoom ; bag full -> closetext, nothing set -setevent EVENT_GOT_SUPER_ROD -.GotSuperRod: writetext GaveSuperRodText / waitbutton / closetext / end -``` - -Bot-relevant: the `yesorno` must be answered **Yes**, and the KEY ITEMS pocket -must have room, or `EVENT_GOT_SUPER_ROD` never gets set and the script is -re-runnable. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_SUPER_ROD` | `constants/event_flags.asm:33` | `Route12SuperRodHouseFishingGuruScript` | Super Rod already handed over | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `SUPER_ROD` | talk to guru, answer Yes | `Route12SuperRodHouseFishingGuruScript` | `EVENT_GOT_SUPER_ROD` | - -**Trainers** / **Wild encounters** - -None. - ---- - -### MAP_ROUTE_13 - -- Script: `maps/Route13.asm` -- Blocks: `maps/Route13.blk` -- Header: `data/maps/maps.asm:361` -> `map Route13, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_13, MUSIC_ROUTE_12, FALSE, PALETTE_AUTO, FISHGROUP_QWILFISH_NO_SWARM` -- Dimensions: `constants/map_constants.asm:333` -> `map_const ROUTE_13, 30, 9` (60x18 cells) -- Attributes/connections: `data/maps/attributes.asm:324-326` -> `$43`; north `Route12` (20), south `Route14` (0) - -**Warps** / **Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 29 | 13 | `BGEVENT_READ` | `Route13TrainerTips` ("Look! Right there, at the left side of the post.") | -| 41 | 11 | `BGEVENT_READ` | `Route13Sign` | -| 17 | 13 | `BGEVENT_READ` | `Route13DirectionsSign` | -| 30 | 13 | `BGEVENT_ITEM` | `Route13HiddenCalcium` -> `hiddenitem CALCIUM, EVENT_ROUTE_13_HIDDEN_CALCIUM` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE13_YOUNGSTER1` (2) | `SPRITE_YOUNGSTER` | 42 | 6 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerBirdKeeperPerry` | -1 | -| `ROUTE13_YOUNGSTER2` (3) | `SPRITE_YOUNGSTER` | 43 | 6 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerBirdKeeperBret` | -1 | -| `ROUTE13_POKEFAN_M1` (4) | `SPRITE_POKEFAN_M` | 32 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPokefanmJoshua` | -1 | -| `ROUTE13_POKEFAN_M2` (5) | `SPRITE_POKEFAN_M` | 14 | 10 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerHikerKenny` | -1 | -| `ROUTE13_POKEFAN_M3` (6) | `SPRITE_POKEFAN_M` | 25 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerPokefanmAlex` | -1 | - -Kenny is class `HIKER` but wears `SPRITE_POKEFAN_M`. - -**Cut trees** - -`maps/Route13.blk` block (22, 2) = `$34` (`CUT_TREE` in the top-left quadrant) --> face walk cell (44, 4). Not mentioned by the walkthrough. - -**Scripts of interest** - -All five trainers use the plain `trainer <CLASS>, <NAME>, EVENT_BEAT_*, ...` + -`endifjustbattled` pattern. `Route13HiddenCalcium` is the only item. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BIRD_KEEPER_PERRY` | `constants/event_flags.asm` | `TrainerBirdKeeperPerry` | trainer beaten | -| `EVENT_BEAT_BIRD_KEEPER_BRET` | `constants/event_flags.asm` | `TrainerBirdKeeperBret` | trainer beaten | -| `EVENT_BEAT_POKEFANM_JOSHUA` | `constants/event_flags.asm` | `TrainerPokefanmJoshua` | trainer beaten | -| `EVENT_BEAT_POKEFANM_ALEX` | `constants/event_flags.asm` | `TrainerPokefanmAlex` | trainer beaten | -| `EVENT_BEAT_HIKER_KENNY` | `constants/event_flags.asm` | `TrainerHikerKenny` | trainer beaten | -| `EVENT_ROUTE_13_HIDDEN_CALCIUM` | `constants/event_flags.asm:244` | `Route13HiddenCalcium` | hidden item taken | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `CALCIUM` | hidden, face (30, 13) (the sign at (29, 13) points at it) | `bg_event 30, 13, BGEVENT_ITEM` | `EVENT_ROUTE_13_HIDDEN_CALCIUM` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `PERRY` | `BIRD_KEEPER` | BirdKeeper (15) | L34 Farfetch'd | `TrainerBirdKeeperPerry` | none | -| `BRET` | `BIRD_KEEPER` | BirdKeeper (16) | L32 Pidgeotto, L32 Fearow | `TrainerBirdKeeperBret` | none | -| `JOSHUA` | `POKEFANM` | PokefanM (4), `TRAINERTYPE_ITEM` | L23 Pikachu x6, each holding `BERRY` | `TrainerPokefanmJoshua` | none | -| `ALEX` | `POKEFANM` | PokefanM (12), `TRAINERTYPE_ITEM` | L29 Nidoking/`BERRY`, L29 Slowking/`BERRY`, L29 Seaking/`BERRY` | `TrainerPokefanmAlex` | none | -| `KENNY` | `HIKER` | Hiker (16) | L27 Sandslash, L29 Graveler, L31 Golem, L29 Graveler | `TrainerHikerKenny` | none | - -Base rewards: `BIRD_KEEPER` 6, `POKEFANM` 20, `HIKER` 8. - -**Wild encounters** - -`data/wild/kanto_grass.asm:756` `def_grass_wildmons ROUTE_13`, 10%/10%/10%, -`_GOLD` arm: - -- morn: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L22 Hoppip, L24 Hoppip, L22 Quagsire, L25 Chansey -- day: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L22 Hoppip, L24 Hoppip, L24 Hoppip, L25 Chansey -- nite: L23 Nidorina, L23 Nidorino, L25 Noctowl, L22 Quagsire, L24 Quagsire, L24 Quagsire, L25 Chansey - -(`_SILVER` swaps the Nidorina/Nidorino order.) - -Water: `data/wild/kanto_water.asm:40` `def_water_wildmons ROUTE_13`, 6% - -L25 Tentacool, L25 Quagsire, L25 Tentacruel. Fishing group -`FISHGROUP_QWILFISH_NO_SWARM` (same table as Route 12). - ---- - -### MAP_ROUTE_14 - -- Script: `maps/Route14.asm` -- Blocks: `maps/Route14.blk` -- Header: `data/maps/maps.asm:362` -> `map Route14, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_14, MUSIC_ROUTE_12, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:334` -> `map_const ROUTE_14, 10, 18` (20x36 cells) -- Attributes/connections: `data/maps/attributes.asm:320-322` -> `$43`; north `Route13` (0), west `Route15` (9) - -**Warps** / **Coord events** / **BG events** - -None (all three tables are empty). - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE14_POKEFAN_M1` (2) | `SPRITE_POKEFAN_M` | 12 | 14 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerPokefanmCarter` | -1 | -| `ROUTE14_YOUNGSTER` (3) | `SPRITE_YOUNGSTER` | 11 | 27 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperRoy` | -1 | -| `ROUTE14_POKEFAN_M2` (4) | `SPRITE_POKEFAN_M` | 5 | 9 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerPokefanmTrevor` | -1 | -| `ROUTE14_KIM` (5) | `SPRITE_TEACHER` | 7 | 5 | `WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT`, sight 4 | `Kim` | -1 | - -**Cut trees** - -From `maps/Route14.blk` against `CutTreeBlockPointers.kanto`: - -| block (bx, by) | block id | cut quadrant | walk cell to face | -|---|---|---|---| -| (2, 4) | `$35` | top-right | (5, 8) | -| (5, 7) | `$35` | top-right | (11, 14) | -| (1, 12) | `$35` | top-right | (3, 24) | - -(5, 8) is directly above Trevor at (5, 9) and opens the pocket that holds Kim. -(11, 14) sits beside Carter at (12, 14). (3, 24) is the tree on the way to the -west connection to Route 15. - -**Scripts of interest** - -- `Kim` - `faceplayer / opentext / trade NPC_TRADE_KIM / waitbutton / closetext / - end`. `NPC_TRADE_KIM` is 5 (`constants/npc_trade_constants.asm:22`); - `data/events/npc_trades.asm:19` -> - `npctrade TRADE_DIALOGSET_NEWBIE, CHANSEY, AERODACTYL, "AEROY", $96, $66, GOLD_BERRY, 26491, "KIM", TRADE_GENDER_EITHER`. - You hand over **Chansey**, you receive **Aerodactyl** nicknamed AEROY holding - `GOLD_BERRY`, OT KIM / ID 26491, DVs `$96`/`$66`. One-shot, tracked in - `wTradeFlags` by trade id, not by an `EVENT_*`. -- The three trainer scripts are the plain pattern. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_POKEFANM_CARTER` | `constants/event_flags.asm` | `TrainerPokefanmCarter` | trainer beaten | -| `EVENT_BEAT_BIRD_KEEPER_ROY` | `constants/event_flags.asm` | `TrainerBirdKeeperRoy` | trainer beaten | -| `EVENT_BEAT_POKEFANM_TREVOR` | `constants/event_flags.asm` | `TrainerPokefanmTrevor` | trainer beaten | -| `NPC_TRADE_KIM` | `constants/npc_trade_constants.asm:22` | `Kim` | trade slot 5, stored in `wTradeFlags` | - -**Items** - -None on this map. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `TREVOR` | `POKEFANM` | PokefanM (6), `TRAINERTYPE_ITEM` | L33 Psyduck/`BERRY` | `TrainerPokefanmTrevor` | none | -| `CARTER` | `POKEFANM` | PokefanM (5), `TRAINERTYPE_ITEM` | L29 Bulbasaur/`BERRY`, L29 Charmander/`BERRY`, L29 Squirtle/`BERRY` | `TrainerPokefanmCarter` | none | -| `ROY` | `BIRD_KEEPER` | BirdKeeper (9) | L29 Fearow, L35 Fearow | `TrainerBirdKeeperRoy` | none | - -**Wild encounters** - -`data/wild/kanto_grass.asm:811` `def_grass_wildmons ROUTE_14`, 10%/10%/10%, -`_GOLD` arm: - -- morn: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L24 Hoppip, L26 Skiploom, L22 Quagsire, L25 Chansey -- day: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L24 Hoppip, L26 Skiploom, L26 Skiploom, L25 Chansey -- nite: L23 Nidorina, L23 Nidorino, L25 Noctowl, L22 Quagsire, L24 Quagsire, L24 Quagsire, L25 Chansey - -No water table for `ROUTE_14`. Fishing group `FISHGROUP_SHORE`. - ---- - -### MAP_ROUTE_15 - -- Script: `maps/Route15.asm` -- Blocks: `maps/Route15.blk` -- Header: `data/maps/maps.asm:363` -> `map Route15, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_15, MUSIC_ROUTE_12, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:335` -> `map_const ROUTE_15, 20, 9` (40x18 cells) -- Attributes/connections: `data/maps/attributes.asm:316-318` -> `$0f`; west `FuchsiaCity` (-9), east `Route14` (-9) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 4 | `ROUTE_15_FUCHSIA_GATE` | 3 | -| 2 | 2 | 5 | `ROUTE_15_FUCHSIA_GATE` | 4 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 19 | 9 | `BGEVENT_READ` | `Route15Sign` (documented lowercase-"Route 15" bug, `maps/Route15.asm:196`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE15_YOUNGSTER1` (2) | `SPRITE_YOUNGSTER` | 11 | 10 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSchoolboyKipp` | -1 | -| `ROUTE15_YOUNGSTER2` (3) | `SPRITE_YOUNGSTER` | 11 | 11 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerSchoolboyTommy` | -1 | -| `ROUTE15_YOUNGSTER3` (4) | `SPRITE_YOUNGSTER` | 33 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSchoolboyJohnny` | -1 | -| `ROUTE15_YOUNGSTER4` (5) | `SPRITE_YOUNGSTER` | 27 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSchoolboyBilly` | -1 | -| `ROUTE15_TEACHER1` (6) | `SPRITE_TEACHER` | 30 | 12 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerTeacherColette` | -1 | -| `ROUTE15_TEACHER2` (7) | `SPRITE_TEACHER` | 16 | 10 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerTeacherHillary` | -1 | -| `ROUTE15_POKE_BALL` (8) | `SPRITE_POKE_BALL` | 12 | 5 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route15PPUp` (`itemball PP_UP`) | `EVENT_ROUTE_15_PP_UP` | - -**Cut trees** - -None. `maps/Route15.blk` contains no `TILESET_KANTO` cut-tree block -(`$32`/`$33`/`$34`/`$35`/`$60`). See "Unresolved". - -**Scripts of interest** - -All six trainers are the plain pattern. `Route15PPUp` is `itemball PP_UP`. - -Walkthrough fight order maps to: Johnny (33, 10) -> Colette (30, 12) -> -Billy (27, 10) -> Hillary (16, 10) -> Tommy (11, 11) -> Kipp (11, 10), -i.e. east to west along row y=10. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SCHOOLBOY_JOHNNY` | `constants/event_flags.asm` | `TrainerSchoolboyJohnny` | trainer beaten | -| `EVENT_BEAT_TEACHER_COLETTE` | `constants/event_flags.asm` | `TrainerTeacherColette` | trainer beaten | -| `EVENT_BEAT_SCHOOLBOY_BILLY` | `constants/event_flags.asm` | `TrainerSchoolboyBilly` | trainer beaten | -| `EVENT_BEAT_TEACHER_HILLARY` | `constants/event_flags.asm` | `TrainerTeacherHillary` | trainer beaten | -| `EVENT_BEAT_SCHOOLBOY_TOMMY` | `constants/event_flags.asm` | `TrainerSchoolboyTommy` | trainer beaten | -| `EVENT_BEAT_SCHOOLBOY_KIP` | `constants/event_flags.asm` | `TrainerSchoolboyKipp` | trainer beaten (note the flag drops the second "p") | -| `EVENT_ROUTE_15_PP_UP` | `constants/event_flags.asm:1325` | itemball object | ball consumed | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `PP_UP` | itemball at (12, 5) | `Route15PPUp` | `EVENT_ROUTE_15_PP_UP` | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `JOHNNY` | `SCHOOLBOY` | Schoolboy (4) | L29 Bellsprout, L31 Weepinbell, L33 Victreebel | `TrainerSchoolboyJohnny` | none | -| `COLETTE` | `TEACHER` | Teacher (1) | L36 Clefairy | `TrainerTeacherColette` | none | -| `BILLY` | `SCHOOLBOY` | Schoolboy (9) | L27 Paras, L27 Paras, L27 Poliwhirl, L35 Ditto | `TrainerSchoolboyBilly` | none | -| `HILLARY` | `TEACHER` | Teacher (2) | L32 Aipom, L36 Cubone | `TrainerTeacherHillary` | none | -| `TOMMY` | `SCHOOLBOY` | Schoolboy (6) | L32 Xatu, L34 Alakazam | `TrainerSchoolboyTommy` | none | -| `KIPP` | `SCHOOLBOY` | Schoolboy (2) | L27 Voltorb, L27 Magnemite, L31 Voltorb, L31 Magneton | `TrainerSchoolboyKipp` | none | - -Base rewards: `SCHOOLBOY` 8, `TEACHER` 18. - -**Wild encounters** - -`data/wild/kanto_grass.asm:866` `def_grass_wildmons ROUTE_15`, 10%/10%/10%, -`_GOLD` arm: - -- morn: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L22 Hoppip, L24 Hoppip, L22 Quagsire, L25 Chansey -- day: L23 Nidorina, L23 Nidorino, L25 Pidgeotto, L22 Hoppip, L24 Hoppip, L24 Hoppip, L25 Chansey -- nite: L23 Nidorina, L23 Nidorino, L25 Noctowl, L22 Quagsire, L24 Quagsire, L24 Quagsire, L25 Chansey - ---- - -### MAP_ROUTE_15_FUCHSIA_GATE - -- Script: `maps/Route15FuchsiaGate.asm` -- Header: `data/maps/maps.asm:373` -> `map Route15FuchsiaGate, TILESET_GATE, GATE, LANDMARK_ROUTE_15, MUSIC_ROUTE_12, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:345` -> `map_const ROUTE_15_FUCHSIA_GATE, 5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `FUCHSIA_CITY` | 8 | -| 2 | 0 | 5 | `FUCHSIA_CITY` | 9 | -| 3 | 9 | 4 | `ROUTE_15` | 1 | -| 4 | 9 | 5 | `ROUTE_15` | 2 | - -**Coord events** / **BG events** - -None. This gate has **no** bicycle or badge check - it is free passage. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE15FUCHSIAGATE_OFFICER` (2) | `SPRITE_OFFICER` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route15FuchsiaGateOfficerScript` (`jumptextfaceplayer`) | -1 | - ---- - -### MAP_FUCHSIA_CITY - -- Script: `maps/FuchsiaCity.asm` -- Blocks: `maps/FuchsiaCity.blk` -- Header: `data/maps/maps.asm:365` -> `map FuchsiaCity, TILESET_KANTO, TOWN, LANDMARK_FUCHSIA_CITY, MUSIC_CELADON_CITY, FALSE, PALETTE_AUTO, FISHGROUP_GYARADOS` -- Dimensions: `constants/map_constants.asm:337` -> `map_const FUCHSIA_CITY, 20, 18` (40x36 cells) -- Attributes/connections: `data/maps/attributes.asm:291-294` -> `$0f`; south `Route19` (0), west `Route18` (7), east `Route15` (9) -- Fly spawn: `data/maps/spawn_points.asm:23` -> `spawn FUCHSIA_CITY, 19, 28`; `data/maps/flypoints.asm:27` -> `db LANDMARK_FUCHSIA_CITY, SPAWN_FUCHSIA` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 13 | `FUCHSIA_MART` | 2 | -| 2 | 22 | 13 | `SAFARI_ZONE_MAIN_OFFICE` | 1 | -| 3 | 8 | 27 | `FUCHSIA_GYM` | 1 | -| 4 | 11 | 27 | `BILLS_OLDER_SISTERS_HOUSE` | 1 | -| 5 | 19 | 27 | `FUCHSIA_POKECENTER_1F` | 1 | -| 6 | 27 | 27 | `SAFARI_ZONE_WARDENS_HOME` | 1 | -| 7 | 18 | 3 | `SAFARI_ZONE_FUCHSIA_GATE_BETA` | 3 (commented `; inaccessible` in the asm) | -| 8 | 37 | 22 | `ROUTE_15_FUCHSIA_GATE` | 1 | -| 9 | 37 | 23 | `ROUTE_15_FUCHSIA_GATE` | 2 | -| 10 | 7 | 35 | `ROUTE_19_FUCHSIA_GATE` | 1 | -| 11 | 8 | 35 | `ROUTE_19_FUCHSIA_GATE` | 2 | - -Warp 7 is the "the door is gone" Safari Zone entrance: the warp row exists but -the block behind it is not walkable, which is why the walkthrough says the door -is missing. - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 21 | 15 | `BGEVENT_READ` | `FuchsiaCitySign` | -| 5 | 29 | `BGEVENT_READ` | `FuchsiaGymSign` | -| 25 | 15 | `BGEVENT_READ` | `SafariZoneOfficeSign` | -| 27 | 29 | `BGEVENT_READ` | `WardensHomeSign` | -| 17 | 5 | `BGEVENT_READ` | `SafariZoneClosedSign` | -| 13 | 15 | `BGEVENT_READ` | `NoLitteringSign` | -| 20 | 27 | `BGEVENT_READ` | `FuchsiaCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 6 | 13 | `BGEVENT_READ` | `FuchsiaCityMartSign` (`jumpstd MartSignScript`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `FUCHSIACITY_YOUNGSTER` (2) | `SPRITE_YOUNGSTER` | 23 | 18 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `FuchsiaCityYoungster` | -1 | -| `FUCHSIACITY_POKEFAN_M` (3) | `SPRITE_POKEFAN_M` | 13 | 8 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `FuchsiaCityPokefanM` | -1 | -| `FUCHSIACITY_TEACHER` (4) | `SPRITE_TEACHER` | 16 | 14 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `FuchsiaCityTeacher` | -1 | -| `FUCHSIACITY_FRUIT_TREE` (5) | `SPRITE_FRUIT_TREE` | 8 | 1 | `STILL` | `OBJECTTYPE_SCRIPT` | `FuchsiaCityFruitTree` (`fruittree FRUITTREE_FUCHSIA_CITY`) | -1 | - -**Cut trees** - -`maps/FuchsiaCity.blk`: block (8, 5) and block (9, 9), both `$60` (`CUT_TREE` -in the bottom-left quadrant) -> face walk cells (16, 11) and (18, 19). Neither -is required by this section. - -**Scripts of interest** - -- `FuchsiaCityFlypointCallback` (`4e:53c4`) - registered as - `callback MAPCALLBACK_NEWMAP`; body is `setflag ENGINE_FLYPOINT_FUCHSIA / - endcallback`. Runs on the first map load, so simply arriving unlocks Fly here. -- `FuchsiaCityFruitTree` - `fruittree FRUITTREE_FUCHSIA_CITY`. - `data/items/fruit_trees.asm` slot 30 (`$1e`) = `BURNT_BERRY`, which is the - walkthrough's "Burnt Berry in the northwest part of town" - the tree object at - (8, 1) is the north-west corner of the map. -- `SafariZoneClosedSign` / `SafariZoneOfficeSign` - flavour text only; there is - no event flag or script that ever reopens the Safari Zone. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_FUCHSIA` | `constants/engine_flags.asm:76` | `FuchsiaCityFlypointCallback` | Fuchsia becomes a Fly destination | -| `FRUITTREE_FUCHSIA_CITY` | `constants/script_constants.asm:236` | `FuchsiaCityFruitTree` | fruit tree slot 30 | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BURNT_BERRY` | talk to the tree object at (8, 1) | `FuchsiaCityFruitTree` / `data/items/fruit_trees.asm` | none (daily tree) | - -**Trainers** - -None on the overworld map. - -**Wild encounters** - -No grass table. Water: `data/wild/kanto_water.asm:152` -`def_water_wildmons FUCHSIA_CITY`, 2% - L20 Magikarp, L15 Magikarp, L10 Magikarp. -Fishing group `FISHGROUP_GYARADOS` (`data/wild/fish.asm:132-145`): -Old rod Magikarp 10 x3; Good rod Magikarp 20 x3 + `time_group 14` (Gyarados 20); -Super rod Magikarp 40 (40%), `time_group 15` (Gyarados 40), Magikarp 40, Magikarp 40. - ---- - -### MAP_FUCHSIA_POKECENTER_1F - -- Script: `maps/FuchsiaPokecenter1F.asm` -- Header: `data/maps/maps.asm:370` -> `map FuchsiaPokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_FUCHSIA_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:342` -> `map_const FUCHSIA_POKECENTER_1F, 5, 4` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `FUCHSIA_CITY` | 5 | -| 2 | 4 | 7 | `FUCHSIA_CITY` | 5 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -Healing is `FuchsiaPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` -(object const `FUCHSIAPOKECENTER1F_NURSE` = 2). The map also contains a -`FUCHSIAPOKECENTER1F_JANINE_IMPERSONATOR` NPC that spins and `variablesprite`s -itself into `SPRITE_JANINE` - flavour, no flags. - ---- - -### MAP_ROUTE_16 - -- Script: `maps/Route16.asm` -- Blocks: `maps/Route16.blk` -- Header: `data/maps/maps.asm:413` -> `map Route16, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_16, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:381` -> `map_const ROUTE_16, 10, 9` (20x18 cells) -- Attributes/connections: `data/maps/attributes.asm:304-306` -> `$0f`; south `Route17` (0), east `CeladonCity` (-9) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 1 | `ROUTE_16_FUCHSIA_SPEECH_HOUSE` | 1 | -| 2 | 14 | 6 | `ROUTE_16_GATE` | 3 | -| 3 | 14 | 7 | `ROUTE_16_GATE` | 4 | -| 4 | 9 | 6 | `ROUTE_16_GATE` | 1 | -| 5 | 9 | 7 | `ROUTE_16_GATE` | 2 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 5 | `BGEVENT_READ` | `CyclingRoadSign` | - -**Object events** - -None (`def_object_events` is empty). - -**Cut trees** - -`maps/Route16.blk` block (7, 2) = `$32` (`CUT_TREE` top-right) -> face walk -cell (15, 4). This is the tree the walkthrough calls "north of the -route-changing building". Cut trees are **not** persisted: leaving the map (for -example into `ROUTE_16_FUCHSIA_SPEECH_HOUSE`) reloads the block from -`Route16.blk` and the tree is back, which is exactly the behaviour the -walkthrough describes. - -**Scripts of interest** - -`Route16AlwaysOnBikeCallback` (`4e:5a8f`), a `MAPCALLBACK_NEWMAP` callback: - -``` -readvar VAR_YCOORD -ifless 5, .CanWalk -readvar VAR_XCOORD -ifgreater 13, .CanWalk -setflag ENGINE_ALWAYS_ON_BIKE -endcallback -.CanWalk: -clearflag ENGINE_ALWAYS_ON_BIKE -endcallback -``` - -So the north-west corner of Route 16 (`y < 5`, or `x > 13`) is walkable; the -rest of the map forces the bike. Because this is a `MAPCALLBACK_NEWMAP`, it only -re-evaluates on a map load, not while walking. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_ALWAYS_ON_BIKE` | `constants/engine_flags.asm:35` | `Route16AlwaysOnBikeCallback` | dismount, Surf and (via `BIKEFLAGS_ALWAYS_ON_BIKE_F`) some menu actions are refused | - -**Wild encounters** - -`data/wild/kanto_grass.asm:921` `def_grass_wildmons ROUTE_16`, 10%/10%/10%: - -- morn/day (identical): L26 Grimer, L27 Fearow, L28 Grimer, L29 Fearow, L27 Slugma, L30 Muk, L30 Muk -- nite: L26 Grimer, L27 Grimer, L28 Grimer, L28 Murkrow, L27 Slugma, L30 Muk, L30 Muk - -The walkthrough's "Fearow / Grimer" summary is the morn/day view; at night -Fearow is replaced by Grimer and Murkrow. - ---- - -### MAP_ROUTE_16_GATE - -- Script: `maps/Route16Gate.asm` -- Header: `data/maps/maps.asm:435` -> `map Route16Gate, TILESET_GATE, GATE, LANDMARK_ROUTE_16, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:403` -> `map_const ROUTE_16_GATE, 5, 4` -- Scene variable: `data/maps/scenes.asm:18` -> `scene_var ROUTE_16_GATE, wRoute16GateSceneID` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ROUTE_16` | 4 | -| 2 | 0 | 5 | `ROUTE_16` | 5 | -| 3 | 9 | 4 | `ROUTE_16` | 2 | -| 4 | 9 | 5 | `ROUTE_16` | 3 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE16GATE_BICYCLE_CHECK` (= 0) | 5 | 4 | `Route16GateBicycleCheck` | `checkitem BICYCLE`; on failure, shove the player one step RIGHT | -| `SCENE_ROUTE16GATE_BICYCLE_CHECK` (= 0) | 5 | 5 | `Route16GateBicycleCheck` | same | - -`SCENE_ROUTE16GATE_BICYCLE_CHECK` is generated by the `scene_script` macro -(`macros/scripts/maps.asm:25`, which emits `scene_const` into a `const_def` -starting at 0), so its value is 0. Nothing anywhere calls `setmapscene` on this -map, so the scene never changes and the coord event fires on **every** pass. - -**BG events** - -None. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE16GATE_OFFICER` (2) | `SPRITE_OFFICER` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route16GateOfficerScript` | -1 | - -**Scripts of interest** - -`Route16GateBicycleCheck` (`5e:67cb`): - -``` -checkitem BICYCLE -iffalse .NoBicycle -end -.NoBicycle: -showemote EMOTE_SHOCK, ROUTE16GATE_OFFICER, 15 -turnobject PLAYER, UP -opentext / writetext Route16GateCannotPassText / waitbutton / closetext -applymovement PLAYER, Route16GateCannotPassMovement ; step RIGHT, turn_head LEFT -end -``` - -`checkitem` is a **bag** check, not a "is the bike out" check: the BICYCLE must -be in the pack, but the player does not have to be riding it to pass. - ---- - -### MAP_ROUTE_16_FUCHSIA_SPEECH_HOUSE - -- Script: `maps/Route16FuchsiaSpeechHouse.asm` -- Header: `data/maps/maps.asm:434` -> `map Route16FuchsiaSpeechHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_16, MUSIC_CELADON_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:402` -> `map_const ROUTE_16_FUCHSIA_SPEECH_HOUSE, 4, 4` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_16` | 1 | -| 2 | 3 | 7 | `ROUTE_16` | 1 | - -**BG events**: (0, 1) and (1, 1), both `BGEVENT_READ` -> -`Route16FuchsiaSpeechHouseBookshelf` (`jumpstd PictureBookshelfScript`). - -**Object events**: `ROUTE16FUCHSIASPEECHHOUSE_SUPER_NERD` (2), -`SPRITE_SUPER_NERD` at (2, 3), `STANDING_DOWN`, `OBJECTTYPE_SCRIPT`, -`Route16FuchsiaSpeechHouseSuperNerdScript` (text only). - -This is the building whose entry/exit regrows the Route 16 cut tree. - ---- - -### MAP_ROUTE_17 - -- Script: `maps/Route17.asm` -- Blocks: `maps/Route17.blk` -- Header: `data/maps/maps.asm:414` -> `map Route17, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_17, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_NONE` -- Dimensions: `constants/map_constants.asm:382` -> `map_const ROUTE_17, 10, 45` (20x90 cells) -- Attributes/connections: `data/maps/attributes.asm:300-302` -> `$43`; north `Route16` (0), east `Route18` (38) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 82 | `ROUTE_17_ROUTE_18_GATE` | 1 | -| 2 | 17 | 83 | `ROUTE_17_ROUTE_18_GATE` | 2 | - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 12 | 5 | `BGEVENT_ITEM` | `Route17HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_17_HIDDEN_MAX_ETHER` | -| 8 | 77 | `BGEVENT_ITEM` | `Route17HiddenMaxElixer` -> `hiddenitem MAX_ELIXER, EVENT_ROUTE_17_HIDDEN_MAX_ELIXER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE17_BIKER1` (2) | `SPRITE_BIKER` | 4 | 17 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerBikerRiley` | -1 | -| `ROUTE17_BIKER2` (3) | `SPRITE_BIKER` | 16 | 32 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBikerJoel` | -1 | -| `ROUTE17_BIKER3` (4) | `SPRITE_BIKER` | 3 | 53 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBikerGlenn` | -1 | -| `ROUTE17_BIKER4` (5) | `SPRITE_BIKER` | 6 | 80 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerBikerCharles` | -1 | - -Note the y-order: Riley (17) -> Joel (32) -> Glenn (53) -> Charles (80). The -walkthrough fights Riley, Glenn, Joel, Charles; Joel at (16, 32) is on the right -bank (the walkthrough's "bridge across the river to the right") and Glenn at -(3, 53) is further down the left, so the asm ordering by y is -Riley -> Joel -> Glenn -> Charles. - -**Scripts of interest** - -`Route17AlwaysOnBikeCallback` (`4e:5822`), a `MAPCALLBACK_NEWMAP` callback: - -``` -setflag ENGINE_ALWAYS_ON_BIKE -setflag ENGINE_DOWNHILL -endcallback -``` - -Unconditional for the whole map. `ENGINE_DOWNHILL` is what -`engine/overworld/player_movement.asm:13` `.GetDPad` reads (the `bit -BIKEFLAGS_DOWNHILL_F, [hl]` at line 20): on a downhill map -a frame with **no** d-pad held is rewritten to `PAD_DOWN`, i.e. the bike coasts -south on its own; and `player_movement.asm:277-292` downgrades any non-DOWN bike -step from `STEP_BIKE` to `STEP_WALK`, which is the "uphill is slower" behaviour. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_ALWAYS_ON_BIKE` | `constants/engine_flags.asm:35` | `Route17AlwaysOnBikeCallback` | cannot dismount, cannot Surf | -| `ENGINE_DOWNHILL` | `constants/engine_flags.asm:36` | `Route17AlwaysOnBikeCallback` | idle frames auto-step DOWN | -| `EVENT_BEAT_BIKER_RILEY` / `_JOEL` / `_GLENN` / `_CHARLES` | `constants/event_flags.asm` | the four trainer scripts | trainers beaten | -| `EVENT_ROUTE_17_HIDDEN_MAX_ETHER` | `constants/event_flags.asm:246` | `Route17HiddenMaxEther` | hidden item taken | -| `EVENT_ROUTE_17_HIDDEN_MAX_ELIXER` | `constants/event_flags.asm:247` | `Route17HiddenMaxElixer` | hidden item taken | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_ETHER` | hidden, face (12, 5) | `bg_event 12, 5, BGEVENT_ITEM` | `EVENT_ROUTE_17_HIDDEN_MAX_ETHER` | -| `MAX_ELIXER` | hidden, face (8, 77) | `bg_event 8, 77, BGEVENT_ITEM` | `EVENT_ROUTE_17_HIDDEN_MAX_ELIXER` | - -Neither is mentioned by the walkthrough. - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `RILEY` | `BIKER` | Biker (7) | L34 Weezing | `TrainerBikerRiley` | none | -| `GLENN` | `BIKER` | Biker (9) | L28 Koffing, L30 Magmar, L32 Weezing | `TrainerBikerGlenn` | none | -| `JOEL` | `BIKER` | Biker (8) | L32 Magmar, L32 Magmar | `TrainerBikerJoel` | none | -| `CHARLES` | `BIKER` | Biker (6) | L30 Koffing, L30 Charmeleon, L30 Weezing | `TrainerBikerCharles` | none | - -`BIKER` base reward 8 (`attributes.asm:271`). - -**Wild encounters** - -`data/wild/kanto_grass.asm:949` `def_grass_wildmons ROUTE_17`, 10%/10%/10% -(this route's three time bands genuinely differ): - -- morn: L28 Fearow, L27 Grimer, L29 Grimer, L30 Fearow, L29 Slugma, L32 Muk, L32 Muk -- day: L28 Fearow, L27 Slugma, L29 Grimer, L30 Fearow, L25 Slugma, L32 Muk, L32 Muk -- nite: L28 Grimer, L27 Grimer, L29 Grimer, L30 Muk, L29 Slugma, L32 Muk, L32 Muk - -Fishing group is `FISHGROUP_NONE`. - ---- - -### MAP_ROUTE_17_ROUTE_18_GATE - -- Script: `maps/Route17Route18Gate.asm` -- Header: `data/maps/maps.asm:437` -> `map Route17Route18Gate, TILESET_GATE, GATE, LANDMARK_ROUTE_17, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:405` -> `map_const ROUTE_17_ROUTE_18_GATE, 5, 4` -- Scene variable: `data/maps/scenes.asm:19` -> `scene_var ROUTE_17_ROUTE_18_GATE, wRoute17Route18GateSceneID` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 4 | `ROUTE_17` | 1 | -| 2 | 0 | 5 | `ROUTE_17` | 2 | -| 3 | 9 | 4 | `ROUTE_18` | 1 | -| 4 | 9 | 5 | `ROUTE_18` | 2 | - -**Coord events** (`def_coord_events`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ROUTE17ROUTE18GATE_BICYCLE_CHECK` (= 0) | 5 | 4 | `Route17Route18GateBicycleCheck` | `checkitem BICYCLE`; on failure, shove one step RIGHT | -| `SCENE_ROUTE17ROUTE18GATE_BICYCLE_CHECK` (= 0) | 5 | 5 | `Route17Route18GateBicycleCheck` | same | - -**BG events** - -None. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE17ROUTE18GATE_OFFICER` (2) | `SPRITE_OFFICER` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route17Route18GateOfficerScript` | -1 | - -`Route17Route18GateBicycleCheck` (`5e:69ef`) is byte-for-byte the same shape as -the Route 16 one, with `Route17Route18GateCannotPassText`. - -Important asymmetry for a bot: the shove movement is `step RIGHT`, so the check -only actually blocks the **west-to-east** direction (walking off Route 17 into -the gate at x=5). Coming from Route 18 you enter at x=9 and the coord event at -x=5 still fires, but the shove pushes you further right, back the way you came. - ---- - -### MAP_ROUTE_18 - -- Script: `maps/Route18.asm` -- Blocks: `maps/Route18.blk` -- Header: `data/maps/maps.asm:364` -> `map Route18, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_18, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:336` -> `map_const ROUTE_18, 10, 9` (20x18 cells) -- Attributes/connections: `data/maps/attributes.asm:296-298` -> `$43`; west `Route17` (-38), east `FuchsiaCity` (-7) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 6 | `ROUTE_17_ROUTE_18_GATE` | 3 | -| 2 | 2 | 7 | `ROUTE_17_ROUTE_18_GATE` | 4 | - -**Coord events** - -None. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 5 | `BGEVENT_READ` | `Route18Sign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE18_YOUNGSTER1` (2) | `SPRITE_YOUNGSTER` | 9 | 12 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperBoris` | -1 | -| `ROUTE18_YOUNGSTER2` (3) | `SPRITE_YOUNGSTER` | 13 | 6 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperBob` | -1 | - -**Trainers** - -| const | class | id | party | script label | rematch/phone | -|---|---|---|---|---|---| -| `BOB` | `BIRD_KEEPER` | BirdKeeper (11) | L34 Noctowl | `TrainerBirdKeeperBob` | none | -| `BORIS` | `BIRD_KEEPER` | BirdKeeper (10) | L30 Doduo, L28 Doduo, L32 Dodrio | `TrainerBirdKeeperBoris` | none | - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BIRD_KEEPER_BOB` | `constants/event_flags.asm` | `TrainerBirdKeeperBob` | trainer beaten | -| `EVENT_BEAT_BIRD_KEEPER_BORIS` | `constants/event_flags.asm` | `TrainerBirdKeeperBoris` | trainer beaten | - -**Items** - -None. - -**Wild encounters** - -`data/wild/kanto_grass.asm:977` `def_grass_wildmons ROUTE_18`, 10%/10%/10%: - -- morn/day (identical): L26 Grimer, L27 Fearow, L28 Grimer, L29 Fearow, L27 Slugma, L30 Muk, L30 Muk -- nite: L26 Grimer, L27 Grimer, L28 Grimer, L28 Muk, L27 Slugma, L30 Muk, L30 Muk - ---- - -### MAP_FUCHSIA_GYM - -- Script: `maps/FuchsiaGym.asm` -- Blocks: `maps/FuchsiaGym.blk` -- Header: `data/maps/maps.asm:368` -> `map FuchsiaGym, TILESET_LAB, INDOOR, LANDMARK_FUCHSIA_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE` (the `TRUE` is the phone-service flag: phone calls are suppressed here) -- Dimensions: `constants/map_constants.asm:340` -> `map_const FUCHSIA_GYM, 5, 9` (10x18 cells) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `FUCHSIA_CITY` | 3 | -| 2 | 5 | 17 | `FUCHSIA_CITY` | 3 | - -**Coord events** - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 15 | `BGEVENT_READ` | `FuchsiaGymStatue` | -| 6 | 15 | `BGEVENT_READ` | `FuchsiaGymStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `FUCHSIAGYM_JANINE` (2) | `SPRITE_JANINE` | 1 | 10 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `FuchsiaGymJanineScript` | -1 | -| `FUCHSIAGYM_FUCHSIA_GYM_1` (3) | `SPRITE_FUCHSIA_GYM_1` | 5 | 7 | `SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | `LassAliceScript` | -1 | -| `FUCHSIAGYM_FUCHSIA_GYM_2` (4) | `SPRITE_FUCHSIA_GYM_2` | 5 | 11 | `SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | `LassLindaScript` | -1 | -| `FUCHSIAGYM_FUCHSIA_GYM_3` (5) | `SPRITE_FUCHSIA_GYM_3` | 9 | 4 | `SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | `PicnickerCindyScript` | -1 | -| `FUCHSIAGYM_FUCHSIA_GYM_4` (6) | `SPRITE_FUCHSIA_GYM_4` | 4 | 2 | `SPINRANDOM_FAST` | `OBJECTTYPE_SCRIPT` | `CamperBarryScript` | -1 | -| `FUCHSIAGYM_GYM_GUIDE` (7) | `SPRITE_GYM_GUIDE` | 7 | 15 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `FuchsiaGymGuideScript` | -1 | - -Every gym trainer here is `OBJECTTYPE_SCRIPT`, **not** `OBJECTTYPE_TRAINER` - -there are no sight lines, so a bot must walk up and press A. The walkthrough's -route order matches: Linda (5, 11) -> Cindy (9, 4) -> Barry (4, 2) -> -Alice (5, 7) -> Janine (1, 10). - -**Scripts of interest** - -`LassAliceScript` / `LassLindaScript` / `PicnickerCindyScript` / -`CamperBarryScript` share one shape: - -``` -checkevent EVENT_BEAT_<NAME> -iftrue .Unmasked -applymovement <OBJ>, Movement_NinjaSpin ; 13 turn_head steps -faceplayer -variablesprite SPRITE_FUCHSIA_GYM_n, SPRITE_LASS|SPRITE_YOUNGSTER -special LoadUsedSpritesGFX -.Unmasked: -faceplayer / opentext -checkevent EVENT_BEAT_<NAME> -iftrue .AfterScript -writetext <before> / waitbutton / closetext -winlosstext <beaten>, 0 -loadtrainer <CLASS>, <NAME> -startbattle -iftrue .BecomesJanine ; wScriptVar != WIN -reloadmapafterbattle -setevent EVENT_BEAT_<NAME> -end -.BecomesJanine: -variablesprite SPRITE_FUCHSIA_GYM_n, SPRITE_JANINE -reloadmapafterbattle -end -``` - -`Script_startbattle` (`engine/overworld/scripting.asm:1065`) copies -`wBattleResult & ~BATTLERESULT_BITMASK` into `wScriptVar`, so `iftrue` after -`startbattle` means "did not win". Losing to an impostor leaves its beat-event -clear and repaints it as `SPRITE_JANINE`. - -`FuchsiaGymJanineScript` (`5c:40d3`): - -``` -checkflag ENGINE_SOULBADGE -iftrue .FightDone -applymovement FUCHSIAGYM_JANINE, Movement_NinjaSpin -faceplayer / opentext -writetext JanineText_DisappointYou / waitbutton / closetext -winlosstext JanineText_ToughOne, 0 -loadtrainer JANINE, JANINE1 -startbattle -reloadmapafterbattle -setevent EVENT_BEAT_JANINE -setevent EVENT_BEAT_LASS_ALICE -setevent EVENT_BEAT_LASS_LINDA -setevent EVENT_BEAT_PICNICKER_CINDY -setevent EVENT_BEAT_CAMPER_BARRY -variablesprite SPRITE_FUCHSIA_GYM_1..3 -> SPRITE_LASS, _4 -> SPRITE_YOUNGSTER -special LoadUsedSpritesGFX -opentext / writetext Text_ReceivedSoulBadge / playsound SFX_GET_BADGE / waitsfx -setflag ENGINE_SOULBADGE -sjump .AfterBattle -.FightDone: faceplayer / opentext -.AfterBattle: -checkevent EVENT_GOT_TM06_TOXIC -iftrue .AfterTM -writetext JanineText_ToxicSpeech / promptbutton -verbosegiveitem TM_TOXIC -iffalse .AfterTM ; bag full -> no event set -setevent EVENT_GOT_TM06_TOXIC -.AfterTM: writetext JanineText_ApplyMyself / waitbutton / closetext / end -``` - -Three bot-critical facts: - -1. There is **no** `iftrue` after Janine's `startbattle`. The badge, the four - impostor beat-events and `ENGINE_SOULBADGE` are set unconditionally on the - line after `reloadmapafterbattle`. Losing to Janine routes through - `Script_reloadmapafterbattle` -> `Script_BattleWhiteout` - (`engine/overworld/scripting.asm:1080-1090`) and the script never reaches - those lines, so the flags are only ever set on a win. -2. Beating Janine retroactively sets `EVENT_BEAT_LASS_ALICE`, - `EVENT_BEAT_LASS_LINDA`, `EVENT_BEAT_PICNICKER_CINDY` and - `EVENT_BEAT_CAMPER_BARRY`, so the four impostors can be skipped entirely. -3. `TM_TOXIC` is handed out on a **separate** `checkevent` from the badge. If - the TM pocket is full the badge is still awarded and the TM is retried the - next time you talk to her. - -`FuchsiaGymStatue` - `checkflag ENGINE_SOULBADGE / iftrue .Beaten / -jumpstd GymStatue1Script`, `.Beaten` does `gettrainername STRING_BUFFER_4, -JANINE, JANINE1 / jumpstd GymStatue2Script`. - -`FuchsiaGymGuideScript` - `checkevent EVENT_BEAT_JANINE` picks between the two -guide texts. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_SOULBADGE` | `constants/engine_flags.asm:51`; storage `data/events/engine_flags.asm:59` (`engine_flag wKantoBadges, SOULBADGE`) | `FuchsiaGymJanineScript`, `FuchsiaGymStatue` | Kanto badge 5; also the "already beat this gym" gate | -| `EVENT_BEAT_JANINE` | `constants/event_flags.asm:719` | `FuchsiaGymJanineScript`, `FuchsiaGymGuideScript` | leader beaten | -| `EVENT_BEAT_LASS_ALICE` | `constants/event_flags.asm:804` | `LassAliceScript`, `FuchsiaGymJanineScript` | impostor beaten (or skipped via Janine) | -| `EVENT_BEAT_LASS_LINDA` | `constants/event_flags.asm:807` | `LassLindaScript`, `FuchsiaGymJanineScript` | impostor beaten | -| `EVENT_BEAT_PICNICKER_CINDY` | `constants/event_flags.asm:640` | `PicnickerCindyScript`, `FuchsiaGymJanineScript` | impostor beaten | -| `EVENT_BEAT_CAMPER_BARRY` | `constants/event_flags.asm:532` | `CamperBarryScript`, `FuchsiaGymJanineScript` | impostor beaten | -| `EVENT_GOT_TM06_TOXIC` | `constants/event_flags.asm:220` | `FuchsiaGymJanineScript` | TM06 already collected | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_TOXIC` (TM06, item `$c5`) | `verbosegiveitem` after beating Janine | `FuchsiaGymJanineScript` | `EVENT_GOT_TM06_TOXIC` | -| SOULBADGE | `setflag ENGINE_SOULBADGE` | `FuchsiaGymJanineScript` | `ENGINE_SOULBADGE` itself | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `LINDA` | `LASS` | Lass (6) | L30 Bulbasaur, L32 Ivysaur, L34 Venusaur | `LassLindaScript` | none | -| `CINDY` | `PICNICKER` | Picnicker (5) | L36 Nidoqueen | `PicnickerCindyScript` | none | -| `BARRY` | `CAMPER` | Camper (5) | L36 Nidoking | `CamperBarryScript` | none | -| `ALICE` | `LASS` | Lass (3) | L30 Gloom, L34 Arbok, L30 Gloom | `LassAliceScript` | none | -| `JANINE1` | `JANINE` | Janine (1), `TRAINERTYPE_MOVES` | see below | `FuchsiaGymJanineScript` | none (`KantoGymLeaders` entry, `data/trainers/leaders.asm`) | - -Janine's party in **ROM order** (`data/trainers/parties.asm`, Janine (1)): - -| # | level | species | moves | -|---|---|---|---| -| 1 | 36 | Crobat | Screech, Supersonic, Confuse Ray, Wing Attack | -| 2 | 36 | Weezing | Smog, Sludge Bomb, Toxic, Explosion | -| 3 | 36 | Weezing | Smog, Sludge Bomb, Toxic, Explosion | -| 4 | 33 | Ariados | Scary Face, Giga Drain, String Shot, Night Shade | -| 5 | 39 | Venomoth | Foresight, Double Team, Gust, Psychic | - -Base reward 25 (`data/trainers/attributes.asm:157`), last mon level 39 -> -4 x 25 x 39 = 3900G, matching the walkthrough. - -**Wild encounters** - -None (indoor). - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Cut trees on Routes 12 (cells (7,44), (7,49)), 13 ((44,4)), 14 ((5,8), (11,14), (3,24)), 16 ((15,4)), Fuchsia ((16,11), (18,19)) | `engine/events/overworld.asm:117` `CutFunction.CheckAble` (menu use, `03:47e1`) and `engine/events/overworld.asm:1741` `TryCutOW` (walk-into prompt, `03:5193`) | `CheckBadge ENGINE_HIVEBADGE` **and** a party member knowing `CUT` **and** `CheckMapForSomethingToCut` (facing collision `COLL_CUT_TREE` + block id in `CutTreeBlockPointers`) | Hive Badge, already held long before this section. Cut is not persisted: the block reverts on map reload. | -| Route 16 Gate, west-to-east | `maps/Route16Gate.asm:16` `Route16GateBicycleCheck` (`5e:67cb`), coord events at (5,4)/(5,5), scene 0 | `checkitem BICYCLE` (bag, not "riding") | Own the BICYCLE. There is no `setmapscene` for this map, so the check is permanent, not a one-shot cutscene. | -| Route 17/18 Gate | `maps/Route17Route18Gate.asm:16` `Route17Route18GateBicycleCheck` (`5e:69ef`), coord events at (5,4)/(5,5), scene 0 | `checkitem BICYCLE` | Own the BICYCLE | -| Route 17 forced bike / auto-scroll south | `maps/Route17.asm:13` `Route17AlwaysOnBikeCallback` sets `ENGINE_ALWAYS_ON_BIKE` + `ENGINE_DOWNHILL`; enforced by `engine/overworld/map_setup.asm:112` `.CheckForcedBiking` (forces `PLAYER_BIKE`), `engine/events/overworld.asm:1634` `.GetOffBike` (refuses dismount), `engine/events/overworld.asm:343` / `:498` (refuses Surf), `engine/overworld/player_movement.asm:13` `.GetDPad` (idle frame -> `PAD_DOWN`) | none - it is unconditional on this map | Leave the map | -| Route 16 partial forced bike | `maps/Route16.asm:7` `Route16AlwaysOnBikeCallback` | `VAR_YCOORD < 5` or `VAR_XCOORD > 13` clears the flag; anywhere else sets it | Only re-evaluated on `MAPCALLBACK_NEWMAP` | -| Snorlax at the Route 11 west connection | `maps/VermilionCity.asm:41` `VermilionSnorlax`, object at Vermilion City (34, 8), `EVENT_VERMILION_CITY_SNORLAX` | `special SnorlaxAwake` must return true (Pokegear radio Expn card tuned to the Poke Flute channel) before the L50 Snorlax battle triggers; otherwise only `VermilionCitySnorlaxSleepingText` | `EVENT_FOUGHT_SNORLAX` + `disappear VERMILIONCITY_BIG_SNORLAX`. Belongs to a later section; nothing in section 23 clears it. | -| Safari Zone | none - `maps/FuchsiaCity.asm` warp 7 at (18, 3) is commented `; inaccessible` and no script or flag ever opens it | n/a | never opens in Gold/Silver | - -Nothing in this section gates on `ENGINE_SOULBADGE` itself; the badge is the -output, not a prerequisite. - ---- - -## 4. Bot checklist - -Coordinates are walk cells, the same units the asm tables use. "Object N" means -the `object_const_def` index in that map (starting at 2). - -1. `MAP_LAVENDER_TOWN` -> Fly / walk south into `MAP_ROUTE_12` (north connection, - offset 0). Pre: `ENGINE_FLYPOINT_LAVENDER`. Post: none. -2. `MAP_ROUTE_12`, walk to (6, 6) area, talk to object 5 (`TrainerFisherKyle`). - Pre: `EVENT_BEAT_FISHER_KYLE` clear. Post: `EVENT_BEAT_FISHER_KYLE`. -3. `MAP_ROUTE_12`, object 2 at (5, 15) (`TrainerFisherMartin`), sight range 3 - facing DOWN - a bot walking south past x=5 will be challenged automatically. - Post: `EVENT_BEAT_FISHER_MARTIN`. -4. Walk west off `MAP_ROUTE_12` -> `MAP_ROUTE_11` (offset 9). -5. `MAP_ROUTE_11`, object 4 at (29, 7) (`TrainerPsychicHerman`), sight 1. - Post: `EVENT_BEAT_PSYCHIC_HERMAN`. -6. `MAP_ROUTE_11`, object 2 at (22, 14) (`TrainerYoungsterOwen`), sight 3, - `SPINRANDOM_FAST`. Post: `EVENT_BEAT_YOUNGSTER_OWEN`. -7. `MAP_ROUTE_11`, object 3 at (15, 9) (`TrainerYoungsterJason`), sight 2. - Post: `EVENT_BEAT_YOUNGSTER_JASON`. -8. `MAP_ROUTE_11`, talk to object 6 at (32, 2) -> `BERRY`. Pre: bag room. - Post: fruit tree slot 24 marked used for the day. -9. `MAP_ROUTE_11`, stand at (32, 6) facing UP (or use ITEMFINDER) and press A on - (32, 5) -> `REVIVE`. Post: `EVENT_ROUTE_11_HIDDEN_REVIVE`. -10. `MAP_ROUTE_11`, object 5 at (7, 4) (`TrainerPsychicFidel`), sight 3 facing - LEFT. Post: `EVENT_BEAT_PSYCHIC_FIDEL`. -11. Do **not** continue west - the Vermilion Snorlax blocks it. Walk back east - into `MAP_ROUTE_12`. -12. `MAP_ROUTE_12`, object 3 at (15, 28) (`TrainerFisherStephen`), sight 1 - facing UP. Post: `EVENT_BEAT_FISHER_STEPHEN`. -13. `MAP_ROUTE_12`, step on warp 1 at (11, 33) -> `MAP_ROUTE_12_SUPER_ROD_HOUSE`. -14. `MAP_ROUTE_12_SUPER_ROD_HOUSE`, talk to object 2 at (5, 3), answer **Yes** - to the `yesorno`. Pre: `EVENT_GOT_SUPER_ROD` clear, KEY ITEMS room. - Post: `EVENT_GOT_SUPER_ROD`, `SUPER_ROD` in bag. -15. Exit via warp 1/2 at (2, 7)/(3, 7) -> `MAP_ROUTE_12` warp 1. -16. `MAP_ROUTE_12`, object 4 at (13, 39) (`TrainerFisherBarney`), sight 3 facing - LEFT. Post: `EVENT_BEAT_FISHER_BARNEY`. -17. `MAP_ROUTE_12`, face walk cell (7, 44) and use CUT. Pre: `ENGINE_HIVEBADGE` - + party CUT. Post: block `$35` at block (3, 22) swapped for `$4c` (not - persisted). -18. `MAP_ROUTE_12`, walk onto object 6 at (5, 43) -> `CALCIUM`. - Post: `EVENT_ROUTE_12_CALCIUM`. -19. Optional: object 7 at (5, 51) -> `NUGGET` (`EVENT_ROUTE_12_NUGGET`); - hidden `ELIXER` facing (14, 13) (`EVENT_ROUTE_12_HIDDEN_ELIXER`). -20. Walk south off `MAP_ROUTE_12` -> `MAP_ROUTE_13` (offset -20). -21. `MAP_ROUTE_13`, object 2 at (42, 6) (`TrainerBirdKeeperPerry`), sight 2. - Post: `EVENT_BEAT_BIRD_KEEPER_PERRY`. -22. `MAP_ROUTE_13`, object 3 at (43, 6) (`TrainerBirdKeeperBret`), sight 2. - Post: `EVENT_BEAT_BIRD_KEEPER_BRET`. -23. `MAP_ROUTE_13`, object 4 at (32, 8) (`TrainerPokefanmJoshua`), sight 3 - facing LEFT (six L23 Pikachu). Post: `EVENT_BEAT_POKEFANM_JOSHUA`. -24. Optional: hidden `CALCIUM` facing (30, 13) (`EVENT_ROUTE_13_HIDDEN_CALCIUM`). -25. `MAP_ROUTE_13`, object 6 at (25, 6) (`TrainerPokefanmAlex`), sight 4 facing - RIGHT. Post: `EVENT_BEAT_POKEFANM_ALEX`. -26. `MAP_ROUTE_13`, object 5 at (14, 10) (`TrainerHikerKenny`), sight 4 facing - LEFT. Post: `EVENT_BEAT_HIKER_KENNY`. -27. Walk south off `MAP_ROUTE_13` -> `MAP_ROUTE_14` (offset 0). -28. `MAP_ROUTE_14`, face (5, 8) and use CUT, then talk to object 4 at (5, 9) - (`TrainerPokefanmTrevor`), sight 4 facing RIGHT. - Post: `EVENT_BEAT_POKEFANM_TREVOR`. -29. `MAP_ROUTE_14`, talk to object 5 (`Kim`, patrols around (7, 5)). - Pre: a Chansey in the party, trade 5 unset in `wTradeFlags`. - Post: Chansey -> Aerodactyl "AEROY" holding `GOLD_BERRY`, OT KIM / 26491. -30. `MAP_ROUTE_14`, face (11, 14) and CUT, then object 2 at (12, 14) - (`TrainerPokefanmCarter`), sight 4 facing RIGHT. - Post: `EVENT_BEAT_POKEFANM_CARTER`. -31. `MAP_ROUTE_14`, object 3 at (11, 27) (`TrainerBirdKeeperRoy`), sight 3. - Post: `EVENT_BEAT_BIRD_KEEPER_ROY`. -32. `MAP_ROUTE_14`, face (3, 24) and CUT, then walk west off the map -> - `MAP_ROUTE_15` (offset 9). -33. `MAP_ROUTE_15`, walk onto object 8 at (12, 5) -> `PP_UP`. - Post: `EVENT_ROUTE_15_PP_UP`. -34. `MAP_ROUTE_15`, fight east-to-west along y=10: - object 4 (33, 10) Johnny, object 6 (30, 12) Colette, object 5 (27, 10) - Billy, object 7 (16, 10) Hillary, object 3 (11, 11) Tommy, - object 2 (11, 10) Kipp. Post: the six `EVENT_BEAT_*` flags. -35. `MAP_ROUTE_15`, warp 1/2 at (2, 4)/(2, 5) -> `MAP_ROUTE_15_FUCHSIA_GATE` - warps 3/4; then gate warps 1/2 at (0, 4)/(0, 5) -> `MAP_FUCHSIA_CITY` - warps 8/9 at (37, 22)/(37, 23). Post (on map load): - `ENGINE_FLYPOINT_FUCHSIA` set by `FuchsiaCityFlypointCallback`. -36. `MAP_FUCHSIA_CITY`, talk to object 5 at (8, 1) -> `BURNT_BERRY`. -37. `MAP_FUCHSIA_CITY`, warp 5 at (19, 27) -> `MAP_FUCHSIA_POKECENTER_1F`, talk - to object 2 to heal, exit via warp 1/2. -38. Optional Cycling Road detour. Fly to Celadon, walk west into `MAP_ROUTE_16`. - Pre: `BICYCLE` in bag. `ENGINE_ALWAYS_ON_BIKE` is set on load unless the - landing cell has `y < 5` or `x > 13`. -39. `MAP_ROUTE_16`, optional: face (15, 4) and CUT (regrows on any map reload). -40. `MAP_ROUTE_16`, warp 2/3 at (14, 6)/(14, 7) -> `MAP_ROUTE_16_GATE` warps 3/4. - Walking left across (5, 4)/(5, 5) fires `Route16GateBicycleCheck`; with no - BICYCLE the player is shoved one step RIGHT. Exit warps 1/2 at (0, 4)/(0, 5). -41. `MAP_ROUTE_16` -> walk south into `MAP_ROUTE_17` (offset 0). - Post (on load): `ENGINE_ALWAYS_ON_BIKE` + `ENGINE_DOWNHILL`. Releasing the - d-pad now auto-steps DOWN each frame. -42. `MAP_ROUTE_17`, object 2 at (4, 17) (`TrainerBikerRiley`), sight 4. - Post: `EVENT_BEAT_BIKER_RILEY`. -43. `MAP_ROUTE_17`, object 3 at (16, 32) (`TrainerBikerJoel`), sight 3 - the - right-bank bridge. Post: `EVENT_BEAT_BIKER_JOEL`. -44. `MAP_ROUTE_17`, object 4 at (3, 53) (`TrainerBikerGlenn`), sight 3. - Post: `EVENT_BEAT_BIKER_GLENN`. -45. `MAP_ROUTE_17`, object 5 at (6, 80) (`TrainerBikerCharles`), sight 4 facing - RIGHT. Post: `EVENT_BEAT_BIKER_CHARLES`. -46. Optional hidden items: face (12, 5) -> `MAX_ETHER`; face (8, 77) -> - `MAX_ELIXER`. -47. `MAP_ROUTE_17`, warp 1/2 at (17, 82)/(17, 83) -> - `MAP_ROUTE_17_ROUTE_18_GATE` warps 1/2, exit warps 3/4 at (9, 4)/(9, 5) -> - `MAP_ROUTE_18` warps 1/2 at (2, 6)/(2, 7). -48. `MAP_ROUTE_18`, object 3 at (13, 6) (`TrainerBirdKeeperBob`), sight 3. - Post: `EVENT_BEAT_BIRD_KEEPER_BOB`. -49. `MAP_ROUTE_18`, object 2 at (9, 12) (`TrainerBirdKeeperBoris`), sight 3. - Post: `EVENT_BEAT_BIRD_KEEPER_BORIS`. -50. Walk east off `MAP_ROUTE_18` -> `MAP_FUCHSIA_CITY` (offset -7). -51. `MAP_FUCHSIA_CITY`, heal (warp 5), then warp 3 at (8, 27) -> - `MAP_FUCHSIA_GYM` (enter at warps 1/2, (4, 17)/(5, 17)). -52. `MAP_FUCHSIA_GYM`, talk to object 4 at (5, 11) (`LassLindaScript`). - Pre: `EVENT_BEAT_LASS_LINDA` clear. Post: `EVENT_BEAT_LASS_LINDA` on a win; - on a loss the sprite becomes `SPRITE_JANINE` and the flag stays clear. -53. `MAP_FUCHSIA_GYM`, object 5 at (9, 4) (`PicnickerCindyScript`). - Post: `EVENT_BEAT_PICNICKER_CINDY`. -54. `MAP_FUCHSIA_GYM`, object 6 at (4, 2) (`CamperBarryScript`). - Post: `EVENT_BEAT_CAMPER_BARRY`. -55. `MAP_FUCHSIA_GYM`, object 3 at (5, 7) (`LassAliceScript`). - Post: `EVENT_BEAT_LASS_ALICE`. -56. Steps 52-55 are **optional**: step 57 sets all four flags anyway. -57. `MAP_FUCHSIA_GYM`, talk to object 2 at (1, 10) (`FuchsiaGymJanineScript`). - Pre: `ENGINE_SOULBADGE` clear. Post on win: `EVENT_BEAT_JANINE`, - `EVENT_BEAT_LASS_ALICE`, `EVENT_BEAT_LASS_LINDA`, - `EVENT_BEAT_PICNICKER_CINDY`, `EVENT_BEAT_CAMPER_BARRY`, - `ENGINE_SOULBADGE`, then `verbosegiveitem TM_TOXIC` -> - `EVENT_GOT_TM06_TOXIC` (only if the TM pocket has room; otherwise re-talk). -58. Exit via warps 1/2 at (4, 17)/(5, 17) -> `MAP_FUCHSIA_CITY` warp 3. - -Prize money for every trainer above is -`4 x <class base reward> x <level of the trainer's LAST party mon>` -(`ComputeTrainerReward` in `engine/battle/read_trainer_party.asm:300` gives -`base x level`, and `engine/battle/core.asm:2341` adds that amount **four** -times via the `ld c, 4` loop). This reproduces every G figure in the -walkthrough exactly. - ---- - -## 5. Port coverage - -The Gen 2 side of this repo is data-driven: `src/import/RomExtractorGen2.lua` -reads each map's `def_warp_events` / `def_coord_events` / `def_bg_events` / -`def_object_events` tables and the script bytecode straight out of the ROM -(`readMapEvents`, `src/import/RomExtractorGen2.lua:787-980`), and -`src/script/gen2/Vm.lua` executes the original opcodes. So there is no -per-map hand-port to audit for this section; what matters is whether the -mechanics each beat needs are implemented. - -| Beat | Port file | Status | -|---|---|---| -| Map geometry, warps, connections | `src/world/gen2/Map.lua`, `src/import/RomExtractorGen2.lua:787` | implemented (generic, all maps) | -| Coord-event trip-wires (both bike gates) | `src/world/gen2/World.lua:5006` `World:tryCoordScript` (matches x/y + `sceneId`) | implemented | -| `checkitem BICYCLE` | `src/script/gen2/Opcodes.lua:38` (`0x21 checkitem`), handled in `src/script/gen2/Vm.lua` | implemented | -| `showemote` / `turnobject` / `applymovement` refusal choreography | `src/script/gen2/Vm.lua:961`, `:302` | implemented | -| `ENGINE_ALWAYS_ON_BIKE` / `ENGINE_DOWNHILL` (Routes 16, 17) | `src/world/gen2/Bike.lua:30-31`, `src/world/gen2/World.lua:3437-3444`, `:5620` | implemented; `World:alwaysOnBike()` / `World:downhill()` read the same engine-flag store the map callbacks write | -| Downhill auto-step / slow uphill bike step | `src/world/gen2/Bike.lua:136-146` | implemented | -| `readvar VAR_XCOORD` / `VAR_YCOORD` (the Route 16 partial-bike callback) | `src/world/gen2/World.lua:1275-1280`, wired through `src/script/gen2/Vm.lua:670` | implemented | -| `MAPCALLBACK_NEWMAP` dispatch (Fuchsia flypoint, both bike callbacks) | `src/world/gen2/World.lua:5659` `runMapCallback("MAPCALLBACK_NEWMAP")` | implemented | -| CUT field move on `TILESET_KANTO` trees | `src/world/gen2/FieldMoves.lua:191-210` (`CUT_BLOCKS.TILESET_KANTO` transcribes all five kanto rows), badge gate at `:104` (`CUT = "HIVE"`) | implemented | -| Item balls (`OBJECTTYPE_ITEMBALL`) | `src/import/RomExtractorGen2.lua:2968` `readItemBall` | implemented | -| Hidden items (`BGEVENT_ITEM` / `hiddenitem`) | `src/world/gen2/HiddenItems.lua` | implemented | -| Fruit trees (Route 11 Berry, Fuchsia Burnt Berry) | `src/core/gen2/Apricorns.lua:378` (slot 24 `BERRY`), `:384` (slot 30 `BURNT_BERRY`); `fruittree` opcode at `src/script/gen2/Vm.lua:1191` | implemented | -| Kim's Chansey/Aerodactyl trade | `src/core/gen2/NpcTrade.lua` + `src/ui/gen2/TradeMenu.lua`; `trade` opcode at `src/script/gen2/Vm.lua:1292` | implemented (trade rows come from the extracted `data/generated/events.lua` `trades` table) | -| `verbosegiveitem` (Super Rod, TM06) | `src/script/gen2/Vm.lua:490-498` | implemented | -| Super Rod fishing tables | `src/world/gen2/World.lua:311` `ROD_INDEX`, `src/battle/gen2/Encounter.lua:110` | implemented | -| Trainer objects, sight lines, `loadtrainer` / `startbattle` | `src/world/gen2/Trainers.lua`, `src/script/gen2/Vm.lua:806`, `:817`, `src/world/gen2/World.lua:4529` | implemented | -| `variablesprite` + `special LoadUsedSpritesGFX` (the Janine impostor reveal) | `src/script/gen2/Vm.lua:353`, `src/script/gen2/Specials.lua:1033` | implemented | -| Badge award (`setflag ENGINE_SOULBADGE` into `wKantoBadges`) | `src/world/gen2/World.lua` engine-flag store (`save.engineFlags`), Kanto badges counted at `:1240-1245` | implemented (generic engine-flag path; not exercised by any Gold driver) | -| Prize money x4 loop | not located in `src/battle/gen2/Prize.lua` during this pass | unverified - see Unresolved | -| Any driver that walks this section | `tests/drivers/gold_*.lua` (24 files; none touches Routes 11-19 or Fuchsia) | missing | - -Summary: every mechanic this section needs already exists in the port, but -nothing in `tests/drivers/` exercises Kanto's south-east loop, the Cycling Road -forced-bike state or the Fuchsia Gym impostor script. A `gold_cycling_road.lua` -and a `gold_fuchsia_gym.lua` driver would be the cheapest way to prove it. - ---- - -## 6. Unresolved / verify by hand - -1. **Route 19 is never visited.** The section is titled "Routes 11-19" but the - text stops at Fuchsia City after Route 18. `MAP_ROUTE_19` and - `MAP_ROUTE_19_FUCHSIA_GATE` exist (`constants/map_constants.asm:178-180`, - `data/maps/attributes.asm:287`) and Fuchsia warps 10/11 at (7, 35)/(8, 35) - lead there, but no walkthrough beat uses them. -2. **"Cut the tree on your way west" on Route 15.** `maps/Route15.blk` contains - none of the five `TILESET_KANTO` cut-tree block ids. The nearest real cut - tree on that path is the Route 14 one at walk cell (3, 24). The Route 15 - sentence is most likely a duplicate reference to it. -3. **Janine "Strategy VERSUS Espeon".** The walkthrough includes an Espeon - strategy paragraph. Janine (1) in `data/trainers/parties.asm` has no Espeon: - the five mons are Crobat, Weezing, Weezing, Ariados, Venomoth. The paragraph - appears to be copy-paste residue from another gym. -4. **Party ordering differences.** The walkthrough lists several parties in a - different order than the ROM. Confirmed divergences: - Psychic Herman (asm: Exeggcute, Exeggcute, Exeggutor; guide: Exeggcute, - Exeggutor, Exeggcute); Schoolboy Johnny (asm: Bellsprout, Weepinbell, - Victreebel; guide: Bellsprout, Victreebel, Weepinbell); PokeFan Carter (asm: - Bulbasaur, Charmander, Squirtle; guide: Bulbasaur, Squirtle, Charmander); - Bird Keeper Boris (asm: L30 Doduo, L28 Doduo, L32 Dodrio; guide: L30 Doduo, - L32 Dodrio, L28 Doduo); Biker Charles (asm: Koffing, Charmeleon, Weezing; - guide: Koffing, Weezing, Charmeleon); Lass Linda (asm: Bulbasaur, Ivysaur, - Venusaur; guide: Bulbasaur, Venusaur, Ivysaur); Janine (asm order given - above). Levels and species sets always match; only the order differs, so - lead-mon predictions from the guide are unreliable. -5. **Route 12 items.** The walkthrough lists only Calcium and the Super Rod. - The map also has a `NUGGET` itemball at (5, 51) and a hidden `ELIXER` at - (14, 13). Route 13's hidden `CALCIUM` at (30, 13) and Route 17's hidden - `MAX_ETHER` / `MAX_ELIXER` are likewise unlisted. -6. **Route 17 fight order.** The guide's order (Riley, Glenn, Joel, Charles) - does not match the map's y-order (Riley 17, Joel 32, Glenn 53, Charles 80). - Since these are `OBJECTTYPE_TRAINER` objects with sight lines rather than - scripted encounters, a bot coasting south will hit them in y-order, not the - guide's order. -7. **Prize-money x4 in the port.** The disassembly's four-fold payout - (`engine/battle/core.asm:2341`, the `ld c, 4` add loop) was not located in - `src/battle/gen2/Prize.lua` during this pass. Worth a direct read before - trusting any money-based bot assertion. -8. **Fuchsia City cut trees.** Blocks (8, 5) and (9, 9) of - `maps/FuchsiaCity.blk` are `$60` cut trees (walk cells (16, 11) and - (18, 19)). Neither is mentioned in the walkthrough and neither was traced to - a reward; they may simply be scenery on the Safari Zone approach. -9. **Fuchsia warp 7 walkability.** `maps/FuchsiaCity.asm:135` marks the Safari - Zone gate warp `; inaccessible` in a comment; the block data behind (18, 3) - was not decoded to confirm the tile is impassable. The walkthrough's "even - the door is gone" matches the comment, but the collision itself is unverified. diff --git a/docs/gold-walkthrough/asm-walk/section-24-snorlax-and-pewter-city-gym.md b/docs/gold-walkthrough/asm-walk/section-24-snorlax-and-pewter-city-gym.md deleted file mode 100644 index 2a16802d..00000000 --- a/docs/gold-walkthrough/asm-walk/section-24-snorlax-and-pewter-city-gym.md +++ /dev/null @@ -1,919 +0,0 @@ -# Section 24 - Snorlax and Pewter City Gym - -Source: `../section-24-snorlax-and-pewter-city-gym.txt` -(the FAQ numbers this chapter "30 > Snorlax and Pewter City Gym"; the file index -is 24) - -Maps covered: `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_VERMILION_CITY`, -`MAP_VERMILION_POKECENTER_1F`, `MAP_DIGLETTS_CAVE`, `MAP_ROUTE_2`, -`MAP_ROUTE_2_NUGGET_HOUSE`, `MAP_ROUTE_2_GATE`, `MAP_PEWTER_CITY`, -`MAP_PEWTER_POKECENTER_1F`, `MAP_PEWTER_GYM` - -Badges / key milestones in this section: - -- Level 50 **SNORLAX** caught in Vermilion City, holding LEFTOVERS - (`BATTLETYPE_FORCEITEM`); clearing it opens the DIGLETT'S CAVE door -- **SILVER_WING** (Gold) / **RAINBOW_WING** (Silver) from the Pewter City gramps -- **NUGGET** from the Route 2 house -- CARBOS, DIRE_HIT, ELIXER, MAX_POTION item balls on Route 2 -- **BOULDERBADGE** from BROCK (`ENGINE_BOULDERBADGE`) - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `FUCHSIA_CITY` | - | previous section | south connection, or `ROUTE_19_FUCHSIA_GATE` warps 1/2 at (4,0)/(5,0) | **Belongs to the previous section**; only the hop south is listed | -| 1 | `ROUTE_19_FUCHSIA_GATE` | `maps/Route19FuchsiaGate.asm` | warps 1/2 at (4,0)/(5,0) from `FUCHSIA_CITY` 10/11 | warps 3/4 at (4,7)/(5,7) -> `ROUTE_19` 1 | Officer explains why Route 19 is shut | -| 2 | `ROUTE_19` | `maps/Route19.asm` | warp 1 at (7,3) from the gate | dead end - back through warp 1 | **Blocked.** `Route19ClearRocksCallback` paints six boulders while `EVENT_CINNABAR_ROCKS_CLEARED` is clear | -| 3 | `VERMILION_CITY` | `maps/VermilionCity.asm` | FLY (`ENGINE_FLYPOINT_VERMILION`, set by `VermilionCityFlypointCallback`) | warp 10 at (34,7) -> `DIGLETTS_CAVE` 1 | Wake and battle the SNORLAX at (34,8) with the POKe FLUTE radio channel | -| 4 | `VERMILION_POKECENTER_1F` | `maps/VermilionPokecenter1F.asm` | city warp 2 at (9,5) | warps 1/2 at (3,7)/(4,7) back | Heal and withdraw the caught SNORLAX | -| 5 | `DIGLETTS_CAVE` | `maps/DiglettsCave.asm` | warp 1 at (3,33) from `VERMILION_CITY` 10 | warp 3 at (15,5) -> `ROUTE_2` 5 | Ladder chain 2->5, long cave, ladder 6->4, then the door | -| 6 | `ROUTE_2` | `maps/Route2.asm` | warp 5 at (12,7) from `DIGLETTS_CAVE` 3 | north connection to `PEWTER_CITY` | Carbos, Nugget house, Elixer, three Bug Catchers, two cut trees | -| 7 | `ROUTE_2_NUGGET_HOUSE` | `maps/Route2NuggetHouse.asm` | Route 2 warp 1 at (15,15) | warps 1/2 at (2,7)/(3,7) back | `Route2NuggetHouseFisherScript` -> `verbosegiveitem NUGGET` | -| 8 | `ROUTE_2_GATE` | `maps/Route2Gate.asm` | Route 2 warps 3/4 at (16,27)/(17,27) | warps 3/4 at (4,7)/(5,7) -> `ROUTE_2` 2 at (15,31) | The "route-changing house" that drops you further south on the same route | -| 9 | `PEWTER_CITY` | `maps/PewterCity.asm` | south connection from `ROUTE_2` (`connection north, PewterCity, PEWTER_CITY, -5`) | five warps, see below | Sets `ENGINE_FLYPOINT_PEWTER`; gramps hands over the wing | -| 10 | `PEWTER_POKECENTER_1F` | `maps/PewterPokecenter1F.asm` | city warp 4 at (13,25) | warps 1/2 at (3,7)/(4,7) back | Heal; `Chris` runs `trade NPC_TRADE_CHRIS` (GLOOM -> RAPIDASH) | -| 11 | `PEWTER_GYM` | `maps/PewterGym.asm` | city warp 2 at (16,17) | warps 1/2 at (4,13)/(5,13) back | Camper Jerry, then **BROCK** for the BOULDERBADGE | - -Spillover: the FAQ's video-link header also names Tin Tower, Ho-Oh, Routes 1-4 -and Pallet Town. Nothing in the prose of this section enters those maps - the -Routes 3/4 and Pallet beats belong to the following section and are not covered -here. - ---- - -## 2. Maps - -### MAP_ROUTE_19 - -- Script: `maps/Route19.asm` -- Blocks: `maps/Route19.blk` -- Header: `data/maps/maps.asm:196` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_19, MUSIC_ROUTE_3, FALSE (phone allowed), PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:180` `map_const ROUTE_19, 10, 18` (group `CINNABAR`, id 5) = 20 x 36 cells -- Attributes / border: `data/maps/attributes.asm:287` `map_attributes Route19, ROUTE_19, $43` -- Connections: `connection north, FuchsiaCity, FUCHSIA_CITY, 0`; `connection west, Route20, ROUTE_20, 9` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 3 | `ROUTE_19_FUCHSIA_GATE` | 3 | - -**Coord events** (`def_coord_events`) - -none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 13 | `BGEVENT_READ` | `Route19Sign` | -| 11 | 1 | `BGEVENT_READ` | `CarefulSwimmingSign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE19_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 9 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerSwimmerfDawn` | -1 | -| `ROUTE19_SWIMMER_GUY1` | `SPRITE_SWIMMER_GUY` | 13 | 28 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermHarold` | -1 | -| `ROUTE19_SWIMMER_GUY2` | `SPRITE_SWIMMER_GUY` | 11 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermJerome` | -1 | -| `ROUTE19_SWIMMER_GUY3` | `SPRITE_SWIMMER_GUY` | 8 | 23 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerSwimmermTucker` | -1 | -| `ROUTE19_FISHER1` | `SPRITE_FISHER` | 9 | 5 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight 1) | `Route19Fisher1Script` | -1 | -| `ROUTE19_FISHER2` | `SPRITE_FISHER` | 11 | 5 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` (sight 1) | `Route19Fisher2Script` | -1 | - -**Scripts of interest** - -- `Route19ClearRocksCallback` (`callback MAPCALLBACK_TILES`). `checkevent - EVENT_CINNABAR_ROCKS_CLEARED` / `iftrue .Done`; otherwise it runs six - `changeblock` writes that lay a rock block (`$7a`) over block coordinates - `(6,6) (8,6) (10,6) (12,8) (4,8) (10,10)`. This is the wall the FAQ hits. - Block coordinates, so the blocked cells are `x = 2*bx .. 2*bx+1`, - `y = 2*by .. 2*by+1`. -- `Route19Fisher1Script` / `Route19Fisher2Script` both branch on - `EVENT_CINNABAR_ROCKS_CLEARED` for flavour text only. Neither one clears the - rocks - see "Blockers and gates". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | read by `Route19ClearRocksCallback`, `Route19Fisher1Script`, `Route19Fisher2Script`, `Route19FuchsiaGateOfficerScript`; **set** by `Route20ClearRocksCallback` (`maps/Route20.asm:13`, `MAPCALLBACK_NEWMAP`) | Route 19 stays blocked until the player has stood on Route 20, which is reached from the Cinnabar side (Pallet -> Route 21 -> Cinnabar -> Route 20) | - -**Items** - none on this map. - -**Trainers** - four Swimmers, all reachable only by Surf and all beyond the -boulders in the FAQ's play order; not part of this section's critical path. - ---- - -### MAP_ROUTE_19_FUCHSIA_GATE - -- Script: `maps/Route19FuchsiaGate.asm` -- Header: `data/maps/maps.asm:194` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_19, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:178` `map_const ROUTE_19_FUCHSIA_GATE, 5, 4` (group `CINNABAR`, id 3) -- Attributes: `data/maps/attributes.asm:512` `map_attributes Route19FuchsiaGate, ROUTE_19_FUCHSIA_GATE, $00`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `FUCHSIA_CITY` | 10 | -| 2 | 5 | 0 | `FUCHSIA_CITY` | 11 | -| 3 | 4 | 7 | `ROUTE_19` | 1 | -| 4 | 5 | 7 | `ROUTE_19` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE19FUCHSIAGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route19FuchsiaGateOfficerScript` | -1 | - -**Scripts of interest** - -- `Route19FuchsiaGateOfficerScript`: `checkevent EVENT_CINNABAR_ROCKS_CLEARED`, - two text branches. Pure flavour; the officer does not gate the warp. - ---- - -### MAP_VERMILION_CITY - -- Script: `maps/VermilionCity.asm` -- Blocks: `maps/VermilionCity.blk` -- Header: `data/maps/maps.asm:296` -> `TILESET_KANTO, TOWN, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:273` `map_const VERMILION_CITY, 20, 18` (group `VERMILION`, id 3) = 40 x 36 cells -- Attributes: `data/maps/attributes.asm:342` `map_attributes VermilionCity, VERMILION_CITY, $43` -- Connections: `connection north, Route6, ROUTE_6, 5`; `connection east, Route11, ROUTE_11, 0` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `VERMILION_FISHING_SPEECH_HOUSE` | 1 | -| 2 | 9 | 5 | `VERMILION_POKECENTER_1F` | 1 | -| 3 | 7 | 13 | `POKEMON_FAN_CLUB` | 1 | -| 4 | 13 | 13 | `VERMILION_MAGNET_TRAIN_SPEECH_HOUSE` | 1 | -| 5 | 21 | 13 | `VERMILION_MART` | 2 | -| 6 | 21 | 17 | `VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE` | 1 | -| 7 | 10 | 19 | `VERMILION_GYM` | 1 | -| 8 | 19 | 31 | `VERMILION_PORT_PASSAGE` | 1 | -| 9 | 20 | 31 | `VERMILION_PORT_PASSAGE` | 2 | -| 10 | 34 | 7 | `DIGLETTS_CAVE` | 1 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 25 | 3 | `BGEVENT_READ` | `VermilionCitySign` | -| 5 | 19 | `BGEVENT_READ` | `VermilionGymSign` | -| 5 | 13 | `BGEVENT_READ` | `PokemonFanClubSign` | -| 33 | 9 | `BGEVENT_READ` | `VermilionCityDiglettsCaveSign` | -| 27 | 15 | `BGEVENT_READ` | `VermilionCityPortSign` | -| 10 | 5 | `BGEVENT_READ` | `VermilionCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 22 | 13 | `BGEVENT_READ` | `VermilionCityMartSign` (`jumpstd MartSignScript`) | -| 12 | 19 | `BGEVENT_ITEM` | `VermilionCityHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VERMILIONCITY_TEACHER` | `SPRITE_TEACHER` | 18 | 9 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCityTeacherScript` | -1 | -| `VERMILIONCITY_GRAMPS` | `SPRITE_GRAMPS` | 23 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VermilionMachopOwner` | -1 | -| `VERMILIONCITY_MACHOP` | `SPRITE_MACHOP` | 26 | 7 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `VermilionMachop` | -1 | -| `VERMILIONCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 14 | 16 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCitySuperNerdScript` | -1 | -| `VERMILIONCITY_BIG_SNORLAX` | `SPRITE_BIG_SNORLAX` | 34 | 8 | `SPRITEMOVEDATA_BIGDOLLSYM` | `OBJECTTYPE_SCRIPT` | `VermilionSnorlax` | `EVENT_VERMILION_CITY_SNORLAX` | -| `VERMILIONCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 31 | 12 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VermilionGymBadgeGuy` | -1 | - -Note the object-flag polarity (`CheckObjectFlag`, -`engine/overworld/map_objects_2.asm:32`): an object with an `EVENT_*` flag is -**masked when the flag is set**. So the SNORLAX is present only while -`EVENT_VERMILION_CITY_SNORLAX` is clear, and `disappear` is what sets it. - -**Scripts of interest** - -- `VermilionSnorlax` (`4f:5291`). `opentext` -> `special SnorlaxAwake` -> - `iftrue .Awake`. Asleep branch writes `VermilionCitySnorlaxSleepingText` and - ends. `.Awake` writes `VermilionCityRadioNearSnorlaxText`, `pause 15`, - `cry SNORLAX`, then - `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM` / - `loadwildmon SNORLAX, 50` / `startbattle` / - `disappear VERMILIONCITY_BIG_SNORLAX` / `setevent EVENT_FOUGHT_SNORLAX` / - `reloadmapafterbattle`. - The `disappear` and `setevent` run **unconditionally after the battle**, so a - fled-from or fainted Snorlax is still gone: catching it is a one-shot. -- `SnorlaxAwake` (`engine/events/specials.asm:358`, symbol `03:45d6`). Returns - `TRUE` only when *both* hold: - 1. `[wMapMusic] == MUSIC_POKE_FLUTE_CHANNEL`; - 2. the player stands on one of `.ProximityCoords` - (`engine/events/specials.asm:399`): `(33,8) (34,10) (35,10) (36,8) (36,9)`. - The POKe FLUTE channel is the Pokegear radio at knob 78 / "20.0" - (`engine/pokegear/pokegear.asm:1447`, `.PokeFluteRadio` at 1487): it needs - **not-Johto** and `POKEGEAR_EXPN_CARD_F` in `wPokegearFlags`. Tuning the radio - and closing the Pokegear leaves that song as `wMapMusic`. -- `VermilionGymBadgeGuy`: `checkevent EVENT_GOT_HP_UP_FROM_VERMILION_GUY`, then - `readvar VAR_BADGES` with `ifequal NUM_BADGES` / `ifgreater 13` / - `ifgreater 9`. `verbosegiveitem HP_UP` only at all 16 badges. Not this - section's business, but a bot walking Vermilion will trip the text. -- `VermilionCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): - `setflag ENGINE_FLYPOINT_VERMILION`. This is what makes the FAQ's "fly back to - Vermilion City" legal - it must already have been visited on foot. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_VERMILION_CITY_SNORLAX` | `constants/event_flags.asm:1298` | object flag on `VERMILIONCITY_BIG_SNORLAX`; set by `disappear` in `VermilionSnorlax` | clear = Snorlax on the map; set = gone, cave door walkable | -| `EVENT_FOUGHT_SNORLAX` | `constants/event_flags.asm:1266` | set by `VermilionSnorlax`; read by `VermilionPokecenter1FFishingGuruScript` (`maps/VermilionPokecenter1F.asm:18`) and used as the object flag of the right Black Belt in `maps/VictoryRoadGate.asm:119` | "the Snorlax encounter has happened" | -| `ENGINE_FLYPOINT_VERMILION` | `constants/engine_flags.asm:72` | `VermilionCityFlypointCallback` | Fly destination unlocked | -| `POKEGEAR_EXPN_CARD_F` | `constants/ram_constants.asm:280` | read by `.PokeFluteRadio` | without the EXPN CARD the flute channel is static and `SnorlaxAwake` can never be true | -| `EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:252` | `VermilionCityHiddenFullHeal` | hidden FULL_HEAL at (12,19) | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `LEFTOVERS` | attached to the wild SNORLAX | `data/pokemon/base_stats/snorlax.asm:9` `db LEFTOVERS, LEFTOVERS` forced by `BATTLETYPE_FORCEITEM` (`engine/battle/core.asm:5771`) | - | -| `FULL_HEAL` | hidden | `bg_event 12, 19, BGEVENT_ITEM, VermilionCityHiddenFullHeal` | `EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | - -**Wild encounters** - the scripted SNORLAX is not a table entry; it is -`loadwildmon SNORLAX, 50`. Level 50 learnset from -`data/pokemon/evos_attacks.asm:1932`: the four most recent are SNORE (36), -REST (36), BODY_SLAM (43), ROLLOUT (50) - which is exactly the FAQ's "Rest" and -"Rollout". Catch rate 25, base exp 154 (`data/pokemon/base_stats/snorlax.asm`). - ---- - -### MAP_VERMILION_POKECENTER_1F - -- Script: `maps/VermilionPokecenter1F.asm` -- Header: `data/maps/maps.asm` (`VERMILION` group); dimensions - `constants/map_constants.asm:275` `map_const VERMILION_POKECENTER_1F, 5, 4` - -Only the one script matters here: - -- `VermilionPokecenter1FFishingGuruScript`: `checkevent EVENT_FOUGHT_SNORLAX`, - two text branches ("A sleeping #MON is lying in front of DIGLETT'S CAVE" -> - after-text). It is a read-only confirmation that the Snorlax beat is done; the - PC withdraw the FAQ asks for is the standard `POKECENTER_2F` / PC menu, not a - map script. - ---- - -### MAP_DIGLETTS_CAVE - -- Script: `maps/DiglettsCave.asm` -- Blocks: `maps/DiglettsCave.blk` (180 bytes = 10 x 18 blocks) -- Header: `data/maps/maps.asm:153` -> `TILESET_CAVE, CAVE, LANDMARK_DIGLETTS_CAVE, MUSIC_MT_MOON, TRUE (no phone), PALETTE_NITE, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:140` `map_const DIGLETTS_CAVE, 10, 18` (group `DUNGEONS`, id 75) = 20 x 36 cells -- Attributes: `data/maps/attributes.asm:487` `map_attributes DiglettsCave, DIGLETTS_CAVE, $09`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 33 | `VERMILION_CITY` | 10 | -| 2 | 5 | 31 | `DIGLETTS_CAVE` | 5 | -| 3 | 15 | 5 | `ROUTE_2` | 5 | -| 4 | 17 | 3 | `DIGLETTS_CAVE` | 6 | -| 5 | 17 | 33 | `DIGLETTS_CAVE` | 2 | -| 6 | 3 | 3 | `DIGLETTS_CAVE` | 4 | - -Warps 2<->5 and 4<->6 are the two internal ladders. Reading `DiglettsCave.blk` -against `data/tilesets/kanto_collision.asm` (block `$1f`/`$1b` = ladder, `$24` = -door) the map is three disconnected pockets: - -- pocket A (bottom-left, blocks (1,15)-(2,17)): warp 1 (VERMILION door) + warp 2 - (ladder); -- pocket B (the long cave): warp 5 at (17,33) up to warp 6 at (3,3); -- pocket C (top-right, blocks (7,1)-(8,3)): warp 4 (ladder) + warp 3 (ROUTE_2 - door). - -So the traversal is: `(3,33)` -> ladder `(5,31)` -> emerge `(17,33)` -> walk the -long cave to `(3,3)` -> ladder -> emerge `(17,3)` -> door `(15,5)` -> Route 2 -`(12,7)`. That is exactly the FAQ's "down the ladder, ride up, up that ladder, -exit the door". - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 6 | 11 | `BGEVENT_ITEM` | `DiglettsCaveHiddenMaxRevive` -> `hiddenitem MAX_REVIVE, EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `DIGLETTSCAVE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 3 | 31 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `DiglettsCavePokefanMScript` | -1 | - -**Trainers** - none, matching the FAQ. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_REVIVE` | hidden (ITEMFINDER / bump) | `bg_event 6, 11, BGEVENT_ITEM` | `EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE` (`constants/event_flags.asm:228`) | - -**Wild encounters** - `data/wild/kanto_grass.asm:5` `def_grass_wildmons -DIGLETTS_CAVE`, rates `4 / 2 / 8` percent (morn/day/nite). All three time slots -carry the same seven rows: - -| slot | level | species | -|---|---|---| -| 1 | 15 | DIGLETT | -| 2 | 17 | DIGLETT | -| 3 | 19 | DIGLETT | -| 4 | 13 | DIGLETT | -| 5 | 19 | DUGTRIO | -| 6 | 24 | DUGTRIO | -| 7 | 29 | DUGTRIO | - -The FAQ's "capture both Diglett and Dugtrio here" is correct, and night is the -best time (8% vs 2% by day). - -The FAQ's "ride your bike" is legal here: `BikeFunction`'s `.CheckEnvironment` -(`engine/events/overworld.asm:1665`) allows outdoor maps, `CAVE` and `GATE`. - ---- - -### MAP_ROUTE_2 - -- Script: `maps/Route2.asm` -- Blocks: `maps/Route2.blk` (270 bytes = 10 x 27 blocks) -- Header: `data/maps/maps.asm:455` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_2, MUSIC_ROUTE_2, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:421` `map_const ROUTE_2, 10, 27` (group `VIRIDIAN`, id 1) = 20 x 54 cells -- Attributes: `data/maps/attributes.asm:255` `map_attributes Route2, ROUTE_2, $0f` -- Connections: `connection north, PewterCity, PEWTER_CITY, -5`; - `connection south, ViridianCity, VIRIDIAN_CITY, -5` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 15 | 15 | `ROUTE_2_NUGGET_HOUSE` | 1 | -| 2 | 15 | 31 | `ROUTE_2_GATE` | 3 | -| 3 | 16 | 27 | `ROUTE_2_GATE` | 1 | -| 4 | 17 | 27 | `ROUTE_2_GATE` | 2 | -| 5 | 12 | 7 | `DIGLETTS_CAVE` | 3 | - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 51 | `BGEVENT_READ` | `Route2Sign` | -| 11 | 9 | `BGEVENT_READ` | `Route2DiglettsCaveSign` | -| 7 | 23 | `BGEVENT_ITEM` | `Route2HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_2_HIDDEN_MAX_ETHER` | -| 4 | 14 | `BGEVENT_ITEM` | `Route2HiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_ROUTE_2_HIDDEN_FULL_HEAL` | -| 4 | 27 | `BGEVENT_ITEM` | `Route2HiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` | -| 11 | 30 | `BGEVENT_ITEM` | `Route2HiddenRevive` -> `hiddenitem REVIVE, EVENT_ROUTE_2_HIDDEN_REVIVE` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE2_BUG_CATCHER1` | `SPRITE_BUG_CATCHER` | 10 | 45 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerBugCatcherRob` | -1 | -| `ROUTE2_BUG_CATCHER2` | `SPRITE_BUG_CATCHER` | 5 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 1,0) | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerBugCatcherEd` | -1 | -| `ROUTE2_BUG_CATCHER3` | `SPRITE_BUG_CATCHER` | 4 | 43 | `SPRITEMOVEDATA_STANDING_UP` (radius 1,0) | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerBugCatcherDoug` | -1 | -| `ROUTE2_POKE_BALL1` | `SPRITE_POKE_BALL` | 0 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2DireHit` (`itemball DIRE_HIT`) | `EVENT_ROUTE_2_DIRE_HIT` | -| `ROUTE2_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 23 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2MaxPotion` (`itemball MAX_POTION`) | `EVENT_ROUTE_2_MAX_POTION` | -| `ROUTE2_POKE_BALL3` | `SPRITE_POKE_BALL` | 19 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2Carbos` (`itemball CARBOS`) | `EVENT_ROUTE_2_CARBOS` | -| `ROUTE2_POKE_BALL4` | `SPRITE_POKE_BALL` | 14 | 50 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2Elixer` (`itemball ELIXER`) | `EVENT_ROUTE_2_ELIXER` | -| `ROUTE2_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 10 | 14 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route2FruitTree` (`fruittree FRUITTREE_ROUTE_2`) | -1 | - -**Cut trees** (not asm rows - decoded from `maps/Route2.blk` against the -`CUT_TREE` entries of `data/tilesets/kanto_collision.asm`, which are blocks -`$00 $32 $33 $34 $35 $60`) - -| block (bx,by) | block id | covered cells (x, y) | which FAQ beat | -|---|---|---|---| -| (2, 4) | `$32` | 4-5, 8-9 | near the Diglett's Cave door, not used by the FAQ | -| (7, 9) | `$32` | 14-15, 18-19 | "cut the tree to the south" (just below the Nugget house at (15,15)) | -| (5, 20) | `$34` | 10-11, 40-41 | not named by the FAQ | -| (6, 23) | `$34` | 12-13, 46-47 | not named by the FAQ | -| (6, 25) | `$34` | 12-13, 50-51 | "cut the tree just left of it" (immediately left of the Elixer at (14,50)) | - -**Scripts of interest** - -- `TrainerBugCatcherRob` / `TrainerBugCatcherEd` / `TrainerBugCatcherDoug` - (`4d:5bc2`, `4d:5bd6`, `4d:5bea`): each is the plain `trainer CLASS, MEMBER, - EVENT_*, seen, beaten, 0, .Script` header with an `endifjustbattled` + - after-battle text body. No items, no flags beyond the beaten event. -- `Route2FruitTree`: `fruittree FRUITTREE_ROUTE_2` -> - `data/items/fruit_trees.asm:28` `db PSNCUREBERRY`. This is the FAQ's - "PSNCure Berry". - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_2_DIRE_HIT` | `constants/event_flags.asm:1318` | item ball object flag | clear = ball present | -| `EVENT_ROUTE_2_MAX_POTION` | `constants/event_flags.asm:1319` | item ball object flag | clear = ball present | -| `EVENT_ROUTE_2_CARBOS` | `constants/event_flags.asm:1320` | item ball object flag | clear = ball present | -| `EVENT_ROUTE_2_ELIXER` | `constants/event_flags.asm:1321` | item ball object flag | clear = ball present | -| `EVENT_ROUTE_2_HIDDEN_MAX_ETHER` | `constants/event_flags.asm:237` | `Route2HiddenMaxEther` | hidden pickup | -| `EVENT_ROUTE_2_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:238` | `Route2HiddenFullHeal` | hidden pickup | -| `EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` | `constants/event_flags.asm:239` | `Route2HiddenFullRestore` | hidden pickup | -| `EVENT_ROUTE_2_HIDDEN_REVIVE` | `constants/event_flags.asm:240` | `Route2HiddenRevive` | hidden pickup | -| `EVENT_BEAT_BUG_CATCHER_ROB` | `constants/event_flags.asm:840` | `TrainerBugCatcherRob` | trainer beaten | -| `EVENT_BEAT_BUG_CATCHER_ED` | `constants/event_flags.asm:841` | `TrainerBugCatcherEd` | trainer beaten | -| `EVENT_BEAT_BUG_CATCHER_DOUG` | `constants/event_flags.asm:850` | `TrainerBugCatcherDoug` | trainer beaten | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `CARBOS` | item ball at (19,2) | `Route2Carbos` | `EVENT_ROUTE_2_CARBOS` | -| `MAX_POTION` | item ball at (2,23) | `Route2MaxPotion` | `EVENT_ROUTE_2_MAX_POTION` | -| `DIRE_HIT` | item ball at (0,29) | `Route2DireHit` | `EVENT_ROUTE_2_DIRE_HIT` | -| `ELIXER` | item ball at (14,50) | `Route2Elixer` | `EVENT_ROUTE_2_ELIXER` | -| `PSNCUREBERRY` | fruit tree object at (10,14) | `FRUITTREE_ROUTE_2`, `data/items/fruit_trees.asm:28` | daily reset, not an event flag | -| `MAX_ETHER` | hidden at (7,23) | bg_event | `EVENT_ROUTE_2_HIDDEN_MAX_ETHER` | -| `FULL_HEAL` | hidden at (4,14) | bg_event | `EVENT_ROUTE_2_HIDDEN_FULL_HEAL` | -| `FULL_RESTORE` | hidden at (4,27) | bg_event | `EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` | -| `REVIVE` | hidden at (11,30) | bg_event | `EVENT_ROUTE_2_HIDDEN_REVIVE` | -| `NUGGET` | Route 2 Nugget House NPC | `Route2NuggetHouseFisherScript` | `EVENT_GOT_NUGGET_FROM_GUY` | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `ROB` (`constants/trainer_constants.asm:314`) | `BUG_CATCHER` | 2 | `BugCatcherGroup` "ROB" (`data/trainers/parties.asm:1416`): L32 BEEDRILL, L32 BUTTERFREE, `TRAINERTYPE_NORMAL` | `TrainerBugCatcherRob` | no | -| `DOUG` (`constants/trainer_constants.asm:324`) | `BUG_CATCHER` | 12 | `BugCatcherGroup` "DOUG" (`data/trainers/parties.asm:1482`): L34 ARIADOS | `TrainerBugCatcherDoug` | no | -| `ED` (`constants/trainer_constants.asm:315`) | `BUG_CATCHER` | 3 | `BugCatcherGroup` "ED" (`data/trainers/parties.asm:1422`): L30 BEEDRILL x3 | `TrainerBugCatcherEd` | no | - -`BUG_CATCHER` base reward 4 (`data/trainers/attributes.asm:215`); prize = -base x last mon level (`ComputeTrainerReward`, -`engine/battle/read_trainer_party.asm:300`). - -**Wild encounters** - `data/wild/kanto_grass.asm:259` `def_grass_wildmons -ROUTE_2`, rates `10 / 10 / 10`. Gold (`IF DEF(_GOLD)`): - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | L3 CATERPIE | L3 CATERPIE | L3 HOOTHOOT | -| 2 | L3 PIDGEY | L3 PIDGEY | L3 SPINARAK | -| 3 | L5 METAPOD | L5 METAPOD | L5 HOOTHOOT | -| 4 | L7 BUTTERFREE | L7 PIDGEY | L7 NOCTOWL | -| 5 | L7 PIDGEOTTO | L7 PIDGEOTTO | L7 ARIADOS | -| 6 | L4 PIKACHU | L4 PIKACHU | L4 PIKACHU | -| 7 | L4 PIKACHU | L4 PIKACHU | L4 PIKACHU | - -(The Silver table under `ELIF DEF(_SILVER)` swaps the Caterpie line for -Weedle/Kakuna/Beedrill and Spinarak for Ledyba.) - ---- - -### MAP_ROUTE_2_NUGGET_HOUSE - -- Script: `maps/Route2NuggetHouse.asm` -- Header: `data/maps/maps.asm:465` -> `TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_2, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:431` `map_const ROUTE_2_NUGGET_HOUSE, 4, 4` -- Attributes: `data/maps/attributes.asm:664`; no connections - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_2` | 1 | -| 2 | 3 | 7 | `ROUTE_2` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE2NUGGETHOUSE_FISHER` | `SPRITE_FISHER` | 2 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `Route2NuggetHouseFisherScript` | -1 | - -**Scripts of interest** - -- `Route2NuggetHouseFisherScript` (`5f:4e26`): `faceplayer` / `opentext` / - `checkevent EVENT_GOT_NUGGET_FROM_GUY` -> `.GotNugget`; otherwise text, - `promptbutton`, `verbosegiveitem NUGGET`, `iffalse .NoRoom`, - `setevent EVENT_GOT_NUGGET_FROM_GUY`. A full bag skips the flag, so the gift - is re-offerable. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_NUGGET_FROM_GUY` | `constants/event_flags.asm:199` | `Route2NuggetHouseFisherScript` | one-time NUGGET | - ---- - -### MAP_ROUTE_2_GATE - -- Script: `maps/Route2Gate.asm` -- Header: `data/maps/maps.asm:466` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_2, MUSIC_ROUTE_2, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:432` `map_const ROUTE_2_GATE, 5, 4` -- Attributes: `data/maps/attributes.asm:665`; no connections - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `ROUTE_2` | 3 | -| 2 | 5 | 0 | `ROUTE_2` | 4 | -| 3 | 4 | 7 | `ROUTE_2` | 2 | -| 4 | 5 | 7 | `ROUTE_2` | 2 | - -Both sides land back on Route 2 - this is the FAQ's "route-changing house... -You'll still be in Route 2". North side (y=0) <-> Route 2 (16,27)/(17,27); -south side (y=7) <-> Route 2 (15,31). - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE2GATE_SCIENTIST` | `SPRITE_SCIENTIST` | 6 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `Route2GateScientistScript` | -1 | - -`Route2GateScientistScript` is `jumptextfaceplayer` only (Oak's aide flavour). - ---- - -### MAP_PEWTER_CITY - -- Script: `maps/PewterCity.asm` -- Blocks: `maps/PewterCity.blk` -- Header: `data/maps/maps.asm:322` -> `TILESET_KANTO, TOWN, LANDMARK_PEWTER_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:297` `map_const PEWTER_CITY, 20, 18` (group `PEWTER`, id 2) = 40 x 36 cells -- Attributes: `data/maps/attributes.asm:251` `map_attributes PewterCity, PEWTER_CITY, $0f` -- Connections: `connection south, Route2, ROUTE_2, 5`; `connection east, Route3, ROUTE_3, 5` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 29 | 13 | `PEWTER_NIDORAN_SPEECH_HOUSE` | 1 | -| 2 | 16 | 17 | `PEWTER_GYM` | 1 | -| 3 | 23 | 17 | `PEWTER_MART` | 2 | -| 4 | 13 | 25 | `PEWTER_POKECENTER_1F` | 1 | -| 5 | 7 | 29 | `PEWTER_SNOOZE_SPEECH_HOUSE` | 1 | - -There is **no museum warp**: the FAQ's "they even filled the doorway in" is -literally true in the asm - the museum exists only as a sign. - -**Coord events** - none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 25 | 23 | `BGEVENT_READ` | `PewterCitySign` | -| 11 | 17 | `BGEVENT_READ` | `PewterGymSign` | -| 15 | 9 | `BGEVENT_READ` | `PewterMuseumSign` ("closed for renovations") | -| 33 | 19 | `BGEVENT_READ` | `PewterCityMtMoonGiftShopSign` | -| 19 | 29 | `BGEVENT_READ` | `PewterCityWelcomeSign` | -| 14 | 25 | `BGEVENT_READ` | `PewterCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 24 | 17 | `BGEVENT_READ` | `PewterCityMartSign` (`jumpstd MartSignScript`) | - -No hidden items in Pewter City. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PEWTERCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 19 | 11 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `PewterCityCooltrainerFScript` | -1 | -| `PEWTERCITY_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 14 | 29 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `PewterCityBugCatcherScript` | -1 | -| `PEWTERCITY_GRAMPS` | `SPRITE_GRAMPS` | 29 | 17 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `PewterCityGrampsScript` | -1 | -| `PEWTERCITY_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 32 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PewterCityFruitTree1` (`fruittree FRUITTREE_PEWTER_CITY_1`) | -1 | -| `PEWTERCITY_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 30 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PewterCityFruitTree2` (`fruittree FRUITTREE_PEWTER_CITY_2`) | -1 | - -**Scripts of interest** - -- `PewterCityGrampsScript` (`4d:583e`). `faceplayer` / `opentext` / `checkver` - / `iftrue .RainbowWing`. `checkver` leaves `GS_VERSION` in `wScriptVar` - (0 = Gold, 1 = Silver), so: - - **Gold**: `checkevent EVENT_GOT_SILVER_WING` -> `.GotSilverWing`; otherwise - text, `promptbutton`, `verbosegiveitem SILVER_WING`, - `setevent EVENT_GOT_SILVER_WING`. Note there is **no `iffalse` guard** on - this `verbosegiveitem`: the flag is set whether or not the bag had room. - - **Silver**: `checkevent EVENT_GOT_RAINBOW_WING` -> `.GotSilverWing`; - otherwise `verbosegiveitem RAINBOW_WING`, `setevent EVENT_GOT_RAINBOW_WING`, - `setevent EVENT_TEAM_ROCKET_DISBANDED`. - This gramps is the *second* source for the wing pair: the Radio Tower director - (`maps/RadioTower5F.asm:120-140`) gives the opposite one on the same - `checkver` split. -- `PewterCityFlypointCallback` (`MAPCALLBACK_NEWMAP`): - `setflag ENGINE_FLYPOINT_PEWTER`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_GOT_SILVER_WING` | `constants/event_flags.asm:130` | Gold branch of `PewterCityGrampsScript`; also `maps/RadioTower5F.asm:139` (Silver) | one-time | -| `EVENT_GOT_RAINBOW_WING` | `constants/event_flags.asm:129` | Silver branch of `PewterCityGrampsScript`; also `maps/RadioTower5F.asm:129` (Gold); read by `maps/EcruteakTinTowerEntrance.asm:62` | one-time; gates Tin Tower entry | -| `EVENT_TEAM_ROCKET_DISBANDED` | `constants/event_flags.asm:1283` | set by the Silver branch here | side effect of the Rainbow Wing gift | -| `ENGINE_FLYPOINT_PEWTER` | `constants/engine_flags.asm:69` | `PewterCityFlypointCallback` | Fly destination | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `SILVER_WING` (Gold) / `RAINBOW_WING` (Silver) | talk to `PEWTERCITY_GRAMPS` at (29,17) | `PewterCityGrampsScript` | `EVENT_GOT_SILVER_WING` / `EVENT_GOT_RAINBOW_WING` | -| `ICE_BERRY` | fruit tree at (32,3) | `FRUITTREE_PEWTER_CITY_1`, `data/items/fruit_trees.asm:31` | daily | -| `MINT_BERRY` | fruit tree at (30,3) | `FRUITTREE_PEWTER_CITY_2`, `data/items/fruit_trees.asm:32` | daily | - -**Wild encounters** - none (TOWN, no grass table). - ---- - -### MAP_PEWTER_POKECENTER_1F - -- Script: `maps/PewterPokecenter1F.asm` -- Header: `data/maps/maps.asm:326` -> `TILESET_POKECENTER, INDOOR, LANDMARK_PEWTER_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:301` `map_const PEWTER_POKECENTER_1F, 5, 4` -- Attributes: `data/maps/attributes.asm:583` - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `PEWTER_CITY` | 4 | -| 2 | 4 | 7 | `PEWTER_CITY` | 4 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PEWTERPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 | -| `PEWTERPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 8 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FTeacherScript` | -1 | -| `PEWTERPOKECENTER1F_JIGGLYPUFF` | `SPRITE_JIGGLYPUFF` | 1 | 3 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `PewterJigglypuff` | -1 | -| `PEWTERPOKECENTER1F_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FBugCatcherScript` | -1 | -| `PEWTERPOKECENTER1F_CHRIS` | `SPRITE_POKEFAN_M` | 7 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Chris` | -1 | - -**Scripts of interest** - -- `Chris` (`5a:46f5`): `faceplayer` / `opentext` / `trade NPC_TRADE_CHRIS` / - `waitbutton` / `closetext` / `end`. The trade row is - `data/events/npc_trades.asm:18`: - `npctrade TRADE_DIALOGSET_HAPPY, GLOOM, RAPIDASH, "RUNNY", $96, $66, - BURNT_BERRY, 15616, "CHRIS", TRADE_GENDER_EITHER` - the player gives GLOOM and - receives a RAPIDASH nicknamed RUNNY, DVs `$96/$66`, holding a BURNT_BERRY, OT - "CHRIS" ID 15616. The FAQ's "trade a Gloom for a Rapidash" is exact. Note the - NPC sprite is `SPRITE_POKEFAN_M`, not a lady. - ---- - -### MAP_PEWTER_GYM - -- Script: `maps/PewterGym.asm` -- Blocks: `maps/PewterGym.blk` -- Header: `data/maps/maps.asm:324` -> `TILESET_TOWER, INDOOR, LANDMARK_PEWTER_CITY, MUSIC_GYM, TRUE (no phone), PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:299` `map_const PEWTER_GYM, 5, 7` (group `PEWTER`, id 4) = 10 x 14 cells -- Attributes: `data/maps/attributes.asm:581` `map_attributes PewterGym, PEWTER_GYM, $00`; no connections - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 13 | `PEWTER_CITY` | 2 | -| 2 | 5 | 13 | `PEWTER_CITY` | 2 | - -**Coord events** - none. There is no lock-in trip-wire in this gym. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 2 | 11 | `BGEVENT_READ` | `PewterGymStatue` | -| 7 | 11 | `BGEVENT_READ` | `PewterGymStatue` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PEWTERGYM_BROCK` | `SPRITE_BROCK` | 5 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterGymBrockScript` | -1 | -| `PEWTERGYM_YOUNGSTER` | `SPRITE_YOUNGSTER` | 2 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerCamperJerry` | -1 | -| `PEWTERGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 6 | 11 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight 1) | `PewterGymGuideScript` | -1 | - -**Scripts of interest** - -- `PewterGymBrockScript` (`5a:405f`). `faceplayer` / `opentext` / - `checkflag ENGINE_BOULDERBADGE` / `iftrue .FightDone`. Otherwise: - `writetext BrockIntroText`, `waitbutton`, `closetext`, - `winlosstext BrockWinLossText, 0`, `loadtrainer BROCK, BROCK1`, - `startbattle`, `reloadmapafterbattle`, `setevent EVENT_BEAT_BROCK`, - **`setevent EVENT_BEAT_CAMPER_JERRY`**, then `opentext` / - `writetext ReceivedBoulderBadgeText` / `playsound SFX_GET_BADGE` / `waitsfx` / - `setflag ENGINE_BOULDERBADGE` / `writetext BrockBoulderBadgeText` / - `waitbutton` / `closetext` / `end`. - Two consequences for a bot: (1) Camper Jerry is optional - beating Brock marks - him beaten; (2) the badge is a `setflag` on the ENGINE namespace, not an - `EVENT_*`. -- `TrainerCamperJerry` (`5a:4093`): standard `trainer CAMPER, JERRY, - EVENT_BEAT_CAMPER_JERRY, ...` header, sight range 3 facing right from (2,5). -- `PewterGymGuideScript`: `checkevent EVENT_BEAT_BROCK`, two text branches. -- `PewterGymStatue`: `checkflag ENGINE_BOULDERBADGE`; unbeaten -> `jumpstd - GymStatue1Script`, beaten -> `gettrainername STRING_BUFFER_4, BROCK, BROCK1` - then `jumpstd GymStatue2Script`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_BOULDERBADGE` | `constants/engine_flags.asm:47` (row `data/events/engine_flags.asm`, backed by `wKantoBadges`) | checked and set by `PewterGymBrockScript`; checked by `PewterGymStatue` | the gym's own re-entry guard, and the ROCK-type damage boost via `BadgeTypeBoosts` | -| `EVENT_BEAT_BROCK` | `constants/event_flags.asm:715` | set by `PewterGymBrockScript`; read by `PewterGymGuideScript` | gym cleared | -| `EVENT_BEAT_CAMPER_JERRY` | `constants/event_flags.asm:545` | `TrainerCamperJerry`, and also set by `PewterGymBrockScript` | trainer beaten / skipped | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `JERRY` (`constants/trainer_constants.asm:566`) | `CAMPER` | 18 | `CamperGroup` "JERRY" (`data/trainers/parties.asm:2811`): L37 SANDSLASH, `TRAINERTYPE_NORMAL` | `TrainerCamperJerry` | no | -| `BROCK1` (`constants/trainer_constants.asm:91`, class `BROCK` = 11 at line 90) | `BROCK` | 1 | `BrockGroup` (`data/trainers/parties.asm:270`, symbol `0e:5cda`), `TRAINERTYPE_MOVES` | `PewterGymBrockScript` | no phone | - -`BrockGroup` party, in asm order: - -| # | level | species | moves | -|---|---|---|---| -| 1 | 41 | GRAVELER | DEFENSE_CURL, ROCK_SLIDE, ROLLOUT, EARTHQUAKE | -| 2 | 41 | RHYHORN | FURY_ATTACK, SCARY_FACE, EARTHQUAKE, HORN_DRILL | -| 3 | 42 | OMASTAR | BITE, SURF, PROTECT, SPIKE_CANNON | -| 4 | 44 | ONIX | BIND, ROCK_SLIDE, BIDE, SANDSTORM | -| 5 | 42 | KABUTOPS | SLASH, SURF, ENDURE, GIGA_DRAIN | - -Class attributes (`data/trainers/attributes.asm:101`): item1 `HYPER_POTION`, -base reward 25, AI `AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | -AI_CAUTIOUS | AI_STATUS | AI_RISKY`, `CONTEXT_USE | SWITCH_SOMETIMES`. Camper -base reward 5 (`data/trainers/attributes.asm:323`). - -**Wild encounters** - none. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Route 19 boulders | `maps/Route19.asm:Route19ClearRocksCallback` (`4e:4f09`), six `changeblock ... $7a` writes under `callback MAPCALLBACK_TILES` | `EVENT_CINNABAR_ROCKS_CLEARED` | Set only by `maps/Route20.asm:Route20ClearRocksCallback` (`MAPCALLBACK_NEWMAP`) - i.e. by reaching Route 20 from the Cinnabar side. Nothing in this section can clear it; the FAQ is right that you must turn around. | -| SNORLAX blocking the DIGLETT'S CAVE door | `maps/VermilionCity.asm` object at (34,8) with a `SPRITEMOVEDATA_BIGDOLLSYM` 2x2 footprint sitting directly under warp 10 at (34,7) | `EVENT_VERMILION_CITY_SNORLAX` clear = present | `disappear VERMILIONCITY_BIG_SNORLAX` at the end of `VermilionSnorlax`, which only runs after `startbattle` | -| SNORLAX will not wake | `engine/events/specials.asm:358 SnorlaxAwake` | `wMapMusic == MUSIC_POKE_FLUTE_CHANNEL` **and** player on one of (33,8), (34,10), (35,10), (36,8), (36,9) | Pokegear radio tuned to knob 78 / "20.0" | -| POKe FLUTE radio channel is static | `engine/pokegear/pokegear.asm:1487 .PokeFluteRadio` | not in Johto (`.InJohto`, landmark >= `KANTO_LANDMARK`) **and** `POKEGEAR_EXPN_CARD_F` set in `wPokegearFlags` | EXPN CARD, obtained earlier (Lavender radio director) | -| Route 2 cut trees | tileset collision `COLL_CUT_TREE` (`constants/collision_constants.asm:16`), tested by `CheckCutCollision` (`engine/overworld/tile_events.asm:76`) | HM01 CUT + `ENGINE_HIVEBADGE` (`engine/events/overworld.asm:133 CutFunction.CheckAble`) | already held long before this section | -| Diglett's Cave traversal | `maps/DiglettsCave.blk` geometry - the three pockets are joined only by warps 2<->5 and 6<->4 | none beyond walking | - | -| Pewter Gym re-fight | `maps/PewterGym.asm:PewterGymBrockScript` `checkflag ENGINE_BOULDERBADGE / iftrue .FightDone` | badge not yet held | one-shot | -| Pewter museum | no warp exists in `PewterCity_MapEvents`; only `bg_event 15, 9 PewterMuseumSign` | - | permanently closed in GS | - ---- - -## 4. Bot checklist - -Coordinates are asm cell coordinates. "clear" / "set" refer to event flags. - -1. `ROUTE_19_FUCHSIA_GATE` -> `ROUTE_19`: walk warp 3/4 at (4,7)/(5,7). - Precondition: none. Expect to be stopped; `EVENT_CINNABAR_ROCKS_CLEARED` is - clear, so blocks `(6,6) (8,6) (10,6) (12,8) (4,8) (10,10)` are rock. Turn - around through warp 1 at (7,3). -2. FLY to `VERMILION_CITY`. Precondition: `ENGINE_FLYPOINT_VERMILION` set. -3. Open the Pokegear, RADIO card, tune the knob to 78 ("20.0"). Precondition: - `POKEGEAR_EXPN_CARD_F` set and the landmark is Kanto. Postcondition: - `wMapMusic == MUSIC_POKE_FLUTE_CHANNEL`. -4. Close the Pokegear and walk to one of (33,8) / (34,10) / (35,10) / (36,8) / - (36,9). Face `VERMILIONCITY_BIG_SNORLAX` at (34,8) and press A. - Precondition: `EVENT_VERMILION_CITY_SNORLAX` clear. - Effect: `SnorlaxAwake` returns TRUE -> wild L50 SNORLAX with LEFTOVERS forced. -5. Battle: catch or defeat. Either way, postcondition - `EVENT_VERMILION_CITY_SNORLAX` set (object removed) and - `EVENT_FOUGHT_SNORLAX` set. **Save before step 4** - a failed catch is not - repeatable. -6. Optional: `VERMILION_CITY` warp 2 at (9,5) -> `VERMILION_POKECENTER_1F`, heal - and withdraw SNORLAX; back out warps 1/2 at (3,7)/(4,7). -7. `VERMILION_CITY` warp 10 at (34,7) -> `DIGLETTS_CAVE` warp 1 at (3,33). -8. `DIGLETTS_CAVE`: walk (3,33) -> (5,31) (warp 2, ladder). Land at (17,33). -9. Walk the long cave (17,33) -> (3,3) (warp 6, ladder). Land at (17,3). - Optional en route: hidden `MAX_REVIVE` at (6,11), - flag `EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE`. -10. Walk (17,3) -> (15,5) (warp 3, door) -> `ROUTE_2` warp 5 at (12,7). -11. `ROUTE_2`: item ball `ROUTE2_POKE_BALL3` at (19,2) = CARBOS. - Precondition `EVENT_ROUTE_2_CARBOS` clear; postcondition set. -12. Warp 1 at (15,15) -> `ROUTE_2_NUGGET_HOUSE`. Talk to - `ROUTE2NUGGETHOUSE_FISHER` at (2,4). Precondition - `EVENT_GOT_NUGGET_FROM_GUY` clear and >=1 bag slot; postcondition set, - NUGGET in bag. Exit warps 1/2 at (2,7)/(3,7). -13. Optional: fruit tree `ROUTE2_FRUIT_TREE` at (10,14) = PSNCUREBERRY (daily). -14. CUT the tree covering cells (14-15, 18-19). Precondition: HM01 in bag and - `ENGINE_HIVEBADGE` set. -15. Warp 3/4 at (16,27)/(17,27) -> `ROUTE_2_GATE` -> warps 3/4 at (4,7)/(5,7) -> - `ROUTE_2` warp 2 at (15,31). Still Route 2. -16. Item ball `ROUTE2_POKE_BALL4` at (14,50) = ELIXER. - Flag `EVENT_ROUTE_2_ELIXER`. -17. CUT the tree covering cells (12-13, 50-51), then head north. -18. Battle `TrainerBugCatcherRob` - object at (10,45), facing left, sight 5. - Party L32 BEEDRILL / L32 BUTTERFREE. Postcondition - `EVENT_BEAT_BUG_CATCHER_ROB`. -19. Battle `TrainerBugCatcherDoug` - object at (4,43), facing up, sight 5. - Party L34 ARIADOS. Postcondition `EVENT_BEAT_BUG_CATCHER_DOUG`. -20. Optional pickups on the way north: item ball at (0,29) = DIRE_HIT - (`EVENT_ROUTE_2_DIRE_HIT`); item ball at (2,23) = MAX_POTION - (`EVENT_ROUTE_2_MAX_POTION`); hidden MAX_ETHER at (7,23), hidden FULL_HEAL - at (4,14), hidden FULL_RESTORE at (4,27), hidden REVIVE at (11,30). -21. Battle `TrainerBugCatcherEd` - object at (5,5), facing right, sight 4. - Party L30 BEEDRILL x3. Postcondition `EVENT_BEAT_BUG_CATCHER_ED`. -22. Walk off the north edge of `ROUTE_2` into `PEWTER_CITY` - (`connection north, PewterCity, PEWTER_CITY, -5`). Postcondition - `ENGINE_FLYPOINT_PEWTER` set by `PewterCityFlypointCallback`. -23. Talk to `PEWTERCITY_GRAMPS` at (29,17) (he paces x 27..31 on y=17). - Precondition `EVENT_GOT_SILVER_WING` clear (Gold). - Postcondition: SILVER_WING in bag, `EVENT_GOT_SILVER_WING` set. - On Silver: RAINBOW_WING, `EVENT_GOT_RAINBOW_WING` and - `EVENT_TEAM_ROCKET_DISBANDED` set. -24. Optional: warp 4 at (13,25) -> `PEWTER_POKECENTER_1F`. Heal at - `PEWTERPOKECENTER1F_NURSE` (3,1). Talk to `Chris` at (7,2) to trade a GLOOM - for RAPIDASH "RUNNY". Optional: fruit trees at (32,3) = ICE_BERRY and - (30,3) = MINT_BERRY. -25. `PEWTER_CITY` warp 2 at (16,17) -> `PEWTER_GYM` (arrive at warp 1, (4,13)). -26. Optional: battle `TrainerCamperJerry`, object at (2,5), facing right, - sight 3. L37 SANDSLASH. -27. Talk to `PEWTERGYM_BROCK` at (5,1). Precondition `ENGINE_BOULDERBADGE` - clear. Battle `loadtrainer BROCK, BROCK1` (5 mon, L41-44). - Postconditions: `EVENT_BEAT_BROCK`, `EVENT_BEAT_CAMPER_JERRY`, - `ENGINE_BOULDERBADGE`. -28. Exit warps 1/2 at (4,13)/(5,13) -> `PEWTER_CITY` warp 2; heal. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| All maps / warps / objects / bg events for this section | `src/import/RomExtractorGen2.lua` (`MAP_GROUP_COUNT = 26`, line 47; the group walk at line 527) | implemented - every Kanto group is extracted, so these maps are data, not hand-ported code | -| `MAPCALLBACK_TILES` + `changeblock` (Route 19 boulders) | `src/script/gen2/Opcodes.lua:127` (`changeblock`), driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| `special SnorlaxAwake` | `src/script/gen2/Specials.lua:1562` `H.SnorlaxAwake`, with `Specials.SNORLAX_PROXIMITY` transcribing `.ProximityCoords` and `Specials.POKE_FLUTE_SONG = "Music_PokeFluteChannel"`; hooks `currentMusic` / `playerCell` at `src/world/gen2/World.lua:2712` and `:2723` | implemented | -| POKe FLUTE radio channel (knob 78 / 20.0) + EXPN CARD gate | `src/ui/gen2/Pokegear.lua:812-815` (the `knob = 78` row, `ctx.inJohto` / `ctx.expnCard`) | implemented | -| `BATTLETYPE_FORCEITEM` -> Snorlax holds LEFTOVERS | `src/world/gen2/World.lua:111` and `:4576` | implemented | -| `loadwildmon` / `startbattle` / `reloadmapafterbattle` / `disappear` | `src/script/gen2/Vm.lua:806-841` | implemented | -| Catching a wild mon (Ultra Balls on Snorlax) | `src/battle/gen2/Catching.lua` | implemented | -| Trainer battles from `OBJECTTYPE_TRAINER` rows (Rob / Doug / Ed / Jerry) | `src/world/gen2/Trainers.lua`, `src/script/gen2/Vm.lua:806` (`loadtrainer`) | implemented | -| Gym leader script shape (`checkflag`/`winlosstext`/`setflag ENGINE_BOULDERBADGE`) | `src/script/gen2/Vm.lua:208` (`setflag`/`clearflag`), `src/world/gen2/World.lua:1296-1340` (`engineFlags`) | implemented | -| `BadgeTypeBoosts` - BOULDERBADGE boosting ROCK moves (`data/types/badge_type_boosts.asm`, `engine/battle/misc.asm:146 DoBadgeTypeBoosts`) | no match anywhere in `src/battle/gen2/` | **missing** (the Gen 1 port has its own badge table in `src/battle/Damage.lua:28`, but nothing in the Gen 2 battle path reads badges) | -| Item ball pickup (Route 2 CARBOS / DIRE_HIT / ELIXER / MAX_POTION) | extracted into `obj.itemball` at `src/import/RomExtractorGen2.lua:2874` and `:2968`, but no consumer exists in `src/world/gen2/`; `src/script/gen2/CallAsm.lua:550` stubs `TryReceiveItem` with "wItemBallItemID / wItemBallQuantity are set by a script path this port does not run" | **missing** | -| Hidden items (Diglett's Cave MAX_REVIVE, the four Route 2 hidden items, Vermilion FULL_HEAL) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5291` and the ITEMFINDER path at `:3423` | implemented | -| `verbosegiveitem` (NUGGET, SILVER_WING/RAINBOW_WING) | `src/script/gen2/Opcodes.lua:163`, `src/script/gen2/Vm.lua:501` / `:1201` | implemented | -| `fruittree` (PSNCUREBERRY, ICE_BERRY, MINT_BERRY) | `src/script/gen2/Opcodes.lua:160`, `src/world/gen2/World.lua:1058` (`fruitTreeItem`) | implemented | -| `trade NPC_TRADE_CHRIS` (GLOOM -> RAPIDASH) | `src/core/gen2/NpcTrade.lua`, `src/script/gen2/Opcodes.lua:155` | implemented | -| `checkver` Gold/Silver split in `PewterCityGrampsScript` | `src/script/gen2/Vm.lua:774`, `src/world/gen2/World.lua:1287` (`gsVersion`) | implemented | -| `setflag ENGINE_FLYPOINT_PEWTER` / `_VERMILION` and Fly | `src/world/gen2/World.lua:5628` | implemented | -| CUT field move + HIVEBADGE check | `src/world/gen2/FieldMoves.lua` (badge table at lines 100-101) | implemented | -| Bike allowed in `CAVE` (Diglett's Cave) | `src/world/gen2/Bike.lua` | partial - the file exists and comments the `hiddenitem`/BICYCLE interaction, but the `.CheckEnvironment` outdoor/CAVE/GATE test was not located; verify before relying on it | -| `gettrainername` for the beaten-gym statue | `src/script/gen2/Vm.lua:407` | implemented | - ---- - -## 6. Unresolved / verify by hand - -- **Brock's party order.** The FAQ lists Graveler, Omastar, Rhyhorn, Onix, - Kabutops. `data/trainers/parties.asm:270` `BrockGroup` is Graveler (41), - **Rhyhorn (41), Omastar (42)**, Onix (44), Kabutops (42) - slots 2 and 3 are - swapped relative to the FAQ. Levels and species set match. -- **All the FAQ's prize money figures are exactly 4x the asm value.** - `ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) is - `base reward x last mon level`, with no multiplier. Brock 25 x 42 = 1050 (FAQ - says 4,200); Rob 4 x 32 = 128 (FAQ 512); Doug 4 x 34 = 136 (FAQ 544); Ed - 4 x 30 = 120 (FAQ 480); Jerry 5 x 37 = 185 (FAQ 740). The FAQ's EXP figures - likewise do not come from GS. Treat the money/EXP numbers in this section as - belonging to a different game (most likely HeartGold/SoulSilver, which the - text references) and ignore them for a GS bot. -- **"Viridian Forest".** The FAQ says the Max Potion is "in Viridian Forest". - There is no Viridian Forest map in `constants/map_constants.asm`; the - MAX_POTION is `ROUTE2_POKE_BALL2` at (2,23) on `ROUTE_2`, in the area the - forest used to occupy. -- **"the man just to the right of the Pokemart".** `PEWTERCITY_GRAMPS` spawns at - (29,17) with a `WALK_LEFT_RIGHT` radius of 2 (so x 27..31); the mart door is - warp 3 at (23,17). He is to the right of the mart but not adjacent. -- **"The Pokemon Center has a lady".** The trade NPC is `Chris`, sprite - `SPRITE_POKEFAN_M` at (7,2). Male sprite, name CHRIS. -- **Which cut tree the FAQ means.** The two trees named in step 14 and step 17 - above are inferred from the block dump of `maps/Route2.blk` plus the item/warp - positions; they are not labelled anywhere in asm. Three other `CUT_TREE` - blocks exist on Route 2 (see the cut-tree table) and one of those may be the - intended route instead. Verify with a real walk. -- **Diglett's Cave pocket connectivity.** The three-pocket reading is derived by - hand from `maps/DiglettsCave.blk` block ids against - `data/tilesets/kanto_collision.asm`; the per-tile collision of the "long cave" - interior was not fully traced, only that every warp block is reachable within - its pocket. The FAQ's described route is consistent with it. -- **"the route is cleared to Diglett's Cave and through Route 8".** Nothing in - `maps/VermilionCity.asm` or `maps/Route11.asm` gates Route 11 / Route 8 on the - Snorlax; the only thing `EVENT_FOUGHT_SNORLAX` unlocks outside Vermilion is the - right-hand Black Belt object in `maps/VictoryRoadGate.asm:119`. The Snorlax - blocks only the Diglett's Cave door tile at (34,7). -- **"Keep around those four Ultra Balls"** and the Rest/Thunder Wave catch - advice are strategy, not asm; nothing enforces a ball count. -- **`src/world/gen2/Bike.lua` CAVE permission.** Marked partial in section 5 - - the port file was read but the equivalent of `BikeFunction.CheckEnvironment` - (`engine/events/overworld.asm:1665`) was not located, so "you can bike through - Diglett's Cave in the port" is unverified. diff --git a/docs/gold-walkthrough/asm-walk/section-25-lugia-gold-and-ho-oh-silver.md b/docs/gold-walkthrough/asm-walk/section-25-lugia-gold-and-ho-oh-silver.md deleted file mode 100644 index 6ac04beb..00000000 --- a/docs/gold-walkthrough/asm-walk/section-25-lugia-gold-and-ho-oh-silver.md +++ /dev/null @@ -1,987 +0,0 @@ -# Section 25 - Lugia (Gold) and Ho-Oh (Silver) - -Source: `../section-25-lugia-gold-and-ho-oh-silver.txt` -Maps covered: `ROUTE_41`, `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`, `ECRUTEAK_CITY`, `ECRUTEAK_TIN_TOWER_ENTRANCE`, -`ECRUTEAK_TIN_TOWER_BACK_ENTRANCE`, `TIN_TOWER_1F` .. `TIN_TOWER_9F`, -`TIN_TOWER_ROOF` -Badges / key milestones in this section: no badges. The milestone is catching the -*other* version mascot post-Elite-Four - `EVENT_FOUGHT_LUGIA` / -`EVENT_FOUGHT_HO_OH`, armed by the wing the Pewter City gramps hands over -(`maps/PewterCity.asm`, `PewterCityGrampsScript`). - -This section is two mutually exclusive branches. The walkthrough labels them -"Whirl Islands - Gold Version" and "Tin Tower - Silver Version", and the asm -agrees: `Script_checkver` (`engine/overworld/scripting.asm:1471`) returns -`GS_VERSION` (`constants/misc_constants.asm:21-23`: 0 = Gold, 1 = Silver), so on -each mascot the `checkver / iftrue .Silver` pair picks the level. In Gold, Lugia -is **level 70** and Ho-Oh was already caught at level 40; in Silver it is the -mirror image. - ---- - -## 1. Route order - -The walkthrough opens on the Magnet Train back to Johto - that hop belongs to the -neighbouring Kanto section, and the only thing this section needs from it is that -the player already holds the version-opposite wing. - -**Gold branch (Lugia, Whirl Islands)** - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `OLIVINE_CITY` | (fly target) | Fly | west connection to `ROUTE_40` (`data/maps/attributes.asm:145`) | "Fly to Olivine City" | -| 2 | `ROUTE_40` | `data/maps/attributes.asm:223` | east connection from Olivine | south connection to `ROUTE_41` (`attributes.asm:224`) | surf southwest | -| 3 | `ROUTE_41` | `maps/Route41.asm` | north connection from Route 40 | `warp_event 36, 19` (warp 2) | "surf southwest and west until you reach the northeast cave", clearing whirlpools | -| 4 | `WHIRL_ISLAND_NE` | `maps/WhirlIslandNE.asm` | warp 1 `(3, 13)` | warp 2 `(17, 3)` or warp 3 `(13, 11)` | Flash, ledges, `ULTRA_BALL` at `(11, 11)` | -| 5 | `WHIRL_ISLAND_B1F` | `maps/WhirlIslandB1F.asm` | warp 2 `(35, 3)` / warp 3 `(29, 9)` | warp 7 `(25, 21)` or warp 8 `(13, 27)` | ledge chain, `ESCAPE_ROPE` at `(19, 26)`, `CALCIUM` at `(33, 23)` | -| 6 | `WHIRL_ISLAND_CAVE` | `maps/WhirlIslandCave.asm` | B1F warp 9 `(17, 21)` -> Cave warp 1 `(7, 5)` | Cave warp 2 `(3, 13)` -> `WHIRL_ISLAND_NW` | "head out the door to breathe, but that's pointless" | -| 7 | `WHIRL_ISLAND_SW` | `maps/WhirlIslandSW.asm` | B1F warp 4 `(9, 31)` / warp 5 `(23, 31)` | warp 5 `(17, 15)` -> B2F warp 4 | `GUARD_SPEC` at `(15, 2)` | -| 8 | `WHIRL_ISLAND_B2F` | `maps/WhirlIslandB2F.asm` | warp 1 `(11, 5)` from B1F warp 7 | warp 3 `(7, 25)` | `MAX_REVIVE` `(6, 4)`, waterfall descent, "head left and through the door" | -| 9 | `WHIRL_ISLAND_LUGIA_CHAMBER` | `maps/WhirlIslandLugiaChamber.asm` | warp 1 `(9, 13)` | Escape Rope (environment is `CAVE`) | surf north to Lugia at `(9, 5)` | - -**Silver branch (Ho-Oh, Tin Tower)** - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | Fly | warp 3 `(18, 11)` | "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)` -> warp 4 `(17, 15)`, then warp 5 `(17, 3)` | "the Bell Tower entry house. Talk to the man" | -| 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 `(20, 2)` `(20, 3)` | "follow up the trail to the tower itself" | -| 4 | `ECRUTEAK_CITY` (north strip) | `maps/EcruteakCity.asm` | warps 4/5 | warp 12 `(37, 7)` | walk east along the fenced strip to Tin Tower | -| 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" - the sage occupies the stair tile | -| 6 | `TIN_TOWER_2F` | `maps/TinTower2F.asm` | warp 2 `(10, 2)` | warp 1 `(10, 14)` | "wild Rattatas will start converging on you" | -| 7 | `TIN_TOWER_3F` | `maps/TinTower3F.asm` | warp 1 `(10, 14)` | warp 2 `(16, 2)` | `FULL_HEAL` at `(3, 14)`, jump-ledge puzzle | -| 8 | `TIN_TOWER_4F` | `maps/TinTower4F.asm` | warp 2 `(16, 2)` | warps 1 `(2, 4)`, 3 `(2, 14)`, 4 `(17, 15)` | `ULTRA_BALL`, `SUPER_POTION`, `ESCAPE_ROPE` | -| 9 | `TIN_TOWER_5F` | `maps/TinTower5F.asm` | warps 2/3/4 | warp 1 `(11, 15)` | `RARE_CANDY` at `(9, 9)` | -| 10 | `TIN_TOWER_6F` | `maps/TinTower6F.asm` | warp 2 `(11, 15)` | warp 1 `(3, 9)` | "go up and you'll get a Max Potion" (see Unresolved) | -| 11 | `TIN_TOWER_7F` | `maps/TinTower7F.asm` | warp 1 `(3, 9)` | warp 2 `(10, 15)`, plus in-floor warp pair 3/4 and warp 5 to 9F | `MAX_REVIVE` at `(16, 1)`; "go through the next two warps" | -| 12 | `TIN_TOWER_8F` | `maps/TinTower8F.asm` | warp 1 `(2, 5)` | warps 2-6 into 9F | `NUGGET`, `MAX_ELIXER`, `FULL_RESTORE` | -| 13 | `TIN_TOWER_9F` | `maps/TinTower9F.asm` | warps 1/2/3/5/6/7 from 7F+8F | warp 4 `(7, 9)` | "cross the planks down, then go up the ladder" | -| 14 | `TIN_TOWER_ROOF` | `maps/TinTowerRoof.asm` | warp 1 `(9, 13)` | back down warp 1 then Escape Rope | level-70 Ho-Oh at `(9, 5)` | - -The walkthrough's closing line ("ride the Magnet Train back to Kanto and fly back -to Pewter City") hands off to the next section; nothing on that hop is resolved -here. - ---- - -## 2. Maps - -### ROUTE_41 - -- Script: `maps/Route41.asm` -- Blocks: `maps/Route41.blk` -- Header: `data/maps/maps.asm:443` -> `TILESET_JOHTO`, `ROUTE`, `LANDMARK_ROUTE_41`, `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:410` -> `map_const ROUTE_41, 25, 27` (group 22 `CIANWOOD`, map 2) -- Connections (`data/maps/attributes.asm:227-229`): north `Route40`, west `CianwoodCity` - -**Warps** (`def_warp_events`) - -| 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 | - -**Coord events**: none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 9 | 35 | `BGEVENT_ITEM` | `Route41HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_41_HIDDEN_MAX_ETHER` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE41_OLIVINE_RIVAL1` | `SPRITE_OLIVINE_RIVAL` | 32 | 6 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermCharlie` | -1 | -| `ROUTE41_OLIVINE_RIVAL2` | `SPRITE_OLIVINE_RIVAL` | 46 | 8 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermGeorge` | -1 | -| `ROUTE41_OLIVINE_RIVAL3` | `SPRITE_OLIVINE_RIVAL` | 20 | 26 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermBerke` | -1 | -| `ROUTE41_OLIVINE_RIVAL4` | `SPRITE_OLIVINE_RIVAL` | 32 | 30 | `SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermKirk` | -1 | -| `ROUTE41_OLIVINE_RIVAL5` | `SPRITE_OLIVINE_RIVAL` | 19 | 46 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermMathew` | -1 | -| `ROUTE41_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 17 | 4 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfKaylee` | -1 | -| `ROUTE41_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 23 | 19 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfSusie` | -1 | -| `ROUTE41_SWIMMER_GIRL3` | `SPRITE_SWIMMER_GIRL` | 27 | 34 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfDenise` | -1 | -| `ROUTE41_SWIMMER_GIRL4` | `SPRITE_SWIMMER_GIRL` | 44 | 28 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (4) | `TrainerSwimmerfKara` | -1 | -| `ROUTE41_SWIMMER_GIRL5` | `SPRITE_SWIMMER_GIRL` | 9 | 50 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (2) | `TrainerSwimmerfWendy` | -1 | - -Note the sight ranges: Kara is `4`, the widest on the route, which is why the -walkthrough uses her as the landmark for the northeast whirlpool ("the whirlpool -is just northwest of where you fought Swimmer Kara"). Her object sits at -`(44, 28)`; the trainer flag is `EVENT_BEAT_SWIMMERF_KARA`, so once beaten she is -still standing there (`event flag` on the row is `-1`, i.e. never masked) and -still walks into your path - the walkthrough's joke about swimmer stamina is -literally the object table. - -**Scripts of interest** - -- `TrainerSwimmerfKara` - `trainer SWIMMERF, KARA, EVENT_BEAT_SWIMMERF_KARA, ...`. - Post-battle it only prints `SwimmerfKaraAfterBattleText` ("I heard roars from - deep inside the ISLANDS"). No flag beyond the beat flag, no gate. - -**Whirlpools**: they are *map blocks*, not events. `data/collision/field_move_blocks.asm:48-55` -gives `WhirlpoolBlockPointers` -> `TILESET_JOHTO` -> `db $07, $36, 0` (facing -block `$07`, replacement `$36`, animation type 0). A bot that wants the whirlpool -coordinates has to scan `maps/Route41.blk` for block id `$07`; the collision -value under it is `COLL_WHIRLPOOL` `$24` (`constants/collision_constants.asm:27`, -tested by `CheckWhirlpoolTile`, `home/map_objects.asm:176-183`). - -**Wild encounters** - -- Surfing, `data/wild/johto_water.asm:170-181`, `def_water_wildmons ROUTE_41`, - encounter rate `6 percent`. Gold: `20 TENTACOOL`, `20 TENTACRUEL`, `20 MANTINE`. - Silver: `20 TENTACOOL`, `20 TENTACRUEL`, `15 TENTACOOL`. -- Fishing: header fish group is `FISHGROUP_OCEAN` (`data/wild/fish.asm:13`, - `.Ocean_Old/.Ocean_Good/.Ocean_Super`). -- The walkthrough's Max Repel advice is aimed squarely at this 6% table. - ---- - -### WHIRL_ISLAND_NE - -- Script: `maps/WhirlIslandNE.asm` -- 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` (group 3 `DUNGEONS`, map 59) -- Connections: none (island interior). - -**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**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `WHIRLISLANDNE_POKE_BALL` | `SPRITE_POKE_BALL` | 11 | 11 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandNEUltraBall` (`47:401e`) | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` | - -`WhirlIslandNEUltraBall` is a bare `itemball ULTRA_BALL`. - ---- - -### WHIRL_ISLAND_NW - -- Script: `maps/WhirlIslandNW.asm` - no scripts at all, warps only -- Blocks: `maps/WhirlIslandNW.blk` -- Header: `data/maps/maps.asm:136`, same row shape as NE -- Dimensions: `constants/map_constants.asm:123` -> `5, 9` (group 3, map 58) - -**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 or objects. - ---- - -### WHIRL_ISLAND_SW - -- Script: `maps/WhirlIslandSW.asm` -- Blocks: `maps/WhirlIslandSW.blk` -- Header: `data/maps/maps.asm:138` -- Dimensions: `constants/map_constants.asm:125` -> `10, 9` (group 3, map 60) - -**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 | - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `WHIRLISLANDSW_POKE_BALL` | `SPRITE_POKE_BALL` | 15 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandSWGuardSpec` (`47:4044`) | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` | - -`itemball GUARD_SPEC` - the walkthrough's "Guard Specs". - -**Wild encounters**: surfing, `data/wild/johto_water.asm:79-84`, -`def_water_wildmons WHIRL_ISLAND_SW`, rate `4 percent`: `20 TENTACOOL`, -`15 HORSEA`, `20 TENTACRUEL`. - ---- - -### WHIRL_ISLAND_SE - -- Script: `maps/WhirlIslandSE.asm` - warps only -- Blocks: `maps/WhirlIslandSE.blk` -- Header: `data/maps/maps.asm:140` -- Dimensions: `constants/map_constants.asm:127` -> `5, 9` (group 3, map 62) - -**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 or objects. No water encounter table. - ---- - -### WHIRL_ISLAND_CAVE - -- Script: `maps/WhirlIslandCave.asm` - warps only -- Blocks: `maps/WhirlIslandCave.blk` -- Header: `data/maps/maps.asm:139` -- Dimensions: `constants/map_constants.asm:126` -> `5, 9` (group 3, map 61) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 5 | `WHIRL_ISLAND_B1F` | 9 | -| 2 | 3 | 13 | `WHIRL_ISLAND_NW` | 4 | - -This is the "you can now head out the door to breathe, but that's pointless" room: -it is a pure corridor between B1F and the NW island. - ---- - -### WHIRL_ISLAND_B1F - -- Script: `maps/WhirlIslandB1F.asm` (`47:4096` `WhirlIslandB1F_MapScripts`) -- Blocks: `maps/WhirlIslandB1F.blk` (`2b:6f61 WhirlIslandB1F_Blocks`) -- Header: `data/maps/maps.asm:141` -- Dimensions: `constants/map_constants.asm:128` -> `20, 18` (group 3, map 63) - -**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` -> `hiddenitem RARE_CANDY, EVENT_WHIRL_ISLAND_B1F_HIDDEN_RARE_CANDY` | -| 36 | 18 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenUltraBall` -> `hiddenitem ULTRA_BALL, EVENT_WHIRL_ISLAND_B1F_HIDDEN_ULTRA_BALL` | -| 2 | 23 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenFullRestore` -> `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` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FFullRestore` | `EVENT_WHIRL_ISLAND_B1F_FULL_RESTORE` | -| `WHIRLISLANDB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 18 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FCarbos` | `EVENT_WHIRL_ISLAND_B1F_CARBOS` | -| `WHIRLISLANDB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 33 | 23 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FCalcium` | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` | -| `WHIRLISLANDB1F_POKE_BALL4` | `SPRITE_POKE_BALL` | 17 | 8 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FNugget` | `EVENT_WHIRL_ISLAND_B1F_NUGGET` | -| `WHIRLISLANDB1F_POKE_BALL5` | `SPRITE_POKE_BALL` | 19 | 26 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FEscapeRope` | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` | -| `WHIRLISLANDB1F_BOULDER` | `SPRITE_BOULDER` | 23 | 26 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `WhirlIslandB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 | - -The boulder at `(23, 26)` sits two tiles east of the Escape Rope ball and is the -only Strength object in the dungeon. The walkthrough never mentions it. - -**Wild encounters**: grass/cave-floor, `data/wild/johto_grass.asm:1213-1239`, -`def_grass_wildmons WHIRL_ISLAND_B1F`, rates `6 percent` for all three times of -day, and the morn/day/nite slot lists are identical: -`23 KRABBY`, `24 ZUBAT`, `25 KRABBY`, `23 SEEL`, `24 GOLBAT`, `25 SEEL`, `25 SEEL`. -No water table for B1F. - ---- - -### WHIRL_ISLAND_B2F - -- Script: `maps/WhirlIslandB2F.asm` -- Blocks: `maps/WhirlIslandB2F.blk` -- Header: `data/maps/maps.asm:142` -- Dimensions: `constants/map_constants.asm:129` -> `10, 18` (group 3, map 64) - -**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**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `WHIRLISLANDB2F_POKE_BALL1` | `SPRITE_POKE_BALL` | 10 | 11 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FFullRestore` | `EVENT_WHIRL_ISLAND_B2F_FULL_RESTORE` | -| `WHIRLISLANDB2F_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 4 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FMaxRevive` | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` | -| `WHIRLISLANDB2F_POKE_BALL3` | `SPRITE_POKE_BALL` | 5 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FMaxElixer` | `EVENT_WHIRL_ISLAND_B2F_MAX_ELIXER` | - -`(6, 4)` is the single `MAX_REVIVE` in the whole Whirl Islands (see Unresolved -for the walkthrough's "Max Revive x2"). - -**Wild encounters** - -- Floor: `data/wild/johto_grass.asm:1241-1267`, rate `6 percent`, identical to B1F - (`23 KRABBY / 24 ZUBAT / 25 KRABBY / 23 SEEL / 24 GOLBAT / 25 SEEL / 25 SEEL`). -- Water: `data/wild/johto_water.asm:86-91`, rate `4 percent`: `15 HORSEA`, - `20 HORSEA`, `20 TENTACRUEL`. - -The "two items on the cliff but you can't reach those" line matches the two balls -on the west shelf (`(5, 12)` Max Elixer and `(10, 11)` Full Restore are reachable -from the other side of the floor; exactly which two the FAQ means is a `.blk` -question, see Unresolved). - ---- - -### WHIRL_ISLAND_LUGIA_CHAMBER - -- Script: `maps/WhirlIslandLugiaChamber.asm` (`47:418c` callback, `47:41a0 Lugia`) -- Blocks: `maps/WhirlIslandLugiaChamber.blk` (`2b:717d`) -- Header: `data/maps/maps.asm:143` -> `TILESET_DARK_CAVE`, `CAVE`, `LANDMARK_WHIRL_ISLANDS`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, `FISHGROUP_WHIRL_ISLANDS` -- Dimensions: `constants/map_constants.asm:130` -> `10, 9` (group 3, map 65) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 13 | `WHIRL_ISLAND_B2F` | 3 | - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `WHIRLISLANDLUGIACHAMBER_LUGIA` | `SPRITE_LUGIA` | 9 | 5 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_BLUE` | `Lugia` | `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` | - -**Scripts of interest** - -- `WhirlIslandLugiaChamberLugiaCallback` - `callback MAPCALLBACK_OBJECTS`. - `checkevent EVENT_FOUGHT_LUGIA / iftrue .NoAppear`, then - `checkitem SILVER_WING / iftrue .Appear`, else `.NoAppear`. So Lugia is on the - map **only while the SILVER_WING is in the bag and the fight has not happened**. - The wing is a `checkitem`, not an event - selling or storing it makes Lugia - vanish again. -- `Lugia` (`47:41a0`) - `faceplayer`, `opentext`, `writetext LugiaText` - ("Gyaaas!"), `cry LUGIA`, `pause 15`, `closetext`, - **`setevent EVENT_FOUGHT_LUGIA`**, `checkver`, `iftrue .Silver`. - - Gold arm: `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM`, - `loadwildmon LUGIA, 70`, `startbattle`, - `disappear WHIRLISLANDLUGIACHAMBER_LUGIA`, `reloadmapafterbattle`, `end`. - - `.Silver`: identical but `loadwildmon LUGIA, 40`. - - `EVENT_FOUGHT_LUGIA` is set **before** the battle, so fleeing, fainting or - knocking it out all consume the encounter. This is exactly why the - walkthrough insists on saving first: there is no re-arm path in the asm. - - `BATTLETYPE_FORCEITEM` (`constants/battle_constants.asm:101`) makes - `InitEnemyMon`'s `.WildItem` arm hand over `wBaseItem1` unconditionally - (`engine/battle/core.asm:5765-5773`). Lugia's `data/pokemon/base_stats/lugia.asm` - row is `db NO_ITEM, NO_ITEM`, so it holds nothing - the forced-item type is - inherited boilerplate here. - -**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. Once set, Lugia never reappears | -| `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` | `constants/event_flags.asm:1247` | the object row's mask flag, driven by `appear`/`disappear` | object visibility only | -| `EVENT_GOT_SILVER_WING` | `constants/event_flags.asm:130` | set by `PewterCityGrampsScript` (Gold) / `RadioTower5F` (Silver) | prerequisite for holding `SILVER_WING` | - -**Wild encounters** - -- Floor: `data/wild/johto_grass.asm:1269-1295`, rate `6 percent`: - `24 KRABBY`, `25 ZUBAT`, `26 KRABBY`, `24 SEEL`, `25 GOLBAT`, `26 SEEL`, `26 SEEL`. -- Water: `data/wild/johto_water.asm:93-98`, rate `4 percent`: `20 HORSEA`, - `20 TENTACRUEL`, `20 SEADRA`. This is the only Seadra slot in the dungeon, and - it is on the water directly in front of Lugia. - -**Catch math** (`data/pokemon/base_stats/lugia.asm`): `db 3 ; catch rate`. -`UltraBallMultiplier` doubles it to 6; `HeavyBallMultiplier` -(`engine/items/item_effects.asm:752`) adds a flat offset by dex weight, and -Lugia's dex weight is `4760` tenths of a pound = 216 kg -(`data/pokemon/dex_entries/gold/lugia.asm`), which lands in the `+20` bracket -> -effective 23. Thard_Verad's tip in the FAQ is correct and is worth ~4x the Ultra -Ball rate. - ---- - -### ECRUTEAK_CITY (transit only) - -- Script: `maps/EcruteakCity.asm` -- Header: `data/maps/maps.asm:173` -> `TILESET_JOHTO`, `TOWN`, `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:159` -> `20, 18` (group 4, map 9) - -**Warps** (only the rows this section uses) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 3 | 18 | 11 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 1 | -| 4 | 20 | 2 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 1 | -| 5 | 20 | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 2 | -| 12 | 37 | 7 | `TIN_TOWER_1F` | 1 | -| 13 | 5 | 5 | `BURNED_TOWER_1F` | 1 | - -There is a `bg_event 38, 10, BGEVENT_READ, TinTowerSign` next to the tower door. - ---- - -### ECRUTEAK_TIN_TOWER_ENTRANCE - -- Script: `maps/EcruteakTinTowerEntrance.asm` -- Blocks: `maps/EcruteakTinTowerEntrance.blk` -- 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` -> `10, 9` (group 4, map 1) - -**Warps** - -| 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 a self-referential pair: the map holds two disconnected -corridors and the "walk up the stairs" is a same-map teleport from `(5, 3)` to -`(17, 15)`. - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (0) | 4 | 7 | `EcruteakTinTowerEntranceSageBlocksLeft` (`52:400c`) | sage 2 side-steps LEFT, sage 1 is `moveobject`d to `(4, 6)` and `appear`ed, sage 2 `disappear`s | -| `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (0) | 5 | 7 | `EcruteakTinTowerEntranceSageBlocksRight` (`52:4021`) | mirror image, sage 2 ends at `(5, 6)` | - -Scene ids come from the `scene_script` macro's inline `const` block -(`macros/scripts/maps.asm:12-33`): `SAGE_BLOCKS` = 0, `NOOP` = 1. Both scene -bodies are `end`; the scene id exists only to arm/disarm the two coord events. - -**BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ECRUTEAKTINTOWERENTRANCE_SAGE1` | `SPRITE_SAGE` | 4 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` (`52:4037`) | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` | -| `ECRUTEAKTINTOWERENTRANCE_SAGE2` | `SPRITE_SAGE` | 5 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_RIGHT` | -| `ECRUTEAKTINTOWERENTRANCE_SAGE3` | `SPRITE_SAGE` | 6 | 9 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceWanderingSageScript` (`52:404b`) | -1 | -| `ECRUTEAKTINTOWERENTRANCE_GRAMPS` | `SPRITE_GRAMPS` | 3 | 11 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceGrampsScript` | -1 | - -Object rows are masked when their flag is **set** (`CheckObjectFlag`, -`engine/overworld/map_objects_2.asm:32-61`: `EventFlagAction`/`CHECK_FLAG` -non-zero -> `.masked`). `InitializeEventsScript` -(`engine/events/std_scripts.asm:438`, line 533) does -`setevent EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` at new game, so on a fresh -save only SAGE2 at `(5, 6)` is standing there. - -**Scripts of interest** - -- `EcruteakTinTowerEntranceSageScript` - `faceplayer`, `opentext`, - `checkflag ENGINE_FOGBADGE`, `iftrue .BlockPassage_GotFogBadge`. Both arms are - pure text (`EcruteakTinTowerEntranceSageText` vs `..._GotFogBadge`); **the - script never opens the path**. -- The actual unblock is `maps/EcruteakGym.asm:34`: - `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP`, - run in the same block as `setflag ENGINE_FOGBADGE` after beating Morty. With - the scene at `NOOP` the two coord events no longer match and the sage never - steps into the doorway. By section 25 the player has all 16 badges, so this is - already open - the walkthrough's "talk to the man and he'll let you through" is - flavour text, not a required action. -- `EcruteakTinTowerEntranceWanderingSageScript` - `checkevent EVENT_GOT_RAINBOW_WING`; - the post-wing line is "The TIN TOWER shook! A #MON must have returned to the - top!". This is the only in-map confirmation that Ho-Oh is now spawnable. - ---- - -### ECRUTEAK_TIN_TOWER_BACK_ENTRANCE - -- Script: `maps/EcruteakTinTowerBackEntrance.asm` - warps only, no scripts -- Blocks: `maps/EcruteakTinTowerBackEntrance.blk` -- Header: `data/maps/maps.asm:166` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`, `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:152` -> `4, 4` (group 4, map 2) - -**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 | - ---- - -### TIN_TOWER_1F - -- Script: `maps/TinTower1F.asm` (`42:4b1d TinTowerSageScript`) -- Blocks: `maps/TinTower1F.blk` -- Header: `data/maps/maps.asm:82` -> `TILESET_TOWER`, `DUNGEON`, `LANDMARK_TIN_TOWER`, `MUSIC_TIN_TOWER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:69` -> `10, 9` (group 3, map 4) - -**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**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `TINTOWER1F_SAGE` | `SPRITE_SAGE` | 10 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TinTowerSageScript` | `EVENT_TEAM_ROCKET_DISBANDED` | - -**This is the real gate for the whole Silver branch.** The sage's coordinates are -`(10, 2)`, byte-for-byte the coordinates of warp 3. While the object is visible -the stair tile is occupied and 2F is unreachable. The object is masked - i.e. the -sage disappears - when `EVENT_TEAM_ROCKET_DISBANDED` -(`constants/event_flags.asm:1283`) is **set**, and that flag is only ever set by -the two Rainbow Wing hand-offs: - -- `maps/RadioTower5F.asm:124-130` (Gold): `verbosegiveitem RAINBOW_WING` (124), - `setevent EVENT_GOT_RAINBOW_WING` (129), `setevent EVENT_TEAM_ROCKET_DISBANDED` (130). -- `maps/PewterCity.asm:42-51` `.RainbowWing` (Silver): the gramps gives - `RAINBOW_WING` (49), `setevent EVENT_GOT_RAINBOW_WING` (50), - `setevent EVENT_TEAM_ROCKET_DISBANDED` (51). - -So the walkthrough's "another bald man will let you through because you have the -beautiful Rainbow Wing" is literally true, but the mechanism is a masked object -keyed on `EVENT_TEAM_ROCKET_DISBANDED`, not a `checkitem RAINBOW_WING`. - -**Wild encounters**: none - `TIN_TOWER_1F` has no `def_grass_wildmons` row -(`data/wild/johto_grass.asm` starts the tower at `TIN_TOWER_2F`, line 61). - ---- - -### TIN_TOWER_2F - -- Script: `maps/TinTower2F.asm` - warps only -- Blocks: `maps/TinTower2F.blk` -- Header: `data/maps/maps.asm:83`; Dimensions `constants/map_constants.asm:70` -> `10, 9` (group 3, map 5) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 10 | 14 | `TIN_TOWER_3F` | 1 | -| 2 | 10 | 2 | `TIN_TOWER_1F` | 3 | - -**Wild encounters**: `data/wild/johto_grass.asm:61-87`, -`def_grass_wildmons TIN_TOWER_2F`, rates `2 percent` morn/day/nite. - -- morn/day: `20 RATTATA`, `21 RATTATA`, `22 RATTATA`, `22 RATTATA`, `23 RATTATA`, `24 RATTATA`, `24 RATTATA` -- nite: `20 GASTLY`, `21 GASTLY`, `22 GASTLY`, `22 RATTATA`, `23 RATTATA`, `24 RATTATA`, `24 RATTATA` - -Every floor 2F..9F carries the identical table (checked 2F at line 61 and 9F at -line 257). The walkthrough only lists Rattata; Gastly at night is real. - ---- - -### TIN_TOWER_3F - -- Script: `maps/TinTower3F.asm`; Blocks `maps/TinTower3F.blk` -- Header: `data/maps/maps.asm:84`; Dimensions `constants/map_constants.asm:71` -> `10, 9` (group 3, map 6) - -**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 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower3FFullHeal` | `EVENT_TIN_TOWER_3F_FULL_HEAL` | - -Matches the walkthrough's "hop left for a Full Heal". - ---- - -### TIN_TOWER_4F - -- Script: `maps/TinTower4F.asm`; Blocks `maps/TinTower4F.blk` -- Header: `data/maps/maps.asm:85`; Dimensions `constants/map_constants.asm:72` -> `10, 9` (group 3, map 7) - -**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` -> `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` | `OBJECTTYPE_ITEMBALL` | `TinTower4FUltraBall` (`42:4cc1`) | `EVENT_TIN_TOWER_4F_ULTRA_BALL` | -| `TINTOWER4F_POKE_BALL2` | `SPRITE_POKE_BALL` | 17 | 14 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower4FSuperPotion` | `EVENT_TIN_TOWER_4F_SUPER_POTION` | -| `TINTOWER4F_POKE_BALL3` | `SPRITE_POKE_BALL` | 2 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower4FEscapeRope` | `EVENT_TIN_TOWER_4F_ESCAPE_ROPE` | - ---- - -### TIN_TOWER_5F - -- Script: `maps/TinTower5F.asm`; Blocks `maps/TinTower5F.blk` -- Header: `data/maps/maps.asm:86`; Dimensions `constants/map_constants.asm:73` -> `10, 9` (group 3, map 8) - -**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` -> `hiddenitem FULL_RESTORE, EVENT_TIN_TOWER_5F_HIDDEN_FULL_RESTORE` | -| 3 | 15 | `BGEVENT_ITEM` | `TinTower5FHiddenCarbos` -> `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` | `OBJECTTYPE_ITEMBALL` | `TinTower5FRareCandy` (`42:4d12`) | `EVENT_TIN_TOWER_5F_RARE_CANDY` | - ---- - -### TIN_TOWER_6F - -- Script: `maps/TinTower6F.asm` - warps only, no items, no objects -- Blocks: `maps/TinTower6F.blk` -- Header: `data/maps/maps.asm:87`; Dimensions `constants/map_constants.asm:74` -> `10, 9` (group 3, map 9) - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 9 | `TIN_TOWER_7F` | 1 | -| 2 | 11 | 15 | `TIN_TOWER_5F` | 1 | - -The walkthrough's "go up and you'll get a Max Potion ... left across the upper -bridge to get a Full Heal" floor has **no item balls and no bg events at all** in -pokegold. See Unresolved. - ---- - -### TIN_TOWER_7F - -- Script: `maps/TinTower7F.asm`; Blocks `maps/TinTower7F.blk` -- Header: `data/maps/maps.asm:88`; Dimensions `constants/map_constants.asm:75` -> `10, 9` (group 3, map 10) - -**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 the in-floor pair the walkthrough calls "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` | `OBJECTTYPE_ITEMBALL` | `TinTower7FMaxRevive` | `EVENT_TIN_TOWER_7F_MAX_REVIVE` | - ---- - -### TIN_TOWER_8F - -- Script: `maps/TinTower8F.asm`; Blocks `maps/TinTower8F.blk` -- Header: `data/maps/maps.asm:89`; Dimensions `constants/map_constants.asm:76` -> `10, 9` (group 3, map 11) - -**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` | `OBJECTTYPE_ITEMBALL` | `TinTower8FNugget` | `EVENT_TIN_TOWER_8F_NUGGET` | -| `TINTOWER8F_POKE_BALL2` | `SPRITE_POKE_BALL` | 11 | 6 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower8FMaxElixer` | `EVENT_TIN_TOWER_8F_MAX_ELIXER` | -| `TINTOWER8F_POKE_BALL3` | `SPRITE_POKE_BALL` | 3 | 1 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower8FFullRestore` | `EVENT_TIN_TOWER_8F_FULL_RESTORE` | - ---- - -### TIN_TOWER_9F - -- Script: `maps/TinTower9F.asm` (`42:4de0 TinTower9F_MapScripts`) - no callbacks, - no objects; carries two unreferenced texts `TinTower9FUnusedHoOhText` / - `TinTower9FUnusedLugiaText` -- Blocks: `maps/TinTower9F.blk` (`2b:625f`) -- Header: `data/maps/maps.asm:90`; Dimensions `constants/map_constants.asm:77` -> `10, 9` (group 3, map 12) - -**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 | - -Warp 4 at `(7, 9)` is the only exit to the roof. - ---- - -### TIN_TOWER_ROOF - -- Script: `maps/TinTowerRoof.asm` (`5b:68ff` callback, `5b:6913 TinTowerHoOh`) -- Blocks: `maps/TinTowerRoof.blk` (`2b:62b9`) -- Header: `data/maps/maps.asm:344` -> `TILESET_TOWER`, **`ROUTE`**, `LANDMARK_TIN_TOWER`, `MUSIC_TIN_TOWER`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:318` -> `10, 9`, and note the group: - `TIN_TOWER_ROOF` is map 12 of **group 15 (`FAST_SHIP`)**, not group 3 with the - rest of the tower. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 13 | `TIN_TOWER_9F` | 4 | - -**Coord events**: none. **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` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_RED` | `TinTowerHoOh` | `EVENT_TIN_TOWER_ROOF_HO_OH` | - -**Scripts of interest** - -- `TinTowerRoofHoOhCallback` - `callback MAPCALLBACK_OBJECTS`: - `checkevent EVENT_FOUGHT_HO_OH / iftrue .NoAppear`, then - `checkitem RAINBOW_WING / iftrue .Appear`. Same shape as Lugia's: the wing must - be **in the bag**, not merely once-received. -- `TinTowerHoOh` (`5b:6913`) - text "Shaoooh!", `cry HO_OH`, `pause 15`, - `setevent EVENT_FOUGHT_HO_OH` **before** the fight, `checkver / iftrue .Silver`. - - Gold arm: `loadwildmon HO_OH, 40`. - - `.Silver`: `loadwildmon HO_OH, 70` - the level the walkthrough quotes. - - Both arms run `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM` first, and - Ho-Oh's base row is `db SACRED_ASH, SACRED_ASH` - (`data/pokemon/base_stats/ho_oh.asm:9`), so **Ho-Oh always holds a SACRED_ASH**. - That is a guaranteed item a bot should strip before/after the catch. - -**Escape Rope caveat**: `EscapeRopeFunction` -> `.CheckCanDig` -(`engine/events/overworld.asm:724-754`) only succeeds when -`GetMapEnvironment` is `CAVE` or `DUNGEON`. `TIN_TOWER_ROOF` is `ROUTE`, so the -walkthrough is right that you must go back down to 9F first. Every Whirl Island -map including `WHIRL_ISLAND_LUGIA_CHAMBER` is `CAVE`, so the rope works on the -spot there. - -**Catch math**: Ho-Oh's catch rate is also `3` -(`data/pokemon/base_stats/ho_oh.asm:7`). Its dex weight is `4390` -(`data/pokemon/dex_entries/gold/ho_oh.asm`) = 199 kg, which falls in the -`HeavyBallMultiplier` "add 0" bracket - so unlike Lugia, a Heavy Ball is *worse* -than an Ultra Ball on Ho-Oh. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Reaching Route 41 at all | `SurfFunction.TrySurf`, `engine/events/overworld.asm:322` (badge test at `:340`) | `ENGINE_FOGBADGE` + a party member that knows `SURF` | Fog Badge (Morty) | -| Whirlpools guarding the Whirl Islands | `WhirlpoolFunction` `engine/events/overworld.asm:1061` (badge test at `:1077`) and `TryWhirlpoolOW` `:1167` (badge test at `:1171`) | `ENGINE_GLACIERBADGE` + `WHIRLPOOL` in the party | Glacier Badge (Pryce, badge 7) - **not** eight badges, see Unresolved | -| Darkness inside every Whirl Island map | `FlashFunction.CheckUseFlash`, `engine/events/overworld.asm:271` | `ENGINE_ZEPHYRBADGE` + `FLASH`, and `wTimeOfDayPalset == DARKNESS_PALSET` (the map header's `PALETTE_DARK`) | Zephyr Badge. Flash is *refused* on any non-dark map, so a bot must only try it inside | -| Climbing back up the B2F waterfall | `WaterfallFunction` `engine/events/overworld.asm:611` (badge test at `:618`) and `TryWaterfallOW` `:683` (badge test at `:687`); direction check `CheckMapCanWaterfall` (must face UP, `wTileUp` is a waterfall tile) | `ENGINE_RISINGBADGE` + `WATERFALL` | Rising Badge (Clair). Descending needs nothing - only the UP direction is gated | -| B1F boulder at `(23, 26)` | `WhirlIslandB1FBoulder` -> `jumpstd StrengthBoulderScript` | `STRENGTH` + `ENGINE_PLAINBADGE` | optional; not on the Lugia path | -| Lugia not on the map | `WhirlIslandLugiaChamberLugiaCallback` | `checkitem SILVER_WING` true **and** `EVENT_FOUGHT_LUGIA` clear | `EVENT_GOT_SILVER_WING` via `PewterCityGrampsScript` (Gold) or `RadioTower5F` (Silver) - and keep the wing in the bag | -| Ho-Oh not on the map | `TinTowerRoofHoOhCallback` | `checkitem RAINBOW_WING` true **and** `EVENT_FOUGHT_HO_OH` clear | `EVENT_GOT_RAINBOW_WING` via `RadioTower5F` (Gold) or `PewterCityGrampsScript` `.RainbowWing` (Silver) | -| Tin Tower stairs to 2F | `TinTower1F` object row: sage stands on `(10, 2)`, masked by `EVENT_TEAM_ROCKET_DISBANDED` | flag must be **set** | `setevent EVENT_TEAM_ROCKET_DISBANDED` in `maps/RadioTower5F.asm:130` or `maps/PewterCity.asm:51` | -| Tin Tower entrance corridor | `coord_event 4, 7` / `5, 7` under `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` | scene must be `NOOP` | `maps/EcruteakGym.asm:34` `setmapscene ... SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP` on the Fog Badge | -| One-shot mascot | `Lugia` / `TinTowerHoOh` set `EVENT_FOUGHT_LUGIA` / `EVENT_FOUGHT_HO_OH` **before** `startbattle` | n/a | none. Save-before-fight is the only recovery | - ---- - -## 4. Bot checklist - -**Preconditions for either branch**: 8 Johto badges (Zephyr, Fog, Plain, Glacier, -Rising in particular), party moves `SURF`, `WHIRLPOOL`, `FLASH`, `WATERFALL` -(`STRENGTH` optional), the version-opposite wing in the bag, a stack of -`ULTRA_BALL` (`HEAVY_BALL` for Lugia), and a save immediately before the mascot. - -**Gold branch - Lugia** - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | any | Olivine City | fly | `ENGINE_FLYPOINT_OLIVINE` | on `OLIVINE_CITY` | -| 2 | `OLIVINE_CITY` | west edge | walk, then surf | `ENGINE_FOGBADGE` + `SURF` | on `ROUTE_40` | -| 3 | `ROUTE_40` | south edge | surf | - | on `ROUTE_41` | -| 4 | `ROUTE_41` | any block id `$07` (`TILESET_JOHTO` whirlpool) | face it, press A -> `TryWhirlpoolOW` | `ENGINE_GLACIERBADGE` + `WHIRLPOOL` | block replaced with `$36` for this map session only (`DisappearWhirlpool` writes the map buffer, not the save) | -| 5 | `ROUTE_41` | `(36, 19)` warp 2 | walk onto | whirlpools cleared | on `WHIRL_ISLAND_NE` at `(3, 13)` | -| 6 | `WHIRL_ISLAND_NE` | menu | use `FLASH` | `ENGINE_ZEPHYRBADGE`, map palette `PALETTE_DARK` | cave lit | -| 7 | `WHIRL_ISLAND_NE` | object `WHIRLISLANDNE_POKE_BALL` `(11, 11)` | walk onto / A | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` clear | `ULTRA_BALL` in bag, flag set | -| 8 | `WHIRL_ISLAND_NE` | warp 2 `(17, 3)` (or warp 3 `(13, 11)`) | walk onto | - | `WHIRL_ISLAND_B1F` `(35, 3)` / `(29, 9)` | -| 9 | `WHIRL_ISLAND_B1F` | `(19, 26)` | pick up | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` clear | `ESCAPE_ROPE` in bag | -| 10 | `WHIRL_ISLAND_B1F` | `(33, 23)` | pick up | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` clear | `CALCIUM` in bag | -| 11 | `WHIRL_ISLAND_B1F` | optional `(30, 4)`, `(36, 18)`, `(2, 23)` | face + A (hidden) | matching `EVENT_..._HIDDEN_*` clear | Rare Candy / Ultra Ball / Full Restore | -| 12 | `WHIRL_ISLAND_B1F` | warp 4 `(9, 31)` or warp 5 `(23, 31)` | walk onto | - | `WHIRL_ISLAND_SW` | -| 13 | `WHIRL_ISLAND_SW` | `(15, 2)` | pick up | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` clear | `GUARD_SPEC` in bag | -| 14 | `WHIRL_ISLAND_B1F` | warp 7 `(25, 21)` | walk onto | - | `WHIRL_ISLAND_B2F` `(11, 5)` | -| 15 | `WHIRL_ISLAND_B2F` | `(6, 4)` | pick up | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` clear | `MAX_REVIVE` in bag | -| 16 | `WHIRL_ISLAND_B2F` | waterfall block, moving DOWN | surf south over it | `SURF` only (no badge for descent) | lower B2F pool | -| 17 | `WHIRL_ISLAND_B2F` | warp 3 `(7, 25)` | walk onto | - | `WHIRL_ISLAND_LUGIA_CHAMBER` `(9, 13)` | -| 18 | `WHIRL_ISLAND_LUGIA_CHAMBER` | - | **SAVE** | - | recoverable | -| 19 | `WHIRL_ISLAND_LUGIA_CHAMBER` | object at `(9, 5)` | surf north, A | `SILVER_WING` in bag, `EVENT_FOUGHT_LUGIA` clear | `EVENT_FOUGHT_LUGIA` set, wild Lugia Lv70 (Gold) / Lv40 (Silver) | -| 20 | battle | - | weaken, status, throw balls | catch rate 3 (Ultra 6, Heavy 23) | Lugia caught, `disappear` + `reloadmapafterbattle` | -| 21 | `WHIRL_ISLAND_LUGIA_CHAMBER` | bag | use `ESCAPE_ROPE` | environment `CAVE` | back at last Pokecenter spawn | - -**Silver branch - Ho-Oh** - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | any | Ecruteak City | fly | `ENGINE_FLYPOINT_ECRUTEAK` | on `ECRUTEAK_CITY` | -| 2 | `ECRUTEAK_CITY` | warp 3 `(18, 11)` | walk onto | - | `ECRUTEAK_TIN_TOWER_ENTRANCE` `(4, 17)` | -| 3 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | `(4, 7)` / `(5, 7)` | walk through | map scene must be `NOOP` (Fog Badge) | no coord event fires | -| 4 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | warp 3 `(5, 3)` | walk onto | - | same map, `(17, 15)` | -| 5 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | warp 5 `(17, 3)` | walk onto | - | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` `(2, 4)` | -| 6 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | warp 1 `(7, 4)` | walk onto | - | `ECRUTEAK_CITY` `(20, 2)` | -| 7 | `ECRUTEAK_CITY` | warp 12 `(37, 7)` | walk east then onto | - | `TIN_TOWER_1F` `(9, 15)` | -| 8 | `TIN_TOWER_1F` | warp 3 `(10, 2)` | walk onto | `EVENT_TEAM_ROCKET_DISBANDED` **set** (sage masked) | `TIN_TOWER_2F` `(10, 2)` | -| 9 | `TIN_TOWER_2F` | warp 1 `(10, 14)` | walk onto | - | `TIN_TOWER_3F` | -| 10 | `TIN_TOWER_3F` | `(3, 14)` then warp 2 `(16, 2)` | pick up, walk onto | `EVENT_TIN_TOWER_3F_FULL_HEAL` clear | `FULL_HEAL`; `TIN_TOWER_4F` | -| 11 | `TIN_TOWER_4F` | `(14, 10)`, `(17, 14)`, `(2, 12)`, hidden `(11, 6)` | pick up | matching flags clear | Ultra Ball, Super Potion, Escape Rope, hidden Max Potion | -| 12 | `TIN_TOWER_4F` | warp 1 `(2, 4)` / 3 `(2, 14)` / 4 `(17, 15)` | walk onto | - | `TIN_TOWER_5F` | -| 13 | `TIN_TOWER_5F` | `(9, 9)`, hidden `(16, 14)` and `(3, 15)` | pick up | matching flags clear | Rare Candy, Full Restore, Carbos | -| 14 | `TIN_TOWER_5F` | warp 1 `(11, 15)` | walk onto | - | `TIN_TOWER_6F` `(11, 15)` | -| 15 | `TIN_TOWER_6F` | warp 1 `(3, 9)` | walk onto | - | `TIN_TOWER_7F` `(3, 9)` | -| 16 | `TIN_TOWER_7F` | `(16, 1)`; warps 3 `(12, 7)` / 4 `(8, 3)` | pick up, hop the pair | `EVENT_TIN_TOWER_7F_MAX_REVIVE` clear | `MAX_REVIVE` | -| 17 | `TIN_TOWER_7F` | warp 2 `(10, 15)` | walk onto | - | `TIN_TOWER_8F` `(2, 5)` | -| 18 | `TIN_TOWER_8F` | `(7, 13)`, `(11, 6)`, `(3, 1)` | pick up | matching flags clear | Nugget, Max Elixer, Full Restore | -| 19 | `TIN_TOWER_8F` | warp 2 `(2, 11)` (or 3/4/5/6) | walk onto | - | `TIN_TOWER_9F` | -| 20 | `TIN_TOWER_9F` | warp 4 `(7, 9)` | walk onto | - | `TIN_TOWER_ROOF` `(9, 13)` | -| 21 | `TIN_TOWER_ROOF` | - | **SAVE** | - | recoverable | -| 22 | `TIN_TOWER_ROOF` | object at `(9, 5)` | walk up, A | `RAINBOW_WING` in bag, `EVENT_FOUGHT_HO_OH` clear | `EVENT_FOUGHT_HO_OH` set, wild Ho-Oh Lv70 (Silver) / Lv40 (Gold) holding `SACRED_ASH` | -| 23 | battle | - | weaken, status, Ultra Balls | catch rate 3; Heavy Ball gives no bonus here | Ho-Oh caught | -| 24 | `TIN_TOWER_9F` | warp 1 on the roof, then bag | descend, use `ESCAPE_ROPE` | roof is `ROUTE` environment - rope fails there | back at spawn | - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers, warps, coord/bg/object events for all of these maps | `src/import/RomExtractorGen2.lua` (walks all 26 map groups, `MAP_GROUP_COUNT` at line 47), consumed by `src/world/gen2/Map.lua` / `World.lua` | implemented (generic - no map-specific code, so these maps come in with everything else) | -| `MAPCALLBACK_OBJECTS` (the Lugia / Ho-Oh appear-check) | `src/world/gen2/World.lua:5700` `runMapCallback("MAPCALLBACK_OBJECTS")`; regression driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| Object masking by event flag (`CheckObjectFlag`) - the Tin Tower 1F sage, every item ball | `src/world/gen2/World.lua:5106,5122` `events:objectVisible(obj.eventFlag)` | implemented | -| `checkver` version split (Lv70 vs Lv40 mascot) | `src/script/gen2/Vm.lua:774-783`; opcode table `src/script/gen2/Opcodes.lua:29` | implemented (comments even cite `WhirlIslandLugiaChamber` by name) | -| `loadwildmon` / `startbattle` / `reloadmapafterbattle` | `src/script/gen2/Opcodes.lua:98-101`, `src/script/gen2/Vm.lua:817-896` | implemented | -| `BATTLETYPE_FORCEITEM` -> Ho-Oh's guaranteed `SACRED_ASH` | `src/world/gen2/World.lua:107-111` and `:4571-4579` | implemented | -| Whirlpool field move, badge gate and block replacement | `src/world/gen2/FieldMoves.lua:109` (`WHIRLPOOL = "GLACIER"`), `:219` `WHIRLPOOL_BLOCKS`, `:247` `somethingToWhirlpool`, `:542` `whirlpoolFromMenu`, `:626` `tryWhirlpoolOW` | implemented | -| Flash + `PALETTE_DARK` darkness | `src/world/gen2/FieldMoves.lua:105,468`; `src/world/gen2/Palettes.lua:53,75-103` | implemented | -| Waterfall (up-only gate, `RISINGBADGE`) | `src/world/gen2/FieldMoves.lua:110,258,646` | implemented | -| Hidden items (`hiddenitem` bg events) | `src/world/gen2/HiddenItems.lua`, `src/world/gen2/World.lua:1373,5286` | implemented | -| Catch rate formula incl. the two cart bugs | `src/battle/gen2/Catching.lua:1-90` | implemented | -| **Heavy Ball weight bonus** (the FAQ's Lugia tip) | `src/battle/gen2/Catching.lua:36` - `HEAVY_BALL = 1`, a flat multiplier with a comment saying the specialty balls "key off conditions the caller supplies", and no dex-weight lookup anywhere | **missing** - a Heavy Ball currently behaves as a plain Poke Ball on Lugia | -| Magnet Train hop that opens the section | `src/core/gen2/MagnetTrain.lua` | implemented (not re-verified for this section) | -| Escape Rope environment check (`CAVE`/`DUNGEON` only, roof excluded) | not located in `src/world/gen2/` or `src/battle/gen2/` by grep | **unverified** - could not find an `EscapeRopeFunction` equivalent; a bot should not assume the roof refusal is modelled | -| Hand-ported scripts for these specific maps | none - grep for `whirl`/`tintower`/`lugia`/`hooh` across `src/` returns only battle-anim and title-screen hits | not applicable (all script bodies are extracted, run through `src/script/gen2/Vm.lua`) | - ---- - -## 6. Unresolved / verify by hand - -1. **"You'll need eight badges in order to use Whirlpool."** The asm gates - Whirlpool on `ENGINE_GLACIERBADGE` alone (`engine/events/overworld.asm:1077` - and `:1171`), i.e. seven badges. Eight is wrong, though harmless in practice - for a post-game section. -2. **Whirl Islands item list.** The FAQ lists Calcium, Escape Rope, Guard Specs, - Max Revive x2, Ultra Ball. The asm has *one* Max Revive in the entire dungeon - (`WhirlIslandB2FMaxRevive` at B2F `(6, 4)`) and additionally a `FULL_RESTORE`, - `CARBOS`, `NUGGET` on B1F, a second `FULL_RESTORE` and a `MAX_ELIXER` on B2F, - plus three hidden items on B1F that the FAQ never mentions. The walkthrough's - two "grab the Max Revive" beats cannot both be the B2F ball; the first one is - unlocatable. -3. **Tin Tower item list.** The FAQ names a PP Up, an HP Up, a Max Potion item - ball, and a second Full Heal. None exist in pokegold: there is no `PP_UP` or - `HP_UP` anywhere in `maps/TinTower*.asm`, the only Max Potion is the *hidden* - `TinTower4FHiddenMaxPotion` at `(11, 6)`, and the only Full Heal is - `TinTower3FFullHeal`. Conversely the FAQ omits `SUPER_POTION` (4F), - `NUGGET`/`FULL_RESTORE` (8F) and the three hidden items on 4F/5F. The list - looks like it was written against a different version of the game. -4. **"On the next level, go up and you'll get a Max Potion ... Full Heal"** places - two items on the 6F-shaped floor; `maps/TinTower6F.asm` has an empty - `def_bg_events` and an empty `def_object_events`. Not locatable. -5. **Ho-Oh's moveset.** The FAQ says "Safeguard and Ancient Power ..., Punishment, - and Sacred Fire". `PUNISHMENT` does not exist in Gen 2 at all - (`data/pokemon/evos_attacks.asm:3324-3336` is the whole learnset). Derived - from that table plus `FillMoves` (`engine/pokemon/evolve.asm:478`, which keeps - the last four moves learnable at or below the level), a level-70 Ho-Oh should - have `RECOVER`, `FIRE_BLAST`, `SUNNY_DAY`, `SWIFT`, and a level-40 one - `SACRED_FIRE`, `SAFEGUARD`, `GUST`, `RECOVER`. Worth confirming in-engine - before a bot plans around Sacred Fire; the `FillMoves` derivation is mine, not - a literal table in the asm. -6. **Lugia's Pokemon list at the Whirl Islands.** The FAQ lists Zubat, Seel, - Krabby, Horsea, Seadra and omits `GOLBAT` (in every grass slot 5), - `TENTACOOL` / `TENTACRUEL` (SW and B2F water). Seadra only appears in - `WHIRL_ISLAND_LUGIA_CHAMBER`'s water table. -7. **"Swimmer Kara" as a whirlpool landmark.** Her object is at `(44, 28)` on - Route 41; the whirlpool "just northwest" of her is a `.blk` block, not an - event, so the exact coordinate could not be pinned from the asm text. A bot - must scan `maps/Route41.blk` for `TILESET_JOHTO` block `$07`. -8. **The Whirl Islands ledge/jump routing** ("leap the ledge to the right on that - bike", "two items on the cliff but you can't reach those") is entirely block - and collision data in `maps/WhirlIsland*.blk`. None of it is expressible from - the event tables; a bot needs the decoded block/collision grid. -9. **The Tin Tower jump-platform routing** (the long "left x5, down, right x2 ..." - sequences) is likewise `.blk` data. Only the warp endpoints above are asm - facts. -10. **"go into the Bell Tower entry house"** - the map, the sign and every text - string in pokegold call it TIN TOWER (`TinTowerSign`, - `EcruteakTinTowerEntranceSageText`). "Bell Tower" is the post-GS rename. -11. **Escape Rope on the port.** Could not find the port's equivalent of - `EscapeRopeFunction`'s `CAVE`/`DUNGEON` environment check, so I cannot say - whether the port correctly refuses the rope on `TIN_TOWER_ROOF`. diff --git a/docs/gold-walkthrough/asm-walk/section-26-mount-moon-and-routes-1-4.md b/docs/gold-walkthrough/asm-walk/section-26-mount-moon-and-routes-1-4.md deleted file mode 100644 index f4992f70..00000000 --- a/docs/gold-walkthrough/asm-walk/section-26-mount-moon-and-routes-1-4.md +++ /dev/null @@ -1,893 +0,0 @@ -# Section 26 - Mount Moon and Routes 1-4 - -Source: `../section-26-mount-moon-and-routes-1-4.txt` (walkthrough heading "32 > Mount Moon and Routes 1-4") -Maps covered: `MAP_ROUTE_3`, `MAP_MOUNT_MOON`, `MAP_MOUNT_MOON_SQUARE`, `MAP_MOUNT_MOON_GIFT_SHOP`, `MAP_ROUTE_4`, `MAP_VIRIDIAN_CITY`, `MAP_TRAINER_HOUSE_1F`, `MAP_TRAINER_HOUSE_B1F`, `MAP_ROUTE_1`, `MAP_PALLET_TOWN`, `MAP_OAKS_LAB` -Badges / key milestones in this section: no badge. The milestones are `EVENT_BEAT_RIVAL_IN_MT_MOON` (the last mandatory Silver fight, and the unlock for both rival re-encounters), `ENGINE_FLYPOINT_VIRIDIAN` and `ENGINE_FLYPOINT_PALLET` (Fly targets), `EVENT_TALKED_TO_OAK_IN_KANTO` (Oak's Kanto-badge conversation, which later opens Mt. Silver via `EVENT_OPENED_MT_SILVER`), plus the `HP_UP` item ball on Route 4. - -Note on paths: everything under `maps/`, `data/`, `constants/`, `engine/`, `macros/` is relative to the pokegold checkout at `/Users/bryanbassett/Documents/development/pokegold`. Everything under `src/` and `tests/` is relative to this repo root. Addresses are `bank:addr` from `/Users/bryanbassett/Documents/development/pokegold-symbols/pokegold.sym`. - ---- - -## 1. Route order - -The section starts with the player already standing on Route 3 (arriving from Pewter City, which belongs to the previous section) and ends by handing off to Route 21 (next section). - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_3` | `maps/Route3.asm` | west connection from `PEWTER_CITY` (`data/maps/attributes.asm:377`) | warp 1 at (52, 1) -> `MOUNT_MOON` warp 1 | four trainers east: Warren, Jimmy, Otis, Burt | -| 2 | `MAP_ROUTE_3` -> Pewter and back | - | - | - | walkthrough tells the player to heal at the Pewter Pokemon Center and save before Mt. Moon (no asm gate; pure advice) | -| 3 | `MAP_MOUNT_MOON` | `maps/MountMoon.asm` | warp 1 at (3, 3) | see below | mandatory rival battle, fired by `scene_script MountMoonRivalEncounterScene, SCENE_MOUNTMOON_RIVAL_BATTLE` | -| 4 | `MAP_MOUNT_MOON` (north lane) | `maps/MountMoon.asm` | warp 3 at (13, 3) -> warp 7 at (25, 3) | warp 5 at (25, 5) -> `MOUNT_MOON_SQUARE` warp 1 | "go right from your Rival battle, up the ladder, and then out" | -| 5 | `MAP_MOUNT_MOON_SQUARE` | `maps/MountMoonSquare.asm` | warp 1 at (20, 5) | warp 3 at (13, 7) -> gift shop; warp 2 at (22, 11) -> `MOUNT_MOON` warp 6 | the outdoor shop and the Monday-night Clefairy dance | -| 6 | `MAP_MOUNT_MOON_GIFT_SHOP` | `maps/MountMoonGiftShop.asm` | warp 1 (3, 7) / warp 2 (4, 7) | either warp -> `MOUNT_MOON_SQUARE` warp 3 | "a shop outside that sells Fresh Water, Soda Pop, and Lemonade" (`MART_MT_MOON`) | -| 7 | `MAP_MOUNT_MOON` (south lane) | `maps/MountMoon.asm` | warp 6 at (25, 15) | warp 8 at (25, 13) -> warp 4 at (15, 11), then warp 2 at (15, 15) | "go down into another cave entrance and down the ladder, you'll make your way through" | -| 8 | `MAP_ROUTE_4` | `maps/Route4.asm` | warp 1 at (2, 5) from `MOUNT_MOON` warp 2 | east connection to `CERULEAN_CITY` (not taken here) | three trainers: Hope, Hank, Sharon; `HP_UP` item ball at (26, 3) | -| 9 | `MAP_VIRIDIAN_CITY` | `maps/ViridianCity.asm` | walkthrough routes Fly -> Pewter -> `ROUTE_2` -> `VIRIDIAN_FOREST` -> Viridian (those two maps belong to a neighbouring section) | warp 3 at (23, 15) -> `TRAINER_HOUSE_1F` | Trainer House daily battle | -| 10 | `MAP_TRAINER_HOUSE_1F` | `maps/TrainerHouse1F.asm` | warp 1/2 at (2, 13)/(3, 13) | warp 3 at (8, 2) -> `TRAINER_HOUSE_B1F` warp 1 | "simply go downstairs in that house" | -| 11 | `MAP_TRAINER_HOUSE_B1F` | `maps/TrainerHouseB1F.asm` | warp 1 at (9, 4) | same warp back up | coord_event at (7, 3) runs `TrainerHouseReceptionistScript`, the once-a-day CAL battle | -| 12 | `MAP_ROUTE_1` | `maps/Route1.asm` | north connection from `VIRIDIAN_CITY` (`data/maps/attributes.asm:267`) | south connection to `PALLET_TOWN` | Bitter Berry fruit tree, Schoolboy Danny, Cooltrainerf Quinn | -| 13 | `MAP_PALLET_TOWN` | `maps/PalletTown.asm` | north connection from `ROUTE_1` | warp 3 at (12, 11) -> `OAKS_LAB` warp 1 | Oak's badge conversation; sets `ENGINE_FLYPOINT_PALLET` | -| 14 | `MAP_OAKS_LAB` | `maps/OaksLab.asm` | warp 1 at (4, 11) / warp 2 at (5, 11) | either warp -> `PALLET_TOWN` warp 3 | "Talk with Professor Oak in his lab" | -| 15 | hand-off | - | - | south connection from `PALLET_TOWN` to `ROUTE_21` (`data/maps/attributes.asm:273`) | "Now, surf south onto Route 21" - Route 21 belongs to the next section; stopping here. | - -`ROUTE_2` and `VIRIDIAN_FOREST` are named in the walkthrough only as the walking route from Pewter back to Viridian; they are not covered here. - ---- - -## 2. Maps - -### MAP_ROUTE_3 - -- Script: `maps/Route3.asm` (`Route3_MapEvents` = `50:514b`) -- Blocks: `maps/Route3.blk` -- Header: `data/maps/maps.asm:321` -> `map Route3, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_3, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:296` -> `map_const ROUTE_3, 30, 9` (30x9 blocks = 60x18 walk cells), group `PEWTER` -- Connections: `data/maps/attributes.asm:377` -> west `PewterCity` (offset -5), east `Route4` (offset 0). No north/south. -- Scene scripts: none (`def_scene_scripts` is empty). Callbacks: none. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 52 | 1 | `MOUNT_MOON` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 49 | 13 | `BGEVENT_READ` | `Route3MtMoonSquareSign` -> `Route3MtMoonSquareSignText` ("MT.MOON SQUARE / Just go up the stairs.") | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE3_FISHER1` | `SPRITE_FISHER` | 26 | 12 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerFirebreatherOtis` (`50:4f26`) | -1 | -| `ROUTE3_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 11 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerYoungsterWarren` (`50:4f3a`) | -1 | -| `ROUTE3_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 20 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerYoungsterJimmy` (`50:4f4e`) | -1 | -| `ROUTE3_FISHER2` | `SPRITE_FISHER` | 49 | 5 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerFirebreatherBurt` (`50:4f62`) | -1 | - -Column order comes from `macros/scripts/maps.asm:113` (`object_event x, y, sprite, movement, radius_x, radius_y, hour1, hour2, palette, type, sight_range, script, event_flag`). All four here have radius 0/0 and hour limits `-1, -1` (always present). - -**Scripts of interest** - -- `TrainerFirebreatherOtis` / `TrainerYoungsterWarren` / `TrainerYoungsterJimmy` / `TrainerFirebreatherBurt`: each is a plain `trainer CLASS, ID, EVENT_BEAT_*, SeenText, BeatenText, 0, .Script` header. `.Script` is `endifjustbattled / opentext / writetext <After>Text / waitbutton / closetext / end`. No items, no flags beyond the trainer's own beaten flag, no warps. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_FIREBREATHER_OTIS` | `constants/event_flags.asm:576` | `trainer` header | set on win; trainer will not re-challenge | -| `EVENT_BEAT_FIREBREATHER_BURT` | `constants/event_flags.asm:579` | `trainer` header | same | -| `EVENT_BEAT_YOUNGSTER_WARREN` | `constants/event_flags.asm:971` | `trainer` header | same | -| `EVENT_BEAT_YOUNGSTER_JIMMY` | `constants/event_flags.asm:972` | `trainer` header | same | - -**Items** - -None on this map. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `WARREN` | `YOUNGSTER` | YOUNGSTER (9), line 386 | L35 FEAROW (`TRAINERTYPE_NORMAL`) | `TrainerYoungsterWarren` | none | -| `JIMMY` | `YOUNGSTER` | YOUNGSTER (10), line 391 | L33 RATICATE, L33 ARBOK | `TrainerYoungsterJimmy` | none | -| `OTIS` | `FIREBREATHER` | FIREBREATHER (1), line 2334 | L29 MAGMAR, L32 WEEZING, L29 MAGMAR | `TrainerFirebreatherOtis` | none | -| `BURT` | `FIREBREATHER` | FIREBREATHER (4), line 2353 | L32 KOFFING, L32 SLUGMA | `TrainerFirebreatherBurt` | none | - -All four are `TRAINERTYPE_NORMAL`, i.e. level+species only, moves come from the learnset. - -**Wild encounters** - -`data/wild/kanto_grass.asm:314` `def_grass_wildmons ROUTE_3`, rates `10 percent, 10 percent, 10 percent` (morn/day/nite). The table is version-split: - -- `IF DEF(_GOLD)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 SPEAROW, L6 JIGGLYPUFF, L10 RATTATA, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 RATTATA, L6 JIGGLYPUFF, L10 RATTATA, L8 RATTATA, L8 RATTATA. -- `ELIF DEF(_SILVER)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 RATTATA, L8 RATTATA. - -The walkthrough's "Rattata / Ekans / Arbok" is the **Silver** column. On Gold there is no Ekans or Arbok on Route 3. See section 6. - -No water/fishing/headbutt entry for `ROUTE_3` in `data/wild/kanto_water.asm` or `data/wild/treemons.asm`. - ---- - -### MAP_MOUNT_MOON - -- Script: `maps/MountMoon.asm` (`MountMoon_MapEvents` = `42:587e`, `MountMoonRivalBattleScript` = `42:55b0`) -- Blocks: `maps/MountMoon.blk` -- Header: `data/maps/maps.asm:154` -> `map MountMoon, TILESET_CAVE, CAVE, LANDMARK_MT_MOON, MUSIC_MT_MOON, TRUE, PALETTE_NITE, FISHGROUP_SHORE` (the `TRUE` column is the phone/"requires flash-style" fixed palette flag position in the `map` macro; palette is `PALETTE_NITE`, so the cave is dark-toned regardless of clock) -- Dimensions: `constants/map_constants.asm:141` -> `map_const MOUNT_MOON, 15, 9` (15x9 blocks = 30x18 walk cells), group `CERULEAN`-adjacent index 76 in its group listing -- Attributes: `data/maps/attributes.asm:488` -> `map_attributes MountMoon, MOUNT_MOON, $09`. **No connections** - the map is entered and left only through warps. -- Scene var: `data/maps/scenes.asm:46` -> `wMountMoonSceneID` - -**Scene scripts** (`def_scene_scripts`, ids allocated from 0 by `macros/scripts/maps.asm:12`) - -| scene id | constant | script | -|---|---|---| -| 0 | `SCENE_MOUNTMOON_RIVAL_BATTLE` | `MountMoonRivalEncounterScene` -> `sdefer MountMoonRivalBattleScript` | -| 1 | `SCENE_MOUNTMOON_NOOP` | `MountMoonNoopScene` -> `end` | - -Nothing in the game sets scene 0; it is the power-on default, so the very first entry to Mt. Moon fires the rival cutscene. `RunSceneScript` is polled in `engine/overworld/events.asm:256`, after `CheckTrainerEvent` and `CheckTileEvent`, so it lands on the first player-event poll after the warp completes. - -**Callbacks**: none. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `ROUTE_3` | 1 | -| 2 | 15 | 15 | `ROUTE_4` | 1 | -| 3 | 13 | 3 | `MOUNT_MOON` | 7 | -| 4 | 15 | 11 | `MOUNT_MOON` | 8 | -| 5 | 25 | 5 | `MOUNT_MOON_SQUARE` | 1 | -| 6 | 25 | 15 | `MOUNT_MOON_SQUARE` | 2 | -| 7 | 25 | 3 | `MOUNT_MOON` | 3 | -| 8 | 25 | 13 | `MOUNT_MOON` | 4 | - -Warps 3<->7 and 4<->8 are the two internal ladder pairs. Read as a graph: -`ROUTE_3 -(1)- (3,3) ... (13,3) -(3/7)- (25,3) ... (25,5) -(5)- SQUARE` -and `SQUARE -(2/6)- (25,15) ... (25,13) -(8/4)- (15,11) ... (15,15) -(2)- ROUTE_4`. - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMOON_RIVAL` | `SPRITE_RIVAL` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` (the shared ROM0 stub) | `EVENT_MT_MOON_RIVAL` | - -Flag semantics (`engine/overworld/scripting.asm:879-898`): `appear` clears the object's event flag, `disappear` sets it. `EVENT_MT_MOON_RIVAL` therefore starts **clear** = rival visible, and `disappear MOUNTMOON_RIVAL` at the end of the cutscene sets it = rival gone forever. - -**Scripts of interest** - -`MountMoonRivalBattleScript` (`42:55b0`), reached through `sdefer` from scene 0: - -1. `turnobject PLAYER, RIGHT`, `showemote EMOTE_SHOCK, PLAYER, 15`, `special FadeOutMusic`, `pause 15`. -2. `applymovement MOUNTMOON_RIVAL, MountMoonRivalMovementBefore` = three `step LEFT` (rival walks from (7,3) toward the player at the (3,3) entrance). -3. `playmusic MUSIC_RIVAL_ENCOUNTER`, `opentext`, `writetext MountMoonRivalTextBefore`, `waitbutton`, `closetext`. -4. Starter branch: - - `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `.Totodile` -> `loadtrainer RIVAL2, RIVAL2_1_CHIKORITA` - - `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `.Chikorita` -> `loadtrainer RIVAL2, RIVAL2_1_CYNDAQUIL` - - fallthrough (player took Cyndaquil) -> `loadtrainer RIVAL2, RIVAL2_1_TOTODILE` - Note the label names describe the *player's* starter; the constant names describe the *rival's*. Each arm does `winlosstext MountMoonRivalTextWin, MountMoonRivalTextLoss`, `setlasttalked MOUNTMOON_RIVAL`, `startbattle`, `dontrestartmapmusic`, `reloadmapafterbattle`. -5. `.FinishBattle`: `playmusic MUSIC_RIVAL_AFTER`, after-text, `turnobject PLAYER, UP`, `turnobject PLAYER, RIGHT`, `applymovement MOUNTMOON_RIVAL, MountMoonRivalMovementAfter` (`RIGHT, RIGHT, DOWN x5`), `disappear MOUNTMOON_RIVAL`, `setscene SCENE_MOUNTMOON_NOOP`, `setevent EVENT_BEAT_RIVAL_IN_MT_MOON`, `playmapmusic`, `end`. - -The battle is unavoidable: there is no `iftrue` skip and no branch that reaches the rest of the map without running it, because the scene id is 0 until `setscene` runs at the very end of the script. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_MT_MOON_RIVAL` | `constants/event_flags.asm:1308` | object row; `disappear` in `MountMoonRivalBattleScript` | clear = rival object on the map | -| `EVENT_BEAT_RIVAL_IN_MT_MOON` | `constants/event_flags.asm:456` | set here; read by `maps/DragonsDenB1F.asm:13` and `maps/IndigoPlateauPokecenter1F.asm:45,67` | unlocks both post-game rival re-encounters | -| `EVENT_GOT_TOTODILE_FROM_ELM` | `constants/event_flags.asm:37` | read here | selects rival party | -| `EVENT_GOT_CHIKORITA_FROM_ELM` | `constants/event_flags.asm:38` | read here | selects rival party | -| `SCENE_MOUNTMOON_RIVAL_BATTLE` = 0 | `maps/MountMoon.asm:6` (allocated by `def_scene_scripts`) | `wMountMoonSceneID` | 0 = cutscene armed | -| `SCENE_MOUNTMOON_NOOP` = 1 | `maps/MountMoon.asm:7` | `setscene` at end of script | 1 = cutscene spent | - -**Items**: none on this map. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `RIVAL2_1_CHIKORITA` | `RIVAL2` (`constants/trainer_constants.asm:424`) | RIVAL2 (1), line 2042 | L41 SNEASEL (Quick Attack, Screech, Faint Attack, Fury Cutter); L42 GOLBAT (Leech Life, Bite, Confuse Ray, Wing Attack); L41 MAGNETON (ThunderShock, SonicBoom, Thunder Wave, Swift); L43 GENGAR (Mean Look, Curse, Shadow Ball, Confuse Ray); L43 ALAKAZAM (Disable, Recover, Future Sight, Psychic); **L45 MEGANIUM** (Razor Leaf, PoisonPowder, Body Slam, Light Screen) | `MountMoonRivalBattleScript` `.Totodile` | rematch at Indigo Plateau / Dragon's Den, see gates | -| `RIVAL2_1_CYNDAQUIL` | `RIVAL2` | RIVAL2 (2), line 2052 | same first five, **L45 TYPHLOSION** (SmokeScreen, Quick Attack, Flame Wheel, Swift) | `.Chikorita` | same | -| `RIVAL2_1_TOTODILE` | `RIVAL2` | RIVAL2 (3), line 2062 | same first five, **L45 FERALIGATR** (Rage, Water Gun, Scary Face, Slash) | fallthrough | same | - -`TRAINERTYPE_MOVES`, so the four move slots above are exactly what the rival fields. `RIVAL2_2_*` (rows 4-6, levels 45-50 with Crobat) are the Indigo Plateau rematch parties, not this fight. - -**Wild encounters** - -`data/wild/kanto_grass.asm:33` `def_grass_wildmons MOUNT_MOON`, rates `6 percent, 6 percent, 6 percent`. - -- `IF DEF(_GOLD)` - identical morn/day/nite: L6 ZUBAT, L8 GEODUDE, L8 SANDSHREW, L12 PARAS, L10 SANDSLASH, L8 CLEFAIRY, L8 CLEFAIRY. -- `ELIF DEF(_SILVER)` - L6 ZUBAT, L8 GEODUDE, L8 ZUBAT, L12 PARAS, L10 GEODUDE, L8 CLEFAIRY, L8 CLEFAIRY. - -The walkthrough's "Zubat / Parus [sic, Paras] / Geodude" is a subset of both columns. Gold additionally has Sandshrew and Sandslash, and both versions have a 2-slot Clefairy line the walkthrough does not mention. - ---- - -### MAP_MOUNT_MOON_SQUARE - -- Script: `maps/MountMoonSquare.asm` (`MountMoonSquare_MapEvents` = `5b:6827`, `ClefairyDance` = `5b:676a`) -- Blocks: `maps/MountMoonSquare.blk` -- Header: `data/maps/maps.asm:342` -> `map MountMoonSquare, TILESET_KANTO, ROUTE, LANDMARK_MT_MOON, MUSIC_MT_MOON_SQUARE, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:316` -> `map_const MOUNT_MOON_SQUARE, 15, 9` (30x18 walk cells), group `FAST_SHIP` -- Attributes: `data/maps/attributes.asm:595` -> `$2d`, no connections -- Scene var: `data/maps/scenes.asm:66` -> `wMountMoonSquareSceneID` - -**Scene scripts** - -| scene id | constant | script | -|---|---|---| -| 0 | `SCENE_MOUNTMOONSQUARE_CLEFAIRY_DANCE` | `MountMoonSquareNoopScene` -> `end` (the constant only exists to name the coord_event's scene, the scene script itself is a no-op and the id is never changed) | - -**Callbacks** - -- `MAPCALLBACK_NEWMAP` -> `MountMoonSquareDisappearMoonStoneCallback`: `setevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` / `endcallback`. Every fresh entry re-hides the Moon Stone. -- `MAPCALLBACK_OBJECTS` -> `MountMoonSquareDisappearRockCallback`: `disappear MOUNTMOONSQUARE_ROCK` / `endcallback`. The smashable rock is hidden on every object refresh; it exists on-screen only during the dance cutscene, which `appear`s it mid-script. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 20 | 5 | `MOUNT_MOON` | 5 | -| 2 | 22 | 11 | `MOUNT_MOON` | 6 | -| 3 | 13 | 7 | `MOUNT_MOON_GIFT_SHOP` | 1 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_MOUNTMOONSQUARE_CLEFAIRY_DANCE` (0) | 7 | 11 | `ClefairyDance` (`5b:676a`) | the Monday-night Clefairy cutscene; always armed because the scene id is never advanced | - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 7 | `BGEVENT_ITEM` | `MountMoonSquareHiddenMoonStone` (`5b:67e8`) = `hiddenitem MOON_STONE, EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` | -| 17 | 7 | `BGEVENT_READ` | `DontLitterSign` -> `DontLitterSignText` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMOONSQUARE_FAIRY1` | `SPRITE_FAIRY` | 6 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` | `EVENT_MT_MOON_SQUARE_CLEFAIRY` | -| `MOUNTMOONSQUARE_FAIRY2` | `SPRITE_FAIRY` | 7 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` | `EVENT_MT_MOON_SQUARE_CLEFAIRY` | -| `MOUNTMOONSQUARE_ROCK` | `SPRITE_ROCK` | 7 | 7 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT`, sight 0 | `MtMoonSquareRock` (`5b:67ee`) = `jumpstd SmashRockScript` | `EVENT_MT_MOON_SQUARE_ROCK` | - -**Scripts of interest** - -`ClefairyDance` - the whole cutscene is guarded by three conditions in a row, all of which must pass: - -``` -checkflag ENGINE_MT_MOON_SQUARE_CLEFAIRY ; iftrue .NoDancing (once per save) -readvar VAR_WEEKDAY / ifnotequal MONDAY, .NoDancing -checktime NITE / iffalse .NoDancing -``` - -Then: `appear` both fairies, `applymovement PLAYER, PlayerWalksUpToDancingClefairies` (one `step UP`, so the player ends on (7, 10)), `pause 15`, `appear MOUNTMOONSQUARE_ROCK`, a long `follow`/`applymovement`/`cry CLEFAIRY` dance across `ClefairyDanceStep1..7`, `showemote EMOTE_SHOCK` when the Clefairy notice the player, `applymovement ... ClefairyFleeMovement`, `disappear` both fairies, `stopfollow`, then the payoff: - -``` -clearevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE -setflag ENGINE_MT_MOON_SQUARE_CLEFAIRY -``` - -So the Moon Stone at bg_event (7, 7) becomes takeable, but the rock object was `appear`ed onto that exact cell during the cutscene and is only cleared by `MAPCALLBACK_OBJECTS` on the next map load. That is the asm behind Thard_Verad's "Bring Rock Smash": you either smash the rock at (7, 7) in the same visit, or leave and come back (the `MAPCALLBACK_NEWMAP` callback then re-sets the Moon Stone flag and re-hides the item, so leaving loses it). See section 6. - -`MtMoonSquareRock` -> `jumpstd SmashRockScript` (`engine/events/std_scripts.asm:199`) -> `farsjump AskRockSmashScript` (`engine/events/overworld.asm:1365`), which is `callasm HasRockSmash` (`engine/events/overworld.asm:1386`: `ld d, ROCK_SMASH / call CheckPartyMove`) then a yes/no prompt. **No badge check** - Rock Smash in Gen 2 is TM08, not an HM. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` | `constants/event_flags.asm:236` | set by `MAPCALLBACK_NEWMAP`; cleared at the end of `ClefairyDance`; consumed by `hiddenitem` | set = Moon Stone not present | -| `EVENT_MT_MOON_SQUARE_CLEFAIRY` | `constants/event_flags.asm:1307` | object rows; `appear`/`disappear` in `ClefairyDance` | clear = the two Clefairy are on the map (only during the cutscene) | -| `EVENT_MT_MOON_SQUARE_ROCK` | `constants/event_flags.asm:1306` | object row; `disappear` in the OBJECTS callback, `appear` mid-cutscene | clear = rock is standing on (7, 7) | -| `ENGINE_MT_MOON_SQUARE_CLEFAIRY` | `constants/engine_flags.asm:106` | `checkflag`/`setflag` in `ClefairyDance` | set = the dance has been seen once and will never replay | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MOON_STONE` | hidden item on (7, 7), only after the Clefairy dance clears its flag | `bg_event 7, 7, BGEVENT_ITEM, MountMoonSquareHiddenMoonStone` | `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` | - -**Trainers**: none. **Wild encounters**: no `MOUNT_MOON_SQUARE` entry in `data/wild/kanto_grass.asm` or `kanto_water.asm`. - ---- - -### MAP_MOUNT_MOON_GIFT_SHOP - -- Script: `maps/MountMoonGiftShop.asm` (`MountMoonGiftShop_MapEvents` = `5b:68b6`, `MountMoonGiftShopClerkScript` = `5b:6877`) -- Blocks: `data/maps/blocks.asm:701` -> `INCBIN "maps/GiftShop.blk"` (shared blocks file, no `MountMoonGiftShop.blk`) -- Header: `data/maps/maps.asm:343` -> `map MountMoonGiftShop, TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_MT_MOON, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:317` -> `map_const MOUNT_MOON_GIFT_SHOP, 4, 4` (8x8 walk cells) -- Attributes: `data/maps/attributes.asm:596` -> `$00`, no connections -- No scene scripts, no callbacks. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `MOUNT_MOON_SQUARE` | 3 | -| 2 | 4 | 7 | `MOUNT_MOON_SQUARE` | 3 | - -**Coord events**: none. **BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `MOUNTMOONGIFTSHOP_GRAMPS1` | `SPRITE_GRAMPS` | 4 | 3 | `SPRITEMOVEDATA_STANDING_DOWN`, hours `-1, MORN` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopClerkScript` | -1 | -| `MOUNTMOONGIFTSHOP_GRAMPS2` | `SPRITE_GRAMPS` | 1 | 2 | `SPRITEMOVEDATA_STANDING_RIGHT`, hours `-1, DAY` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopClerkScript` | -1 | -| `MOUNTMOONGIFTSHOP_LASS1` | `SPRITE_LASS` | 1 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0), hours `-1, MORN` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopLassScript` | -1 | -| `MOUNTMOONGIFTSHOP_LASS2` | `SPRITE_LASS` | 5 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1), hours `-1, DAY` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopLassScript` | -1 | - -The `hour1 = -1` form means `hour2` is a time-of-day mask (`macros/scripts/maps.asm:123`). **There is no NITE clerk**: at night the shop is unattended, which the walkthrough does not mention. - -**Scripts of interest** - -`MountMoonGiftShopClerkScript`: `faceplayer / opentext / pokemart MARTTYPE_STANDARD, MART_MT_MOON / closetext / end`. - -**Items** (`MartMtMoon`, `data/items/marts.asm:374`, 6 items) - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `POKE_DOLL` | bought | `MartMtMoon` | - | -| `FRESH_WATER` | bought | `MartMtMoon` | - | -| `SODA_POP` | bought | `MartMtMoon` | - | -| `LEMONADE` | bought | `MartMtMoon` | - | -| `REPEL` | bought | `MartMtMoon` | - | -| `PORTRAITMAIL` | bought | `MartMtMoon` | - | - -The walkthrough names only Fresh Water / Soda Pop / Lemonade; the other three are real and in the same list. - -**Trainers**: none. **Wild encounters**: none. - ---- - -### MAP_ROUTE_4 - -- Script: `maps/Route4.asm` (`Route4_MapEvents` = `50:5338`) -- Blocks: `maps/Route4.blk` -- Header: `data/maps/maps.asm:216` -> `map Route4, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_4, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:198` -> `map_const ROUTE_4, 20, 9` (40x18 walk cells), group `CERULEAN` -- Connections: `data/maps/attributes.asm:381` -> west `Route3` (offset 0), east `CeruleanCity` (offset -5) -- No scene scripts, no callbacks. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 5 | `MOUNT_MOON` | 2 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 7 | `BGEVENT_READ` | `MtMoonSquareSign` -> `MtMoonSquareSignText` | -| 10 | 3 | `BGEVENT_ITEM` | `Route4HiddenUltraBall` (`50:51d2`) = `hiddenitem ULTRA_BALL, EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE4_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperHank` (`50:5191`) | -1 | -| `ROUTE4_LASS1` | `SPRITE_LASS` | 10 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5 | `TrainerPicnickerHope` (`50:51a5`) | -1 | -| `ROUTE4_LASS2` | `SPRITE_LASS` | 21 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerPicnickerSharon` (`50:51b9`) | -1 | -| `ROUTE4_POKE_BALL` | `SPRITE_POKE_BALL` | 26 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route4HPUp` (`50:51d0`) = `itemball HP_UP` | `EVENT_ROUTE_4_HP_UP` | - -**Scripts of interest** - -All three trainer scripts are the standard `endifjustbattled / opentext / writetext <After>Text / waitbutton / closetext / end`. `Route4HPUp` is not bytecode at all - `itemball HP_UP` is two raw bytes (`macros/scripts/maps.asm`, `dwb`-style), read by the engine's item-ball path rather than the script VM. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_BIRD_KEEPER_HANK` | `constants/event_flags.asm:501` | `trainer` header | set on win | -| `EVENT_BEAT_PICNICKER_HOPE` | `constants/event_flags.asm:641` | `trainer` header | set on win | -| `EVENT_BEAT_PICNICKER_SHARON` | `constants/event_flags.asm:642` | `trainer` header | set on win | -| `EVENT_ROUTE_4_HP_UP` | `constants/event_flags.asm:1322` | item-ball object row | set = ball already taken (object hidden) | -| `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` | `constants/event_flags.asm:241` | `hiddenitem` | set = Ultra Ball already found | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `HP_UP` | walk onto / face and A the Poke Ball object at (26, 3) | `object_event ... OBJECTTYPE_ITEMBALL, 0, Route4HPUp` | `EVENT_ROUTE_4_HP_UP` | -| `ULTRA_BALL` | hidden, face (10, 3) and press A, or Itemfinder | `bg_event 10, 3, BGEVENT_ITEM, Route4HiddenUltraBall` | `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` | - -The walkthrough lists only the HP Up. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `HOPE` | `PICNICKER` | PICNICKER (6), line 2611 | L34 FLAAFFY | `TrainerPicnickerHope` | none | -| `HANK` | `BIRD_KEEPER` | BIRD_KEEPER (8), line 569 | L12 PIDGEY, L34 PIDGEOT | `TrainerBirdKeeperHank` | none | -| `SHARON` | `PICNICKER` | PICNICKER (7), line 2616 | L31 FURRET, L33 RAPIDASH | `TrainerPicnickerSharon` | none | - -All `TRAINERTYPE_NORMAL`. The L12 Pidgey in Hank's party is not a typo in the walkthrough; it really is level 12. - -**Wild encounters** - -- Grass: `data/wild/kanto_grass.asm:369` `def_grass_wildmons ROUTE_4`, rates `10/10/10 percent`. - - `IF DEF(_GOLD)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 SPEAROW, L6 JIGGLYPUFF, L10 RATTATA, L8 SPEAROW, L8 SPEAROW; nite: L5 SPEAROW, L5 ZUBAT, L8 RATTATA, L6 JIGGLYPUFF, L10 RATTATA, L8 RATTATA, L8 RATTATA. - - `ELIF DEF(_SILVER)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 RATTATA, L8 RATTATA. - The walkthrough's "Rattata / Spearow / Arbok" is the Silver column again. -- Water: `data/wild/kanto_water.asm:5` `def_water_wildmons ROUTE_4`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING. -- Fishing group `FISHGROUP_LAKE` from the map header. - ---- - -### MAP_VIRIDIAN_CITY - -- Script: `maps/ViridianCity.asm` (`ViridianCity_MapEvents` = `4e:4486`) -- Blocks: `maps/ViridianCity.blk` -- Header: `data/maps/maps.asm:457` -> `map ViridianCity, TILESET_KANTO, TOWN, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:423` -> `map_const VIRIDIAN_CITY, 20, 18` (40x36 walk cells), group `VIRIDIAN` -- Connections: `data/maps/attributes.asm:259` -> north `Route2` (offset 5), south `Route1` (offset 10), west `Route22` (offset 4) -- Spawn: `data/maps/spawn_points.asm:15` -> `spawn VIRIDIAN_CITY, 23, 26`; flypoint `data/maps/flypoints.asm:19` -> `db LANDMARK_VIRIDIAN_CITY, SPAWN_VIRIDIAN` - -**Scene scripts**: none. **Callbacks**: `MAPCALLBACK_NEWMAP` -> `ViridianCityFlypointCallback` = `setflag ENGINE_FLYPOINT_VIRIDIAN / endcallback`. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 32 | 7 | `VIRIDIAN_GYM` | 1 | -| 2 | 21 | 9 | `VIRIDIAN_NICKNAME_SPEECH_HOUSE` | 1 | -| 3 | 23 | 15 | `TRAINER_HOUSE_1F` | 1 | -| 4 | 29 | 19 | `VIRIDIAN_MART` | 2 | -| 5 | 23 | 25 | `VIRIDIAN_POKECENTER_1F` | 1 | - -The Trainer House (23, 15) is exactly 10 cells due north of the Pokemon Center (23, 25), which is the walkthrough's "north from the Pokemon Center". - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 17 | `BGEVENT_READ` | `ViridianCitySign` | -| 27 | 7 | `BGEVENT_READ` | `ViridianGymSign` | -| 19 | 1 | `BGEVENT_READ` | `ViridianCityWelcomeSign` | -| 21 | 15 | `BGEVENT_READ` | `TrainerHouseSign` | -| 24 | 25 | `BGEVENT_READ` | `ViridianCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 30 | 19 | `BGEVENT_READ` | `ViridianCityMartSign` (`jumpstd MartSignScript`) | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIRIDIANCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 5 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `ViridianCityCoffeeGramps` | -1 | -| `VIRIDIANCITY_GRAMPS2` | `SPRITE_GRAMPS` | 30 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianCityGrampsNearGym` | -1 | -| `VIRIDIANCITY_FISHER` | `SPRITE_FISHER` | 6 | 23 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianCityDreamEaterFisher` (`4e:4032`) | -1 | -| `VIRIDIANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 21 | `SPRITEMOVEDATA_WANDER` (radius 3,3) | `OBJECTTYPE_SCRIPT` | `ViridianCityYoungsterScript` | -1 | - -**Scripts of interest** - -- `ViridianCityFlypointCallback` - runs on `MAPCALLBACK_NEWMAP`, so simply *entering* Viridian City registers it as a Fly destination. -- `ViridianCityDreamEaterFisher` (`4e:4032`) - `faceplayer / opentext / checkevent EVENT_GOT_TM42_DREAM_EATER / iftrue .GotDreamEater / writetext ... / promptbutton / verbosegiveitem TM_DREAM_EATER / iffalse .NoRoomForDreamEater / setevent EVENT_GOT_TM42_DREAM_EATER`. A free TM42 the walkthrough never mentions. -- `ViridianCityGrampsNearGym` - branches on `EVENT_BLUE_IN_CINNABAR`; flavour only. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_VIRIDIAN` | `constants/engine_flags.asm:68` | set by `ViridianCityFlypointCallback` | Fly target unlocked on first visit | -| `EVENT_GOT_TM42_DREAM_EATER` | `constants/event_flags.asm:223` | `ViridianCityDreamEaterFisher` | one-time TM | -| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | read only, here | selects the gym gramps' text | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `TM_DREAM_EATER` (TM42) | talk to `VIRIDIANCITY_FISHER` at (6, 23) | `ViridianCityDreamEaterFisher`, `verbosegiveitem` | `EVENT_GOT_TM42_DREAM_EATER` | - -**Trainers**: none on the overworld map. - -**Wild encounters**: `data/wild/kanto_water.asm:124` `def_water_wildmons VIRIDIAN_CITY`, 2 percent (surf only). No grass entry for `VIRIDIAN_CITY`. - ---- - -### MAP_TRAINER_HOUSE_1F - -- Script: `maps/TrainerHouse1F.asm` (`TrainerHouse1F_MapEvents` = `5f:48f9`) -- Blocks: `maps/TrainerHouse1F.blk` -- Header: `data/maps/maps.asm:460` -> `map TrainerHouse1F, TILESET_HOUSE, INDOOR, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:425` -> `map_const TRAINER_HOUSE_1F, 5, 7` (10x14 walk cells) -- Attributes: `data/maps/attributes.asm:659` -> `$00`, no connections -- No scene scripts, no callbacks. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 13 | `VIRIDIAN_CITY` | 3 | -| 2 | 3 | 13 | `VIRIDIAN_CITY` | 3 | -| 3 | 8 | 2 | `TRAINER_HOUSE_B1F` | 1 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 0 | `BGEVENT_READ` | `TrainerHouseSign1` | -| 7 | 0 | `BGEVENT_READ` | `TrainerHouseSign2` | -| 7 | 10 | `BGEVENT_READ` | `TrainerHouseIllegibleBook` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `TRAINERHOUSE1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 0 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FReceptionistScript` | -1 | -| `TRAINERHOUSE1F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FCooltrainerMScript` | -1 | -| `TRAINERHOUSE1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 6 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FCooltrainerFScript` | -1 | -| `TRAINERHOUSE1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 4 | 8 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FYoungsterScript` | -1 | -| `TRAINERHOUSE1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 2 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FGentlemanScript` | -1 | - -Every 1F NPC is `jumptextfaceplayer` flavour. Nothing here gates the basement. - ---- - -### MAP_TRAINER_HOUSE_B1F - -- Script: `maps/TrainerHouseB1F.asm` (`TrainerHouseB1F_MapEvents` = `5f:4b97`, `TrainerHouseReceptionistScript` = `5f:4965`) -- Blocks: `maps/TrainerHouseB1F.blk` -- Header: `data/maps/maps.asm:461` -> `map TrainerHouseB1F, TILESET_FACILITY, INDOOR, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:426` -> `map_const TRAINER_HOUSE_B1F, 5, 8` (10x16 walk cells) -- Attributes: `data/maps/attributes.asm:660` -> `$00`, no connections -- Scene var: `data/maps/scenes.asm:15` -> `wTrainerHouseB1FSceneID` - -**Scene scripts** - -| scene id | constant | script | -|---|---|---| -| 0 | `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` | `TrainerHouseB1FNoopScene` -> `end` (the id only exists to arm the coord_event; it is never changed) | - -**Callbacks**: none. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 4 | `TRAINER_HOUSE_1F` | 3 | - -**Coord events** - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` (0) | 7 | 3 | `TrainerHouseReceptionistScript` (`5f:4965`) | the once-a-day CAL battle | - -The bot walks in at (9, 4) and must reach (7, 3) - two cells left and one up. That is the walkthrough's "head left after you go downstairs and the lady will talk to you". - -**BG events**: none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `TRAINERHOUSEB1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 7 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (stub - she is only reachable through the coord_event) | -1 | -| `TRAINERHOUSEB1F_CHRIS` | `SPRITE_CHRIS` | 6 | 11 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | -1 | - -`TRAINERHOUSEB1F_CHRIS` is the opponent sprite in the battle room; `setlasttalked TRAINERHOUSEB1F_CHRIS` points the battle at it. - -**Scripts of interest** - -`TrainerHouseReceptionistScript`: - -1. `turnobject PLAYER, UP`, `opentext`. -2. `checkflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` -> `iftrue .FoughtTooManyTimes` (refusal text, `applymovement PLAYER, Movement_TrainerHouseTurnBack`). -3. `special TrainerHouse` (`engine/events/specials.asm:454`: reads `sMysteryGiftTrainerHouseFlag` into `wScriptVar`). True -> name from `gettrainername STRING_BUFFER_3, CAL, CAL2`; false -> `CAL, CAL3`. -4. `yesorno`. No -> `.Declined`. Yes -> `setflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY`, text, `applymovement PLAYER, Movement_EnterTrainerHouseBattleRoom` (`LEFT x3, DOWN x8, LEFT, turn_head RIGHT`). -5. `special TrainerHouse` again. True -> `loadtrainer CAL, CAL2`; false -> `.NoSpecialBattle` -> `loadtrainer CAL, CAL3`. Both arms `winlosstext TrainerHouseB1FCalBeatenText, 0`, `setlasttalked TRAINERHOUSEB1F_CHRIS`, `startbattle`, `reloadmapafterbattle`. -6. `.End`: `applymovement PLAYER, Movement_ExitTrainerHouseBattleRoom`. - -`CAL, CAL2` is the only trainer in the game whose party is **not** read from `data/trainers/parties.asm`: `engine/battle/read_trainer_party.asm:17-22` branches to `.cal2` and reads `sMysteryGiftTrainer` out of SRAM, and `GetTrainerName` (same file, line 326) reads `sMysteryGiftPartnerName`. With no Mystery Gift ever performed the flag is 0 and the game always fights `CAL3`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` | `constants/engine_flags.asm:104` | `checkflag`/`setflag` here | set = already battled today; cleared by the daily reset | -| `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` = 0 | `maps/TrainerHouseB1F.asm:7` | `wTrainerHouseB1FSceneID` | always 0, so the coord_event is always armed | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `CAL3` | `CAL` (`constants/trainer_constants.asm:73`) | CAL (3), line 222 | L50 MEGANIUM, L50 TYPHLOSION, L50 FERALIGATR (`TRAINERTYPE_NORMAL`) | `TrainerHouseReceptionistScript` `.NoSpecialBattle` | once per day | -| `CAL2` | `CAL` | CAL (2), line 214 - **dead data** | table row is L30 BAYLEEF/QUILAVA/CROCONAW, but `ReadTrainerParty` never reaches it; the real party comes from `sMysteryGiftTrainer` | `TrainerHouseReceptionistScript` main arm | Mystery Gift only | - -The walkthrough's "Level 50 Meganium / Typhlosion / Feraligatr" is `CAL3`, and its Thard_Verad note ("Cal is a placeholder; if you Mystery Gift with anyone, they can show up") is exactly the `CAL2` branch. - ---- - -### MAP_ROUTE_1 - -- Script: `maps/Route1.asm` (`Route1_MapEvents` = `4e:4675`) -- Blocks: `maps/Route1.blk` -- Header: `data/maps/maps.asm:311` -> `map Route1, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_1, MUSIC_ROUTE_1, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:287` -> `map_const ROUTE_1, 10, 18` (20x36 walk cells), group `PALLET` -- Connections: `data/maps/attributes.asm:267` -> north `ViridianCity` (offset -10), south `PalletTown` (offset 0) -- No scene scripts, no callbacks, **no warps** (connections only). - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 27 | `BGEVENT_READ` | `Route1Sign` -> `Route1SignText` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE1_YOUNGSTER` | `SPRITE_YOUNGSTER` | 7 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSchoolboyDanny` (`4e:4531`) | -1 | -| `ROUTE1_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 3 | 26 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 1,0) | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerCooltrainerfQuinn` (`4e:4545`) | -1 | -| `ROUTE1_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 3 | 7 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route1FruitTree` (`4e:455c`) = `fruittree FRUITTREE_ROUTE_1` | -1 | - -**Scripts of interest** - -`Route1FruitTree` is one command: `fruittree FRUITTREE_ROUTE_1` (`constants/script_constants.asm:232`). The item comes from `FruitTreeItems` (`data/items/fruit_trees.asm`), whose `ROUTE_1` row is `db BITTER_BERRY`. This is the walkthrough's "Item on Route 1: Bitter Berry" - it is a **fruit tree**, not a ground item, so it is a daily respawn rather than a one-time pickup (`engine/events/fruit_trees.asm`, `TryResetFruitTrees`). - -Both trainer scripts are the standard `endifjustbattled` after-battle text. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SCHOOLBOY_DANNY` | `constants/event_flags.asm:621` | `trainer` header | set on win | -| `EVENT_BEAT_COOLTRAINERF_QUINN` | `constants/event_flags.asm:890` | `trainer` header | set on win | - -The fruit tree's picked state is **not** an `EVENT_*` - it is a per-tree bit in the fruit-tree flag array with a daily reset (see `engine/events/fruit_trees.asm`). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `BITTER_BERRY` | face the tree object at (3, 7) and press A | `Route1FruitTree` -> `FruitTreeItems` row `ROUTE_1` | daily per-tree flag, not an event flag | - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `DANNY` | `SCHOOLBOY` | SCHOOLBOY (5), line 435 | L31 JYNX, L31 ELECTABUZZ, L31 MAGMAR | `TrainerSchoolboyDanny` | none | -| `QUINN` | `COOLTRAINERF` | COOLTRAINERF (14), line 948 | L38 IVYSAUR, L38 STARMIE | `TrainerCooltrainerfQuinn` | none | - -**Wild encounters** - -`data/wild/kanto_grass.asm:231` `def_grass_wildmons ROUTE_1`, rates `10/10/10 percent`. **No version split.** - -- morn/day: L2 PIDGEY, L2 RATTATA, L3 SENTRET, L3 PIDGEY, L6 FURRET, L4 PIDGEY, L4 PIDGEY. -- nite: L2 HOOTHOOT, L2 RATTATA, L3 RATTATA, L3 HOOTHOOT, L6 RATTATA, L4 HOOTHOOT, L4 HOOTHOOT. - -The walkthrough's "Pidgey / Furret" is the morn/day column minus Rattata and Sentret; at night there is no Pidgey or Furret at all. - ---- - -### MAP_PALLET_TOWN - -- Script: `maps/PalletTown.asm` (`PalletTown_MapEvents` = `4e:47aa`) -- Blocks: `maps/PalletTown.blk` -- Header: `data/maps/maps.asm:312` -> `map PalletTown, TILESET_KANTO, TOWN, LANDMARK_PALLET_TOWN, MUSIC_PALLET_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:288` -> `map_const PALLET_TOWN, 10, 9` (20x18 walk cells), group `PALLET` -- Connections: `data/maps/attributes.asm:271` -> north `Route1` (offset 0), south `Route21` (offset 0) -- Spawn: `data/maps/spawn_points.asm:14` -> `spawn PALLET_TOWN, 5, 6`; flypoint `data/maps/flypoints.asm:18` -> `db LANDMARK_PALLET_TOWN, SPAWN_PALLET` - -**Scene scripts**: none. **Callbacks**: `MAPCALLBACK_NEWMAP` -> `PalletTownFlypointCallback` = `setflag ENGINE_FLYPOINT_PALLET / endcallback`. This is the asm behind "You can now fly to Pallet Town" - it fires the moment the map loads, before any NPC is talked to. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `REDS_HOUSE_1F` | 1 | -| 2 | 13 | 5 | `BLUES_HOUSE` | 1 | -| 3 | 12 | 11 | `OAKS_LAB` | 1 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 9 | `BGEVENT_READ` | `PalletTownSign` | -| 3 | 5 | `BGEVENT_READ` | `RedsHouseSign` | -| 13 | 13 | `BGEVENT_READ` | `OaksLabSign` | -| 11 | 5 | `BGEVENT_READ` | `BluesHouseSign` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PALLETTOWN_TEACHER` | `SPRITE_TEACHER` | 3 | 8 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `PalletTownTeacherScript` | -1 | -| `PALLETTOWN_FISHER` | `SPRITE_FISHER` | 12 | 14 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `PalletTownFisherScript` | -1 | - -Both are `jumptextfaceplayer` flavour. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_PALLET` | `constants/engine_flags.asm:67` | set by `PalletTownFlypointCallback` | Fly target unlocked on first visit | - -**Items**: none. **Trainers**: none. - -**Wild encounters**: `data/wild/kanto_water.asm:117` `def_water_wildmons PALLET_TOWN`, 6 percent: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL. No grass entry. Fishing group `FISHGROUP_OCEAN`. - ---- - -### MAP_OAKS_LAB - -- Script: `maps/OaksLab.asm` (`OaksLab_MapEvents` = `59:5f33`, `Oak` = `59:58c3`) -- Blocks: `maps/OaksLab.blk` -- Header: `data/maps/maps.asm:316` -> `map OaksLab, TILESET_LAB, INDOOR, LANDMARK_PALLET_TOWN, MUSIC_POKEMON_TALK, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:291` -> `map_const OAKS_LAB, 5, 6` (10x12 walk cells) -- No connections. `OaksLabNoopScene` exists but is marked `; unreferenced`; `def_scene_scripts` is empty and there are no callbacks. - -**Warps** - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 11 | `PALLET_TOWN` | 3 | -| 2 | 5 | 11 | `PALLET_TOWN` | 3 | - -**Coord events**: none. - -**BG events** - -| x | y | type | script/item | -|---|---|---|---| -| 6,7,8,9 | 1 | `BGEVENT_READ` | `OaksLabBookshelf` (`jumpstd DifficultBookshelfScript`) | -| 0,1,2,3 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` | -| 6,7,8,9 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` | -| 4 | 0 | `BGEVENT_READ` | `OaksLabPoster1` | -| 5 | 0 | `BGEVENT_READ` | `OaksLabPoster2` | -| 9 | 3 | `BGEVENT_READ` | `OaksLabTrashcan` | -| 0 | 1 | `BGEVENT_READ` | `OaksLabPC` | - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OAKSLAB_OAK` | `SPRITE_OAK` | 4 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Oak` (`59:58c3`) | -1 | -| `OAKSLAB_SCIENTIST1` | `SPRITE_SCIENTIST` | 1 | 8 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `OaksAssistant1Script` | -1 | -| `OAKSLAB_SCIENTIST2` | `SPRITE_SCIENTIST` | 8 | 9 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1) | `OBJECTTYPE_SCRIPT` | `OaksAssistant2Script` | -1 | -| `OAKSLAB_SCIENTIST3` | `SPRITE_SCIENTIST` | 1 | 4 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `OaksAssistant3Script` | -1 | - -**Scripts of interest** - -`Oak` (`59:58c3`): - -``` -faceplayer / opentext -checkevent EVENT_OPENED_MT_SILVER ; iftrue .CheckPokedex -checkevent EVENT_TALKED_TO_OAK_IN_KANTO ; iftrue .CheckBadges -writetext OakWelcomeKantoText / promptbutton / setevent EVENT_TALKED_TO_OAK_IN_KANTO -.CheckBadges: - readvar VAR_BADGES - ifequal NUM_BADGES, .OpenMtSilver ; 16 - ifequal NUM_JOHTO_BADGES, .Complain ; 8 (i.e. zero Kanto badges) - sjump .AhGood ; 9..15 badges -.OpenMtSilver: writetext OakOpenMtSilverText / promptbutton / setevent EVENT_OPENED_MT_SILVER / sjump .CheckPokedex -.Complain: writetext OakNoKantoBadgesText / promptbutton / sjump .CheckPokedex -.AhGood: writetext OakYesKantoBadgesText / promptbutton / sjump .CheckPokedex -.CheckPokedex: writetext OakLabDexCheckText / waitbutton / special ProfOaksPCBoot / writetext OakLabGoodbyeText / waitbutton / closetext / end -``` - -`VAR_BADGES` = `constants/script_constants.asm:55`. `NUM_JOHTO_BADGES`, `NUM_KANTO_BADGES` and `NUM_BADGES = NUM_JOHTO_BADGES + NUM_KANTO_BADGES` are at `constants/ram_constants.asm:260,272,273`. The walkthrough's "He notices your Kanto badges. He tells you that he'll give you a prize if you get all the badges" is the `.AhGood` arm; the actual prize is the Mt. Silver unlock in `.OpenMtSilver`, which needs all 16. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_TALKED_TO_OAK_IN_KANTO` | `constants/event_flags.asm:224` | set on the first `Oak` conversation | skips the welcome text afterwards | -| `EVENT_OPENED_MT_SILVER` | `constants/event_flags.asm:1265` | set when `VAR_BADGES == NUM_BADGES` | Mt. Silver / the Red fight opens | - -**Items**: none. **Trainers**: none. **Wild encounters**: none. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Mt. Moon rival battle is unavoidable | `maps/MountMoon.asm` `MountMoonRivalEncounterScene` / `MountMoonRivalBattleScript` (`42:55b0`), armed by `wMountMoonSceneID == SCENE_MOUNTMOON_RIVAL_BATTLE` (0) | be able to win a 6-mon L41-45 fight | `setscene SCENE_MOUNTMOON_NOOP` + `setevent EVENT_BEAT_RIVAL_IN_MT_MOON` at the end of the script. Losing does not set the flag; the scene id is unchanged so the fight re-arms on re-entry. | -| Mt. Moon Square Moon Stone | `maps/MountMoonSquare.asm` `MountMoonSquareDisappearMoonStoneCallback` (sets `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` on every `MAPCALLBACK_NEWMAP`) | see the Clefairy dance | `ClefairyDance` `clearevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE`. Leaving the map re-sets it. | -| Clefairy dance itself | `maps/MountMoonSquare.asm` `ClefairyDance` head | `ENGINE_MT_MOON_SQUARE_CLEFAIRY` clear **and** `VAR_WEEKDAY == MONDAY` **and** `checktime NITE` | one-shot: the script `setflag ENGINE_MT_MOON_SQUARE_CLEFAIRY` at the end | -| Rock on the Moon Stone tile | `maps/MountMoonSquare.asm` `MtMoonSquareRock` -> `engine/events/std_scripts.asm:199 SmashRockScript` -> `engine/events/overworld.asm:1365 AskRockSmashScript` / `HasRockSmash` (line 1386) | `ROCK_SMASH` on a party member (`CheckPartyMove`) - **no badge check** | teach TM08 to anything | -| Trainer House CAL, once per day | `maps/TrainerHouseB1F.asm` `TrainerHouseReceptionistScript` `checkflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` | flag clear | the game's daily flag reset clears `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` | -| Trainer House opponent identity | `engine/events/specials.asm:454 TrainerHouse` reading `sMysteryGiftTrainerHouseFlag`; party via `engine/battle/read_trainer_party.asm:17` | Mystery Gift performed | not reachable without a second cartridge; falls back to `CAL3` | -| Fly back to Pewter/Pallet/Viridian | `engine/events/overworld.asm:544 FlyFunction .TryFly` -> `ld de, ENGINE_STORMBADGE / call CheckBadge` **plus** the destination's own `ENGINE_FLYPOINT_*` | Storm Badge + having visited the town | `ViridianCityFlypointCallback` / `PalletTownFlypointCallback` set their flags on `MAPCALLBACK_NEWMAP` | -| Surf south from Pallet to Route 21 (next section) | `engine/events/overworld.asm:338 SurfFunction .TrySurf` -> `ld de, ENGINE_FOGBADGE / call CheckBadge` | Fog Badge + a party member that knows Surf | out of scope here | -| Indigo Plateau rival re-encounter | `maps/IndigoPlateauPokecenter1F.asm:45,67` `PlateauRivalBattle1/2` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` clear, `VAR_WEEKDAY` not SUNDAY/TUESDAY/THURSDAY/FRIDAY/SATURDAY (so Monday or Wednesday) | matches the walkthrough exactly | -| Dragon's Den rival sighting | `maps/DragonsDenB1F.asm:12` `DragonsDenB1FCheckRivalCallback` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set and `VAR_WEEKDAY == TUESDAY` or `THURSDAY` | matches the walkthrough exactly | - -Nothing in this section requires a badge, an HM or a key item to pass through. The only hard stop is the Mt. Moon rival battle. - ---- - -## 4. Bot checklist - -Coordinates are asm walk cells `(x, y)` on the named map. - -1. `ROUTE_3` - enter from the west connection from `PEWTER_CITY`. Walk east. -2. `ROUTE_3` - trainer `ROUTE3_YOUNGSTER1` at (11, 2), sight 3 downward-ish (`STANDING_DOWN`, range 2). Precondition: `EVENT_BEAT_YOUNGSTER_WARREN` clear. Intent: battle (L35 Fearow). Postcondition: `EVENT_BEAT_YOUNGSTER_WARREN` set. -3. `ROUTE_3` - trainer `ROUTE3_YOUNGSTER2` at (20, 4), `STANDING_RIGHT`, sight 3. Battle (L33 Raticate, L33 Arbok). Post: `EVENT_BEAT_YOUNGSTER_JIMMY`. -4. `ROUTE_3` - trainer `ROUTE3_FISHER1` at (26, 12), `STANDING_UP`, sight 2. Battle (L29 Magmar, L32 Weezing, L29 Magmar). Post: `EVENT_BEAT_FIREBREATHER_OTIS`. -5. `ROUTE_3` - trainer `ROUTE3_FISHER2` at (49, 5), `SPINRANDOM_FAST`, sight 3. Battle (L32 Koffing, L32 Slugma). Post: `EVENT_BEAT_FIREBREATHER_BURT`. -6. `ROUTE_3` - optional: read the sign at (49, 13). Then heal (walkthrough advice, no flag) and save. -7. `ROUTE_3` - step on warp 1 at (52, 1). Postcondition: player on `MOUNT_MOON` at (3, 3). -8. `MOUNT_MOON` - do nothing; the scene script fires. Precondition: `wMountMoonSceneID == 0`, `EVENT_MT_MOON_RIVAL` clear. Intent: battle `RIVAL2` with the party chosen by `EVENT_GOT_TOTODILE_FROM_ELM` / `EVENT_GOT_CHIKORITA_FROM_ELM`. Postcondition: `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `EVENT_MT_MOON_RIVAL` set, `wMountMoonSceneID = 1`. -9. Optional detour: `MOUNT_MOON` (13, 3) warp 3 -> lands at (25, 3); walk to (25, 5) warp 5 -> `MOUNT_MOON_SQUARE` (20, 5). -10. `MOUNT_MOON_SQUARE` - (13, 7) warp 3 -> `MOUNT_MOON_GIFT_SHOP`. Talk to the clerk (`(4, 3)` in MORN, `(1, 2)` in DAY - **no clerk at NITE**) to open `MART_MT_MOON`. Exit at (3, 7) or (4, 7). -11. `MOUNT_MOON_SQUARE` optional, Monday night only: walk onto (7, 11) to trip `ClefairyDance`. Preconditions: `ENGINE_MT_MOON_SQUARE_CLEFAIRY` clear, `VAR_WEEKDAY == MONDAY`, `checktime NITE` true. Postcondition: `ENGINE_MT_MOON_SQUARE_CLEFAIRY` set, `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` cleared. Then, **without leaving the map**, face (7, 7); if the rock object is there, use Rock Smash (requires `ROCK_SMASH` in the party), then press A on (7, 7) to take `MOON_STONE`. -12. `MOUNT_MOON_SQUARE` - (22, 11) warp 2 -> `MOUNT_MOON` (25, 15). Walk to (25, 13) warp 8 -> lands at (15, 11). Walk to (15, 15) warp 2 -> `ROUTE_4` (2, 5). -13. `ROUTE_4` - trainer `ROUTE4_LASS1` at (10, 8), `STANDING_LEFT`, sight 5. Battle (L34 Flaaffy). Post: `EVENT_BEAT_PICNICKER_HOPE`. -14. `ROUTE_4` - trainer `ROUTE4_YOUNGSTER` at (17, 9), `STANDING_DOWN`, sight 3. Battle (L12 Pidgey, L34 Pidgeot). Post: `EVENT_BEAT_BIRD_KEEPER_HANK`. -15. `ROUTE_4` - trainer `ROUTE4_LASS2` at (21, 6), `STANDING_RIGHT`, sight 4. Battle (L31 Furret, L33 Rapidash). Post: `EVENT_BEAT_PICNICKER_SHARON`. -16. `ROUTE_4` - item ball `ROUTE4_POKE_BALL` at (26, 3). Intent: take `HP_UP`. Precondition: `EVENT_ROUTE_4_HP_UP` clear. Postcondition: set. -17. `ROUTE_4` - optional hidden item: face (10, 3), press A -> `ULTRA_BALL`. Post: `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL`. -18. Travel to `VIRIDIAN_CITY` (Fly needs `ENGINE_STORMBADGE` and the destination flypoint; the walkthrough's route is Fly to Pewter, then `ROUTE_2` south through `VIRIDIAN_FOREST`). Entering Viridian sets `ENGINE_FLYPOINT_VIRIDIAN`. -19. `VIRIDIAN_CITY` - optional: talk to `VIRIDIANCITY_FISHER` at (6, 23) for `TM_DREAM_EATER`. Precondition: `EVENT_GOT_TM42_DREAM_EATER` clear and pack room. Post: set. -20. `VIRIDIAN_CITY` - warp 3 at (23, 15) -> `TRAINER_HOUSE_1F` (2, 13). Then warp 3 at (8, 2) -> `TRAINER_HOUSE_B1F` (9, 4). -21. `TRAINER_HOUSE_B1F` - walk to (7, 3) to trip the coord_event. Precondition: `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` clear. Answer YES. Intent: battle `CAL, CAL3` (L50 Meganium / Typhlosion / Feraligatr). Postcondition: `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` set. The script walks the player in and out with `applymovement`; do not fight the movement. -22. Return to `VIRIDIAN_CITY` and cross the south connection into `ROUTE_1`. -23. `ROUTE_1` - fruit tree object at (3, 7). Face it, press A -> `BITTER_BERRY`. Daily respawn. -24. `ROUTE_1` - trainer `ROUTE1_YOUNGSTER` at (7, 17), `SPINRANDOM_FAST`, sight 3. Battle (L31 Jynx, L31 Electabuzz, L31 Magmar). Post: `EVENT_BEAT_SCHOOLBOY_DANNY`. -25. `ROUTE_1` - trainer `ROUTE1_COOLTRAINER_F` at (3, 26), `STANDING_RIGHT`, sight 4. Battle (L38 Ivysaur, L38 Starmie). Post: `EVENT_BEAT_COOLTRAINERF_QUINN`. -26. `ROUTE_1` - south connection into `PALLET_TOWN`. Postcondition on map load: `ENGINE_FLYPOINT_PALLET` set. -27. `PALLET_TOWN` - warp 3 at (12, 11) -> `OAKS_LAB` (4, 11). -28. `OAKS_LAB` - talk to `OAKSLAB_OAK` at (4, 2). Postcondition: `EVENT_TALKED_TO_OAK_IN_KANTO` set; if `VAR_BADGES == 16`, also `EVENT_OPENED_MT_SILVER`. -29. Exit to `PALLET_TOWN` and head south to the `ROUTE_21` connection (Surf, `ENGINE_FOGBADGE`) - next section. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Kanto map geometry, warps, connections, bg/coord/object rows | `src/import/RomExtractorGen2.lua` (extracts all map events), `src/world/gen2/Map.lua`, `src/world/gen2/World.lua` | implemented (generic - every map in the ROM comes through the same path, no per-map Kanto code needed) | -| Scene scripts / `wMapSceneID` (Mt. Moon rival, Trainer House coord_event) | `src/world/gen2/World.lua:1175-1183` (`GetMapSceneID` port), `:5014-5040` (coord event and scene script lookup) | implemented | -| `sdefer` timing for the Mt. Moon rival cutscene | `src/script/gen2/Vm.lua:92-93` | partial - the comment says "sdefer queues after the map settles in retail; run immediately here", so the rival scene starts a frame or two earlier than the cart. Cosmetic for a bot, visible in a screenshot diff. | -| Map callbacks (`MAPCALLBACK_NEWMAP` / `MAPCALLBACK_OBJECTS`) - the flypoint setters and the Mt. Moon Square rock/moon-stone callbacks | `src/world/gen2/World.lua:5969`, driver `tests/drivers/gold_map_callbacks.lua` | implemented, with an assertion driver | -| `appear` / `disappear` object visibility via the event bitfield | `src/world/gen2/Events.lua` (header comment cites `CheckObjectFlag`) | implemented | -| Overworld trainers: `trainer` struct, eyesight, party build | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua:5257-5264`, driver `tests/drivers/gold_trainer_smoke.lua` | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `setlasttalked` (the rival and CAL fights) | `src/script/gen2/Opcodes.lua:99,100,105,109`, `src/script/gen2/Vm.lua:806` | implemented | -| Trainer House daily CAL battle + Mystery Gift fallback | `src/world/gen2/TrainerHouse.lua` | implemented (deliberately answers `CAL3`; Mystery Gift is out of scope, documented in the file header) | -| Cutscene opcodes used by `ClefairyDance` and the rival scene: `showemote`, `follow`, `stopfollow`, `cry`, `moveobject`, `applymovement` | `src/script/gen2/Opcodes.lua:117-137`, `src/script/gen2/Vm.lua:961`, `src/script/gen2/Movement.lua` | implemented | -| `checktime` / `VAR_WEEKDAY` (the Monday-night Clefairy guard) | `src/script/gen2/Vm.lua:784`, `src/core/gen2/BugContest.lua:112` (`weekday`) | implemented | -| Hidden items (`BGEVENT_ITEM`: Mt. Moon Square Moon Stone, Route 4 Ultra Ball) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5291` | implemented | -| Item balls (`OBJECTTYPE_ITEMBALL`: the Route 4 HP Up) | extracted at `src/import/RomExtractorGen2.lua:2968-2969` as `obj.itemball`; **nothing reads it** - `World:interact` (`src/world/gen2/World.lua:5257`) only dispatches `npc.def.trainer`, strength boulders, `npc.def.scriptKey`, bg events and hidden items | **missing** - a bot cannot pick up the Route 4 HP Up in this port today | -| Fruit trees (`fruittree FRUITTREE_ROUTE_1` -> Bitter Berry) | `src/core/gen2/Apricorns.lua` (`FruitTreeItems` table, per-tree flag, `tryResetFruitTrees`), `src/script/gen2/Vm.lua:1191` | implemented | -| `pokemart` / `MART_MT_MOON` | `src/script/gen2/Vm.lua:597`, `src/ui/gen2/MartMenu.lua:336-348` | implemented (list comes from the extracted `data/generated/marts.lua`) | -| `verbosegiveitem` (Viridian TM42) | `src/script/gen2/Vm.lua:490-498` | implemented | -| Rock Smash on the Mt. Moon Square rock | `src/world/gen2/FieldMoves.lua` (comment at :588 lists `ROCK_SMASH` among the field moves); the strength-boulder equivalent is special-cased in `World:interact` | partial - `jumpstd SmashRockScript` is a `farsjump` into ASM, so there is no bytecode for the extractor to find. `World:interact` has an explicit arm for strength boulders (`World.isStrengthBoulder`) but **no equivalent arm for `SPRITEMOVEDATA_SMASHABLE_ROCK`**, so pressing A on the Mt. Moon Square rock reaches a nil `scriptKey`. Verify by hand. | -| Fly / flypoints (`ENGINE_FLYPOINT_PALLET` = 52, `ENGINE_FLYPOINT_VIRIDIAN` = 53) | `src/world/gen2/FieldMoves.lua:342-370` (both Kanto rows present) | implemented | -| Kanto wild tables (Gold vs Silver split) | extracted by `src/import/RomExtractorGen2.lua` from the ROM, so whichever cart was imported is what you get | implemented (no version conditional needed - the ROM already picked) | - ---- - -## 6. Unresolved / verify by hand - -1. **Rival party order contradicts the walkthrough.** The walkthrough lists Sneasel, Golbat, *Starter Evolution*, Magneton, Gengar, Alakazam. `data/trainers/parties.asm:2042-2069` lists Sneasel(41), Golbat(42), Magneton(41), Gengar(43), Alakazam(43), **starter last at level 45**. The walkthrough's per-mon strategy text is still correct; only the ordering is wrong. Levels and species otherwise match. -2. **Route 3 and Route 4 wild lists in the walkthrough are the Silver tables.** `data/wild/kanto_grass.asm:314` and `:369` both have `IF DEF(_GOLD) / ELIF DEF(_SILVER)` arms. Ekans and Arbok only exist in the `_SILVER` arm. On Gold, Route 3 is Spearow/Rattata/Jigglypuff (+Zubat at nite) and Route 4 is the same. The walkthrough is titled for Gold but quoted the Silver data. -3. **Mt. Moon wild list is incomplete in the walkthrough.** Gold also has L8 SANDSHREW, L10 SANDSLASH and two L8 CLEFAIRY slots (`data/wild/kanto_grass.asm:33`). The Clefairy slots mean Clefairy can be caught in the cave at any time, independent of the Monday-night dance. -4. **Route 1 wild list is incomplete and time-dependent.** The walkthrough lists Pidgey and Furret; the nite column (`data/wild/kanto_grass.asm:250`) has neither - it is Hoothoot and Rattata only. -5. **"Bitter Berry" on Route 1 is a fruit tree, not a ground item.** `Route1FruitTree` = `fruittree FRUITTREE_ROUTE_1`, and `data/items/fruit_trees.asm` row `ROUTE_1` is `BITTER_BERRY`. It respawns daily rather than being a one-time pickup, which the walkthrough's "Item on Route 1" phrasing implies. -6. **The two Mt. Moon routes described in the walkthrough could not be verified against the collision map.** The warp graph in section 2 is exact, but which of warps 2/3/4 are reachable from the (3, 3) entrance without passing through the Square depends on `maps/MountMoon.blk` collision, which was not decoded here. The walkthrough's "either way, you end up in the same spot" is consistent with warps 4<->8 and 6 meeting on the (25, 13)/(25, 15) side, but confirm by walking it. -7. **"Bring Rock Smash" for the Clefairy Moon Stone.** The asm supports the claim indirectly: `MountMoonSquareDisappearRockCallback` hides `MOUNTMOONSQUARE_ROCK` on every `MAPCALLBACK_OBJECTS`, `ClefairyDance` `appear`s it at (7, 7) mid-cutscene, and (7, 7) is exactly the `BGEVENT_ITEM` tile the dance unlocks. Whether the rock actually blocks the tile-facing A press (as opposed to being walkable) was not verified; the item could also be reachable by facing (7, 7) from an adjacent cell. Verify in-game. -8. **`ULTRA_BALL` at Route 4 (10, 3) and `TM_DREAM_EATER` in Viridian are absent from the walkthrough.** Both are real (`maps/Route4.asm` bg_event, `maps/ViridianCity.asm` `ViridianCityDreamEaterFisher`). Not a contradiction, just an omission worth carrying into a completionist route. -9. **Mt. Moon Gift Shop has no NITE clerk.** Both `SPRITE_GRAMPS` rows are gated `-1, MORN` and `-1, DAY`. A bot that arrives at night finds no shop, which the walkthrough does not warn about. -10. **`ROUTE_3` warp position vs. its sign.** The only warp is at (52, 1) but the "MT.MOON SQUARE / Just go up the stairs" sign is at (49, 13), twelve cells south. That is what the file says; it reads oddly and is worth an eyeball on the rendered map. -11. **Port: `OBJECTTYPE_ITEMBALL` pickup is unimplemented** (see section 5). The Route 4 HP Up is the first item ball this section needs, and `World:interact` has no arm for it. Confirmed by reading `src/world/gen2/World.lua:5257-5310` and grepping the whole `src/` tree for `itemball` (only the extractor and one comment in `src/script/gen2/CallAsm.lua:547` mention it). -12. **Port: smashable-rock objects have no interact arm** (see section 5). Same class of gap as the item ball, but lower stakes because the only rock in this section is optional. diff --git a/docs/gold-walkthrough/asm-walk/section-27-cinnabar-island-and-seafoam-islands-gym.md b/docs/gold-walkthrough/asm-walk/section-27-cinnabar-island-and-seafoam-islands-gym.md deleted file mode 100644 index 863b84a5..00000000 --- a/docs/gold-walkthrough/asm-walk/section-27-cinnabar-island-and-seafoam-islands-gym.md +++ /dev/null @@ -1,570 +0,0 @@ -# Section 27 - Cinnabar Island and Seafoam Islands Gym - -Source: `../section-27-cinnabar-island-and-seafoam-islands-gym.txt` -(the FAQ numbers this chapter "33 > Cinnabar Island and Seafoam Islands Gym"; the -file index is 27) - -Maps covered: `MAP_ROUTE_21`, `MAP_CINNABAR_ISLAND`, `MAP_CINNABAR_POKECENTER_1F`, -`MAP_ROUTE_20`, `MAP_SEAFOAM_GYM` - -Badges / key milestones in this section: - -- `ENGINE_FLYPOINT_CINNABAR` (set on first entry to Cinnabar Island) -- Blue's one-off speech on Cinnabar Island, which is what unlocks him in Viridian Gym - (`clearevent EVENT_VIRIDIAN_GYM_BLUE`) -- Hidden RARE_CANDY on Cinnabar Island -- `EVENT_CINNABAR_ROCKS_CLEARED` (set silently by walking onto Route 20; this is what - clears the boulders that seal Route 19 to the east) -- **VOLCANOBADGE** from BLAINE in the Seafoam Islands cave (`ENGINE_VOLCANOBADGE`, - `EVENT_BEAT_BLAINE`) - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `PALLET_TOWN` | - | - | south connection to `ROUTE_21` (`data/maps/attributes.asm:270-272`) | **Pallet Town belongs to the previous section; only the hop south is listed here.** | -| 1 | `ROUTE_21` | `maps/Route21.asm` | north connection from `PALLET_TOWN` (`data/maps/attributes.asm:274-275`) | south connection to `CINNABAR_ISLAND` (`attributes.asm:277`) | Surf south. Three trainers: Nikki, Arnold, Seth | -| 2 | `CINNABAR_ISLAND` | `maps/CinnabarIsland.asm` | north connection from `ROUTE_21` (`attributes.asm:279-280`) | warp 1 at (11,11) -> `CINNABAR_POKECENTER_1F` 1; east connection to `ROUTE_20` (`attributes.asm:281`) | Sets `ENGINE_FLYPOINT_CINNABAR`; heal; talk to Blue; hidden RARE_CANDY | -| 3 | `CINNABAR_POKECENTER_1F` | `maps/CinnabarPokecenter1F.asm` | Cinnabar warp 1 | warps 1/2 at (3,7)/(4,7) back to `CINNABAR_ISLAND` 1 | Heal; the two flavour NPCs the walkthrough quotes ("a year since the volcano erupted", "BLAINE lives alone in the SEAFOAM ISLANDS cave") | -| 4 | `ROUTE_20` | `maps/Route20.asm` | west connection from `CINNABAR_ISLAND` (`attributes.asm:283-284`) | warp 1 at (38,7) -> `SEAFOAM_GYM` 1 | `MAPCALLBACK_NEWMAP` sets `EVENT_CINNABAR_ROCKS_CLEARED`; Swimmer Cameron at (12,13) | -| 5 | `SEAFOAM_GYM` | `maps/SeafoamGym.asm` | Route 20 warp 1 | warp 1 at (5,5) back to `ROUTE_20` 1 | BLAINE -> **VOLCANOBADGE** | - -Spillover: Route 20 continues east past the gym to Swimmer Lori (45,13) and Swimmer -Nicole (52,8) and then over the `connection east, Route19, ROUTE_19, -9` boundary into -Fuchsia territory; the walkthrough does not visit them here and they belong to the -following section. - ---- - -## 2. Maps - -### MAP_ROUTE_21 - -- Script: `maps/Route21.asm` -- Blocks: `maps/Route21.blk` -- Header: `data/maps/maps.asm:198` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_21, MUSIC_ROUTE_3, FALSE (phone calls allowed), PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:182` `map_const ROUTE_21, 10, 18` (group `CINNABAR` = 6, map id 7). 10x18 blocks = 20x36 tile coordinates. -- Attributes / border: `data/maps/attributes.asm:274` `map_attributes Route21, ROUTE_21, $43` -- Connections: north `PALLET_TOWN` (offset 0), south `CINNABAR_ISLAND` (offset 0) -- Landmark position: `data/maps/landmarks.asm:96` `landmark 52, 120, Route21Name` - -**Warps** (`def_warp_events`) - -None (`def_warp_events` at `maps/Route21.asm:95` is empty). - -**Coord events** (`def_coord_events`) - -None (`maps/Route21.asm:97`). - -**BG events** (`def_bg_events`) - -None (`maps/Route21.asm:99`). - -**Object events** (`def_object_events`, `maps/Route21.asm:101-104`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE21_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 11 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfNikki` | -1 (always) | -| `ROUTE21_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 2 | 30 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSwimmermSeth` | -1 (always) | -| `ROUTE21_FISHER` | `SPRITE_FISHER` | 14 | 22 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 1 | `TrainerFisherArnold` | -1 (always) | - -The walkthrough's order (Nikki, then the fisherman, then "head way left then down") is -exactly the y ordering 16 -> 22 -> 30, with Seth in the far south-west corner at x=2. - -**Scripts of interest** - -- `TrainerSwimmerfNikki` (`maps/Route21.asm:22`, sym `4e:4803`) - - `trainer SWIMMERF, NIKKI, EVENT_BEAT_SWIMMERF_NIKKI, SwimmerfNikkiSeenText, SwimmerfNikkiBeatenText, 0, .Script`. - The `0` is the loss-text slot (no whiteout text). `.Script` is `endifjustbattled` + - one after-battle line; no items, no flags beyond the beat flag. -- `TrainerFisherArnold` (`maps/Route21.asm:33`, sym `4e:4817`) - same shape, - `EVENT_BEAT_FISHER_ARNOLD`. -- `TrainerSwimmermSeth` (`maps/Route21.asm:11`, sym `4e:47ef`) - same shape, - `EVENT_BEAT_SWIMMERM_SETH`. - -There are no scene scripts and no callbacks on this map (`def_scene_scripts` / -`def_callbacks` at `maps/Route21.asm:7-9` are both empty). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_SWIMMERF_NIKKI` | `constants/event_flags.asm:490` | `trainer` macro row, `maps/Route21.asm:23` | set once Nikki is beaten; suppresses re-battle | -| `EVENT_BEAT_FISHER_ARNOLD` | `constants/event_flags.asm:587` | `maps/Route21.asm:34` | as above | -| `EVENT_BEAT_SWIMMERM_SETH` | `constants/event_flags.asm:958` | `maps/Route21.asm:12` | as above | - -**Items** - -None on this map. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SWIMMERF, NIKKI` | SWIMMERF ($27) | 17 | `; SWIMMERF (17)` at `parties.asm:1872`: L28 SEEL, L28 SEEL, L28 SEEL, L28 DEWGONG (`TRAINERTYPE_NORMAL`) | `TrainerSwimmerfNikki` | no | -| `FISHER, ARNOLD` | FISHER ($25) | 3 | `; FISHER (3)` at `parties.asm:1511`: L34 TENTACRUEL | `TrainerFisherArnold` | no | -| `SWIMMERM, SETH` | SWIMMERM ($26) | 18 | `; SWIMMERM (18)` at `parties.asm:1754`: L29 QUAGSIRE, L29 OCTILLERY, L32 QUAGSIRE | `TrainerSwimmermSeth` | no | - -Payouts (base reward x last-mon level x 4, see section 3 for the citation): -Nikki 5 x 28 x 4 = **560**, Arnold 10 x 34 x 4 = **1360**, Seth 2 x 32 x 4 = **256**. -All three match the walkthrough exactly. -Base rewards: `data/trainers/attributes.asm:223` (Fisher, 10), `:229` (Swimmerm, 2), -`:235` (Swimmerf, 5). - -**Wild encounters** - -- Water (`data/wild/kanto_water.asm:61-66`), 6% rate: L35 TENTACOOL / L30 TENTACOOL / L35 TENTACRUEL. -- Grass (`data/wild/kanto_grass.asm:1005-1031`), 6/6/6% morn/day/nite. There *is* a - grass table, on the small land tiles: - - morn: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 30 TANGELA, 28 MR__MIME, 28 MR__MIME - - day: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 28 MR__MIME, 30 MR__MIME, 30 MR__MIME - - nite: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 30 TANGELA, 28 MR__MIME, 28 MR__MIME -- Fishing: `FISHGROUP_OCEAN` (`data/wild/fish.asm:13`). Old rod `.Ocean_Old` (`:42`) - MAGIKARP/MAGIKARP/TENTACOOL L10; Good rod `.Ocean_Good` (`:46`) MAGIKARP L20 / - TENTACOOL L20 / CHINCHOU L20 / `time_group 2` = SHELLDER L20 day and nite - (`fish.asm:214`); Super rod `.Ocean_Super` (`:51`) CHINCHOU L40 / `time_group 3` = - SHELLDER L40 (`fish.asm:215`) / TENTACRUEL L40 / LANTURN L40. -- No headbutt tree rows and no rock smash on this map. - ---- - -### MAP_CINNABAR_ISLAND - -- Script: `maps/CinnabarIsland.asm` -- Blocks: `maps/CinnabarIsland.blk` -- Header: `data/maps/maps.asm:199` -> `TILESET_KANTO, TOWN, LANDMARK_CINNABAR_ISLAND, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:183` `map_const CINNABAR_ISLAND, 10, 9` (group `CINNABAR` = 6, map id 8). 20x18 tile coordinates. -- Attributes / border: `data/maps/attributes.asm:279` `map_attributes CinnabarIsland, CINNABAR_ISLAND, $43` -- Connections: north `ROUTE_21` (offset 0), east `ROUTE_20` (offset 0) -- Landmark: `data/maps/landmarks.asm:95` `landmark 52, 132, CinnabarIslandName` -- Fly / respawn point: `data/maps/flypoints.asm:28` `db LANDMARK_CINNABAR_ISLAND, SPAWN_CINNABAR`; - `data/maps/spawn_points.asm:24` `spawn CINNABAR_ISLAND, 11, 12` - -**Warps** (`def_warp_events`, `maps/CinnabarIsland.asm:131-132`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 11 | 11 | `CINNABAR_POKECENTER_1F` | 1 | - -**Coord events** (`def_coord_events`) - -None (`maps/CinnabarIsland.asm:134`). Blue is a talk-to NPC, not a trip-wire. - -**BG events** (`def_bg_events`, `maps/CinnabarIsland.asm:136-140`) - -| x | y | type | script/item | -|---|---|---|---| -| 12 | 11 | `BGEVENT_READ` | `CinnabarIslandPokecenterSign` -> `jumpstd PokecenterSignScript` | -| 9 | 11 | `BGEVENT_READ` | `CinnabarIslandGymSign` -> "CINNABAR GYM has relocated to SEAFOAM ISLANDS. BLAINE" | -| 7 | 7 | `BGEVENT_READ` | `CinnabarIslandSign` -> town sign | -| 9 | 1 | `BGEVENT_ITEM` | `CinnabarIslandHiddenRareCandy` -> `hiddenitem RARE_CANDY, EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` (sym `4e:49a2`) | - -**Object events** (`def_object_events`, `maps/CinnabarIsland.asm:142-143`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `CINNABARISLAND_BLUE` | `SPRITE_BLUE` | 9 | 6 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | 0 | `CinnabarIslandBlue` | `EVENT_BLUE_IN_CINNABAR` | - -**Scripts of interest** - -- `CinnabarIslandFlypointCallback` (`maps/CinnabarIsland.asm:10`, sym `4e:4981`), wired - as `callback MAPCALLBACK_NEWMAP` at `:8`. Body is exactly - `setflag ENGINE_FLYPOINT_CINNABAR` / `endcallback`. Walking in once is the whole - unlock for flying here. -- `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:14`, sym `4e:4985`). Control flow, in - order: `faceplayer`, `opentext`, `writetext CinnabarIslandBlueText`, `waitbutton`, - `closetext`, `playsound SFX_WARP_TO`, - `applymovement CINNABARISLAND_BLUE, CinnabarIslandBlueTeleport`, - `disappear CINNABARISLAND_BLUE`, `clearevent EVENT_VIRIDIAN_GYM_BLUE`, `end`. - - There is **no guard flag and no `iftrue`** at the top: the script is one-shot only - because `disappear` sets the object's own `EVENT_BLUE_IN_CINNABAR` (see - `Script_disappear` at `engine/overworld/scripting.asm:887`, `ld b, 1 ; set`). - - `CinnabarIslandBlueTeleport` (`:38`) is a two-byte movement stream: - `teleport_from` (`$4c`, `macros/scripts/movement.asm:148`) then `step_end`. That is - the "levitates and flies off" the walkthrough describes. - - `clearevent EVENT_VIRIDIAN_GYM_BLUE` is the load-bearing side effect: that flag is - set at boot by `InitializeEventsScript` (`engine/events/std_scripts.asm:552`) and it - hides both `VIRIDIANGYM_BLUE` and the Viridian gym guide - (`maps/ViridianGym.asm:183-184`). **Talking to Blue here is the only thing that - puts Blue in Viridian Gym.** -- `CinnabarIslandHiddenRareCandy` (`maps/CinnabarIsland.asm:35`) - a `hiddenitem` - operand, not a script body: `dwb EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY, RARE_CANDY`. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_CINNABAR` | `constants/engine_flags.asm:77` | set by `CinnabarIslandFlypointCallback` | Fly destination unlocked; also the wVisitedSpawns bit for respawn | -| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | *set* implicitly by `disappear CINNABARISLAND_BLUE`; read by `maps/ViridianCity.asm:37` and `maps/ViridianPokecenter1F.asm:18` for "Blue returned" dialogue | clear = Blue is standing on Cinnabar; set = you have heard the speech | -| `EVENT_VIRIDIAN_GYM_BLUE` | `constants/event_flags.asm:1304` | set at `engine/events/std_scripts.asm:552`; cleared by `CinnabarIslandBlue` | while set, Viridian Gym is empty | -| `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` | `constants/event_flags.asm:254` | the `hiddenitem` at `maps/CinnabarIsland.asm:36` | one-shot guard on the hidden candy | - -Note: `EVENT_BLUE_IN_CINNABAR` is **not** in the `InitializeEventsScript` list, so it -starts clear and Blue is visible from the first time you reach the island. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `RARE_CANDY` | face tile (9,1) and press A (or ITEMFINDER) | `bg_event 9, 1, BGEVENT_ITEM, CinnabarIslandHiddenRareCandy` | `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` | - -The walkthrough never mentions this candy. - -**Trainers** - -None. - -**Wild encounters** - -No `def_grass_wildmons`/`def_water_wildmons` row for `CINNABAR_ISLAND` in -`data/wild/kanto_grass.asm` or `data/wild/kanto_water.asm`. The surrounding water belongs -to Route 20 / Route 21. Fishing on the map's own water uses `FISHGROUP_OCEAN` -(`data/maps/maps.asm:199`). - ---- - -### MAP_CINNABAR_POKECENTER_1F - -- Script: `maps/CinnabarPokecenter1F.asm` -- Header: `data/maps/maps.asm:192` -> `TILESET_POKECENTER, INDOOR, LANDMARK_CINNABAR_ISLAND, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:176` `map_const CINNABAR_POKECENTER_1F, 5, 4` (group `CINNABAR` = 6, map id 1) -- Attributes: `data/maps/attributes.asm:510` `map_attributes CinnabarPokecenter1F, CINNABAR_POKECENTER_1F, $00`; no connections - -**Warps** (`def_warp_events`, `maps/CinnabarPokecenter1F.asm:38-41`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `CINNABAR_ISLAND` | 1 | -| 2 | 4 | 7 | `CINNABAR_ISLAND` | 1 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events / BG events** - -None (`maps/CinnabarPokecenter1F.asm:43`, `:45`). - -**Object events** (`maps/CinnabarPokecenter1F.asm:47-50`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `CINNABARPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` | -1 | -| `CINNABARPOKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius x 2) | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FCooltrainerFScript` (BLAINE lives in the Seafoam cave) | -1 | -| `CINNABARPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 2 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FFisherScript` ("It's been a year since the volcano erupted.") | -1 | - -Both flavour NPCs are unconditional `jumptextfaceplayer` - no flags, no branches. These -are the two people the walkthrough tells you to talk to. - ---- - -### MAP_ROUTE_20 - -- Script: `maps/Route20.asm` -- Blocks: `maps/Route20.blk` -- Header: `data/maps/maps.asm:197` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_20, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:181` `map_const ROUTE_20, 30, 9` (group `CINNABAR` = 6, map id 6). 60x18 tile coordinates. -- Attributes / border: `data/maps/attributes.asm:283` `map_attributes Route20, ROUTE_20, $43` -- Connections: west `CINNABAR_ISLAND` (offset 0), east `ROUTE_19` (offset -9) -- Landmarks in view: `data/maps/landmarks.asm:93` Route 20, `:94` `SeafoamIslandsName` - -**Warps** (`def_warp_events`, `maps/Route20.asm:116-117`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 38 | 7 | `SEAFOAM_GYM` | 1 | - -**Coord events** - -None (`maps/Route20.asm:119`). - -**BG events** (`maps/Route20.asm:121-122`) - -| x | y | type | script/item | -|---|---|---|---| -| 37 | 11 | `BGEVENT_READ` | `CinnabarGymSign` -> "CINNABAR GYM / LEADER: BLAINE" | - -**Object events** (`maps/Route20.asm:124-127`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `ROUTE20_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 52 | 8 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfNicole` | -1 | -| `ROUTE20_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 45 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfLori` | -1 | -| `ROUTE20_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 12 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermCameron` | -1 | - -Cameron at x=12 is the only one west of the gym door at x=38, which is why the -walkthrough sees "one trainer" on the way in. Lori (45) and Nicole (52) are east of it. - -**Scripts of interest** - -- `Route20ClearRocksCallback` (`maps/Route20.asm:12`, sym `4e:4cfa`), wired as - `callback MAPCALLBACK_NEWMAP` at `:10`. Body is `setevent EVENT_CINNABAR_ROCKS_CLEARED` - / `endcallback`. It fires silently the first time the map loads. - Consumers: `Route19ClearRocksCallback` (`maps/Route19.asm:15`, sym `4e:4f09`, a - `MAPCALLBACK_TILES` callback) draws six `changeblock ... $7a ; rock` blocks at - (6,6), (8,6), (10,6), (12,8), (4,8), (10,10) **while the flag is clear**; the officer - in `maps/Route19FuchsiaGate.asm:12` and the two fishers in `maps/Route19.asm:74`/`:90` - swap dialogue on it. -- `TrainerSwimmermCameron` (`maps/Route20.asm:38`, sym `4e:4d26`) - plain trainer, - `EVENT_BEAT_SWIMMERM_CAMERON`, `endifjustbattled` + one line. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | set by `Route20ClearRocksCallback`; read by `maps/Route19.asm:16`, `:74`, `:90` and `maps/Route19FuchsiaGate.asm:12` | while clear, six rock blocks seal the Route 19 water path from the Fuchsia side | -| `EVENT_BEAT_SWIMMERM_CAMERON` | `constants/event_flags.asm:957` | `maps/Route20.asm:39` | beat flag | -| `EVENT_BEAT_SWIMMERF_NICOLE` | `constants/event_flags.asm:487` | `maps/Route20.asm:17` | beat flag (next section) | -| `EVENT_BEAT_SWIMMERF_LORI` | `constants/event_flags.asm:488` | `maps/Route20.asm:28` | beat flag (next section) | - -**Items** - -None on this map. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SWIMMERM, CAMERON` | SWIMMERM ($26) | 17 | `; SWIMMERM (17)` at `parties.asm:1749`: L34 MARILL | `TrainerSwimmermCameron` | no | -| `SWIMMERF, NICOLE` | SWIMMERF ($27) | 14 | `; SWIMMERF (14)` at `parties.asm:1854`: L29 MARILL, L29 MARILL, L32 LAPRAS | `TrainerSwimmerfNicole` | no | -| `SWIMMERF, LORI` | SWIMMERF ($27) | 15 | `; SWIMMERF (15)` at `parties.asm:1861`: L32 STARMIE, L32 STARMIE | `TrainerSwimmerfLori` | no | - -Cameron pays 2 x 34 x 4 = **272**, matching the walkthrough. - -**Wild encounters** - -- Water (`data/wild/kanto_water.asm:54-59`), 6% rate: L35 TENTACOOL / L30 TENTACOOL / L35 TENTACRUEL. -- No `def_grass_wildmons ROUTE_20` row exists in `data/wild/kanto_grass.asm` - there is - no land grass on this route. -- Fishing: `FISHGROUP_OCEAN`, same three rod tables as Route 21 above. - ---- - -### MAP_SEAFOAM_GYM - -- Script: `maps/SeafoamGym.asm` -- Blocks: `maps/SeafoamGym.blk` -- Header: `data/maps/maps.asm:195` -> `TILESET_CAVE, INDOOR, LANDMARK_SEAFOAM_ISLANDS, MUSIC_GYM, TRUE (phone calls blocked), PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:179` `map_const SEAFOAM_GYM, 5, 4` (group `CINNABAR` = 6, map id 4). 10x8 tile coordinates - smaller than the 20x18 screen, which is the walkthrough's "this gym doesn't even fit the entire screen". -- Attributes / border: `data/maps/attributes.asm:513` `map_attributes SeafoamGym, SEAFOAM_GYM, $09`; no connections -- There is **no** `MAP_SEAFOAM_ISLANDS` dungeon in Gen 2 - `constants/map_constants.asm` - has only `SEAFOAM_GYM` in the whole Seafoam family. - -**Warps** (`def_warp_events`, `maps/SeafoamGym.asm:161-162`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `ROUTE_20` | 1 | - -**Coord events** - -None (`maps/SeafoamGym.asm:164`). The map does declare one scene script, -`scene_script SeafoamGymNoopScene` (`:7`), and the asm itself annotates it -`; unusable` - the body at `:11` is a bare `end`. - -**BG events** - -None (`maps/SeafoamGym.asm:166`). - -**Object events** (`maps/SeafoamGym.asm:168-170`) - -| const | sprite | x | y | movement | type | sight | script label | event flag | -|---|---|---|---|---|---|---|---|---| -| `SEAFOAMGYM_BLAINE` | `SPRITE_BLAINE` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SeafoamGymBlaineScript` | -1 (always present) | -| `SEAFOAMGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 6 | 5 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | 0 | `SeafoamGymGuideScript` | `EVENT_SEAFOAM_GYM_GYM_GUIDE` | - -**Scripts of interest** - -- `SeafoamGymBlaineScript` (`maps/SeafoamGym.asm:14`, sym `53:516d`). Control flow: - 1. `faceplayer`, `opentext` - 2. `checkflag ENGINE_VOLCANOBADGE` / `iftrue .FightDone` - the entire re-talk guard is - the badge engine flag, **not** an event flag. - 3. `writetext BlaineIntroText`, `waitbutton`, `closetext` - 4. `winlosstext BlaineWinLossText, 0` (no loss text) - 5. `loadtrainer BLAINE, BLAINE1`, `startbattle` - 6. `iftrue .ReturnAfterBattle` - i.e. if the battle result is non-zero (you lost / - drew) it *skips* `appear SEAFOAMGYM_GYM_GUIDE` - 7. `appear SEAFOAMGYM_GYM_GUIDE` - clears `EVENT_SEAFOAM_GYM_GYM_GUIDE`, which is how - the guide "comes in late" - 8. `.ReturnAfterBattle`: `reloadmapafterbattle`, `setevent EVENT_BEAT_BLAINE`, - `opentext`, `writetext ReceivedVolcanoBadgeText`, `playsound SFX_GET_BADGE`, - `waitsfx`, **`setflag ENGINE_VOLCANOBADGE`**, `writetext BlaineAfterBattleText`, - `waitbutton`, `closetext`, `end` - - No `verbosegiveitem` anywhere: **Blaine gives no TM**, only the badge. - - Note the ordering quirk: `setevent EVENT_BEAT_BLAINE` / `setflag ENGINE_VOLCANOBADGE` - are on the shared `.ReturnAfterBattle` path, reached whether or not you won, but - `startbattle` does not return to the script on a whiteout, so in practice this is - only reached after a win. -- `SeafoamGymGuideScript` (`maps/SeafoamGym.asm:46`, sym `53:51a3`) - branches on - `EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE`, first line is the apology - ("CINNABAR GYM was gone, so I didn't know where to find you"), then a second line - forever after. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_VOLCANOBADGE` | `constants/engine_flags.asm:53` (Kanto badge 7; `wKantoBadges` bit 6 per `constants/ram_constants.asm:270`) | checked at `maps/SeafoamGym.asm:17`, set at `:34` | the badge itself; also gates the re-talk branch | -| `EVENT_BEAT_BLAINE` | `constants/event_flags.asm:721` | set at `maps/SeafoamGym.asm:29` | not read by any other map in the checkout; it is the record, not the gate | -| `EVENT_SEAFOAM_GYM_GYM_GUIDE` | `constants/event_flags.asm:1305` | set at boot (`engine/events/std_scripts.asm:553`), cleared by `appear` at `maps/SeafoamGym.asm:26` | while set, the guide object does not exist | -| `EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE` | `constants/event_flags.asm:212` | `maps/SeafoamGym.asm:49`, `:54` | first-talk vs repeat text | - -What VOLCANOBADGE actually does mechanically: `data/types/badge_type_boosts.asm:19` -`db FIRE ; VOLCANOBADGE`, consumed by `DoBadgeTypeBoosts` (`engine/battle/misc.asm:146`, -called from `engine/battle/effect_commands.asm:1261`) - a +12.5% damage boost on the -player's FIRE-type moves. It grants no field move. - -**Items** - -None. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | -|---|---|---|---|---|---| -| `BLAINE, BLAINE1` | BLAINE ($2e), `constants/trainer_constants.asm:469-470` | 1 | `BlaineGroup` (sym `0e:6e70`) at `parties.asm:2303`, `TRAINERTYPE_MOVES`: **L45 MAGCARGO** (CURSE, SMOG, FLAMETHROWER, ROCK_SLIDE), **L45 MAGMAR** (THUNDERPUNCH, FIRE_PUNCH, SUNNY_DAY, CONFUSE_RAY), **L50 RAPIDASH** (QUICK_ATTACK, FIRE_SPIN, FURY_ATTACK, FIRE_BLAST) | `SeafoamGymBlaineScript` | no rematch, no phone | - -Class attributes (`data/trainers/attributes.asm:275-279`): AI items are -**`MAX_POTION, FULL_HEAL`** (not two Full Restores); base reward 25; AI mask -`AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`, -`CONTEXT_USE | SWITCH_SOMETIMES`. Payout 25 x 50 x 4 = **5000**, matching the walkthrough. -`BLAINE` is in `KantoGymLeaders` (`data/trainers/leaders.asm`), so the battle uses the -gym-leader music and awards `HAPPINESS_GYMBATTLE`. - -**Wild encounters** - -None. The map is `INDOOR` with `TILESET_CAVE`; its `FISHGROUP_SHORE` header entry is the -default and has no reachable water. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Everything in this section is across open sea (Pallet -> Route 21 -> Cinnabar -> Route 20 -> gym door) | `engine/events/overworld.asm:469` `TrySurfOW` (walk-into-water path): `ld de, ENGINE_FOGBADGE` / `CheckEngineFlag` at `:490`, then `ld d, SURF` / `CheckPartyMove`. Menu path is `SurfFunction` `.TrySurf` at `:340` with the same `ENGINE_FOGBADGE`. `CheckBadge` itself is at `:50`. | FOGBADGE + a party mon that knows SURF | already held long before Kanto (Johto badge 4) | -| Fly to Cinnabar Island | `data/maps/flypoints.asm:28`; the flag is `ENGINE_FLYPOINT_CINNABAR`, set by `CinnabarIslandFlypointCallback` (`maps/CinnabarIsland.asm:10`) | STORMBADGE + FLY + having stood on Cinnabar Island once | walk in once | -| Blue is not in Viridian Gym | `maps/ViridianGym.asm:183-184` object rows carry `EVENT_VIRIDIAN_GYM_BLUE`, set at `engine/events/std_scripts.asm:552` | - | `clearevent EVENT_VIRIDIAN_GYM_BLUE` at the tail of `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:23`) - **this section is a hard prerequisite for the Viridian Gym leader** | -| Route 19 (east of Route 20) is sealed by six boulder blocks | `maps/Route19.asm:15` `Route19ClearRocksCallback`, a `MAPCALLBACK_TILES` callback that lays `changeblock` rock blocks while `EVENT_CINNABAR_ROCKS_CLEARED` is clear | - | `setevent EVENT_CINNABAR_ROCKS_CLEARED` in `Route20ClearRocksCallback` (`maps/Route20.asm:13`), i.e. simply loading Route 20 once from the Cinnabar side | -| Blaine will not re-battle | `maps/SeafoamGym.asm:17` `checkflag ENGINE_VOLCANOBADGE` / `iftrue .FightDone` | - | none in vanilla; `.FightDone` is terminal | -| The gym guide does not exist until Blaine is beaten | object row event flag `EVENT_SEAFOAM_GYM_GYM_GUIDE` (`maps/SeafoamGym.asm:170`), set at `engine/events/std_scripts.asm:553` | beat Blaine | `appear SEAFOAMGYM_GYM_GUIDE` (`maps/SeafoamGym.asm:26`), only on the win branch | - -Nothing in this section needs CUT, STRENGTH, WHIRLPOOL, WATERFALL, FLASH or a key item. - -## 4. Bot checklist - -Ordered, machine-actionable. `(g,m)` is `(map group, map id)` from -`constants/map_constants.asm`. All coordinates are the raw asm map-event values. - -1. `PALLET_TOWN`: SURF south across the connection into `ROUTE_21 (6,7)`. - Pre: FOGBADGE + SURF in the party. Post: none. -2. `ROUTE_21`: battle `TrainerSwimmerfNikki` at (11,16) - sight radius 3, so she will - trigger on approach. Party L28 SEEL x3 + L28 DEWGONG. Post: `EVENT_BEAT_SWIMMERF_NIKKI`, +560 money. -3. `ROUTE_21`: battle `TrainerFisherArnold` at (14,22) - sight radius 1, faces up. - Party L34 TENTACRUEL. Post: `EVENT_BEAT_FISHER_ARNOLD`, +1360 money. -4. `ROUTE_21`: surf west then south to `TrainerSwimmermSeth` at (2,30) - sight radius 4, - faces right. Party L29 QUAGSIRE / L29 OCTILLERY / L32 QUAGSIRE. - Post: `EVENT_BEAT_SWIMMERM_SETH`, +256 money. - (Both trainers are avoidable - all three are `OBJECTTYPE_TRAINER` with a finite sight - radius, none are on a coord event.) -5. `ROUTE_21`: continue south across the connection into `CINNABAR_ISLAND (6,8)`. - Post: `MAPCALLBACK_NEWMAP` sets `ENGINE_FLYPOINT_CINNABAR`; respawn point becomes - `SPAWN_CINNABAR` = (11,12). -6. `CINNABAR_ISLAND`: step on warp 1 at (11,11) -> `CINNABAR_POKECENTER_1F (6,1)`. - Talk to `CINNABARPOKECENTER1F_NURSE` at (3,1) to heal. Optional: the two flavour NPCs - at (7,6) and (2,4). Exit via warp 1/2 at (3,7)/(4,7). -7. `CINNABAR_ISLAND`: optional - face (9,1) and press A for the hidden **RARE_CANDY**. - Pre: `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` clear + a free bag slot. - Post: flag set. (Not in the walkthrough.) -8. `CINNABAR_ISLAND`: walk to `CINNABARISLAND_BLUE` at (9,6) and talk. - Pre: `EVENT_BLUE_IN_CINNABAR` clear. Expect a long `writetext` (about 13 pages of - `para` breaks - budget A presses accordingly), then `SFX_WARP_TO` + a `teleport_from` - movement, then the object vanishes. - Post: `EVENT_BLUE_IN_CINNABAR` set (via `disappear`), `EVENT_VIRIDIAN_GYM_BLUE` - **cleared**. Do not skip this step: it is the only unlock for the Viridian Gym leader. -9. `CINNABAR_ISLAND`: optional signs at (12,11), (9,11), (7,7). -10. `CINNABAR_ISLAND`: SURF east across the connection into `ROUTE_20 (6,6)`. - Post: `MAPCALLBACK_NEWMAP` sets `EVENT_CINNABAR_ROCKS_CLEARED` (silent; unseals - Route 19 for the next section). -11. `ROUTE_20`: battle `TrainerSwimmermCameron` at (12,13) - sight radius 3. - Party L34 MARILL. Post: `EVENT_BEAT_SWIMMERM_CAMERON`, +272 money. -12. `ROUTE_20`: optional sign at (37,11). Then step onto warp 1 at (38,7) -> - `SEAFOAM_GYM (6,4)`. -13. `SEAFOAM_GYM`: walk to `SEAFOAMGYM_BLAINE` at (5,2) and talk. - Pre: `ENGINE_VOLCANOBADGE` clear (otherwise you get `.FightDone` only). - Battle: `loadtrainer BLAINE, BLAINE1` - L45 MAGCARGO / L45 MAGMAR / L50 RAPIDASH, - AI holds MAX_POTION + FULL_HEAL. - Post: `EVENT_BEAT_BLAINE` set, `EVENT_SEAFOAM_GYM_GYM_GUIDE` cleared (guide appears - at (6,5)), **`ENGINE_VOLCANOBADGE` set**, +5000 money. No TM, no item. -14. `SEAFOAM_GYM`: optional - talk to `SEAFOAMGYM_GYM_GUIDE` at (6,5). - Post: `EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE`. -15. `SEAFOAM_GYM`: exit via warp 1 at (5,5) back to `ROUTE_20` warp 1. - -Follow-up (next section): Route 20 continues east past Lori (45,13) and Nicole (52,8) -into `ROUTE_19`, whose rock blocks are now gone because step 10 set -`EVENT_CINNABAR_ROCKS_CLEARED`. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Kanto map headers, dimensions, connections, warp / coord / bg / object tables | `src/import/RomExtractorGen2.lua` (all 26 map groups, `MAP_GROUP_COUNT = 26` at `:47`, `MapGroupPointers` walk at `:728`) | implemented - fully data-driven, nothing Cinnabar-specific needed | -| `MAPCALLBACK_NEWMAP` (`CinnabarIslandFlypointCallback`, `Route20ClearRocksCallback`) | `src/world/gen2/World.lua:5659` `self:runMapCallback("MAPCALLBACK_NEWMAP")`, in `HandleNewMap` order (`:5646`) | implemented; asserted end-to-end by `tests/drivers/gold_map_callbacks.lua` | -| `MAPCALLBACK_TILES` + `changeblock` (the Route 19 rocks that this section unseals) | `src/world/gen2/World.lua:5662`; `changeblock` opcode at `src/script/gen2/Opcodes.lua:127` | implemented | -| `setflag ENGINE_FLYPOINT_CINNABAR` -> flyable | `src/world/gen2/FieldMoves.lua:367` (`LANDMARK_CINNABAR_ISLAND` / `SPAWN_CINNABAR` / flag 62), `World:setEngineFlag` at `src/world/gen2/World.lua:1326` | implemented | -| `checkevent` / `setevent` / `clearevent`, `checkflag` / `setflag` | `src/script/gen2/Vm.lua:187-229` | implemented - id-keyed stores, `engineFlags` kept separate from `wEventFlags` | -| `appear` / `disappear` writing the object's event flag (Blue vanishing, the guide arriving) | `src/script/gen2/Opcodes.lua:115-116`, handled in `src/script/gen2/Vm.lua` | implemented | -| Hidden item `BGEVENT_ITEM` (Cinnabar RARE_CANDY at 9,1) | `src/world/gen2/HiddenItems.lua` (`BGEVENT_ITEM = 7` at `:22`), extractor branch at `src/import/RomExtractorGen2.lua:2986` | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `endifjustbattled` (the whole Blaine script) | `src/script/gen2/Vm.lua:806`, `:817`, `:886`, `:918` | implemented, incl. the `iftrue` on `wBattleResult` that guards `appear SEAFOAMGYM_GYM_GUIDE` | -| Gym-leader battle music / victory music for BLAINE | `src/battle/gen2/BattleMusic.lua:21`, `src/battle/gen2/Battle.lua:82` (both list `BLAINE = true`) | implemented | -| Prize money = base x last-mon level x 4 (560 / 1360 / 256 / 272 / 5000) | `src/battle/gen2/Prize.lua` (header documents `ComputeTrainerReward` and the `ld c, 4` loop; `Prize.reward`) | implemented, incl. the Mom's-savings split | -| Trainer sight radii (3 / 4 / 1 on Route 21) | `src/world/gen2/Trainers.lua` | implemented | -| SURF gate on FOGBADGE + party move | `src/world/gen2/FieldMoves.lua:106` (`SURF = "FOG"`), `:481` `surfFromMenu`, `:678` overworld path | implemented; driven by `tests/drivers/gold_water_moves.lua` | -| Ocean wild/fish tables for Routes 20/21 | extracted by `src/import/RomExtractorGen2.lua`, consumed by `src/battle/gen2/Encounter.lua` | implemented - correct by construction from the ROM | -| Radio "Place & Name" un-hiding BLAINE after the Kanto badge sweep | `src/ui/gen2/Pokegear.lua:172` (`PNP_HIDDEN`), `:178` `PNP_HIDDEN_BEAT_KANTO`, `:1265` | implemented | -| `setflag ENGINE_VOLCANOBADGE` reaching the trainer card / `VAR_BADGES` count | `src/script/gen2/Vm.lua:208-229` writes `save.engineFlags[53]`; but `VAR_BADGES` (`src/world/gen2/World.lua:1243-1244`) counts `save.player.badges` + `save.player.kantoBadges`, and `src/ui/gen2/TrainerCard.lua:395` reads `player.badges`. Nothing in `src/` ever writes `player.kantoBadges` (only initialised at `src/core/gen2/Save.lua:119`). | **partial** - Blaine's own re-talk guard works (same `engineFlags` id is read back by `checkflag`), but the badge does not show on the trainer card and does not increment `VAR_BADGES` | -| `BadgeTypeBoosts` (VOLCANOBADGE = +12.5% FIRE damage) | no Gen 2 implementation - `grep -rn "badge" src/battle/gen2/` returns nothing; the only badge-boost code is the Gen 1 `src/battle/Damage.lua:35` | **missing** | -| `teleport_from` (`$4c`) movement for Blue's exit | `src/script/gen2/Movement.lua:22` `decodeByte` - `$4c` matches no family branch and falls through to `{ kind = "nop" }` | **partial** - Blue disappears with the sound but without the rise-and-vanish animation | -| End-to-end driver coverage for this stretch | `tests/drivers/gold_*.lua` (25 drivers, all Johto: boot, walk, warp, trainer, ice path, roamers, ...) | **missing** - no Cinnabar / Route 20 / Route 21 / Seafoam Gym driver exists | - -## 6. Unresolved / verify by hand - -- **Blaine's movesets in the walkthrough are not the pokegold movesets.** The FAQ - describes Magcargo with Yawn / Recover / Overheat, Magmar with Confuse Ray, and - Rapidash with Flare Blitz / Bounce. `data/trainers/parties.asm:2303-2308` gives - MAGCARGO (CURSE, SMOG, FLAMETHROWER, ROCK_SLIDE), MAGMAR (THUNDERPUNCH, FIRE_PUNCH, - SUNNY_DAY, CONFUSE_RAY) and RAPIDASH (QUICK_ATTACK, FIRE_SPIN, FURY_ATTACK, - FIRE_BLAST). Only CONFUSE_RAY overlaps; Overheat, Flare Blitz, Bounce and Yawn do not - exist in `constants/move_constants.asm` at all for this generation. **Species and - levels match exactly** (L45 / L45 / L50), so treat the strategy prose as written - against a different (modernised) build and the asm as authoritative. -- **"Blaine will have two Full Restores available."** `data/trainers/attributes.asm:276` - is `db MAX_POTION, FULL_HEAL`. Two different items, neither of them a Full Restore. - The count of *uses* is AI-driven, not a fixed two. -- **"You now have 15 badges!"** Nothing in the asm counts badges as an ordinal; Kanto - gyms have no enforced order (`ENGINE_VOLCANOBADGE` is one bit, - `constants/engine_flags.asm:53`). The 15 is walkthrough prose that assumes a specific - visiting order. -- **Route 21 wild list is abridged in the walkthrough.** The FAQ lists Tentacool and - Tangela. `data/wild/kanto_grass.asm:1005` also has **MR__MIME** in all three - time slots, and `data/wild/kanto_water.asm:61` also has **TENTACRUEL**. Route 20's - list is likewise incomplete (TENTACRUEL is there too). -- **"Surf south on Route 21 ... head way left then down."** The trainer coordinates - confirm the shape of the route (16 -> 22 -> 30, then far west), but the actual walkable - water path is block data in `maps/Route21.blk` and was not decoded here. -- **"You should see a small cave at the Seafoam Islands."** The only asm anchor is - `warp_event 38, 7` on `maps/Route20.asm:117` plus `bg_event 37, 11` for the gym sign. - The cave mouth graphic itself is block data in `maps/Route20.blk`; there is no - Seafoam Islands dungeon map in Gen 2 (`constants/map_constants.asm` has only - `SEAFOAM_GYM`), so the walkthrough's "Seafoam Islands" is a landmark name - (`data/maps/landmarks.asm:94` `LANDMARK_SEAFOAM_ISLANDS`) rather than a walkable - dungeon. -- **EXP figures.** The per-mon EXP numbers in the FAQ (600 for a L28 Seel, 1056 for a - L28 Dewgong, 1492 for a L34 Tentacruel) are consistent with the vanilla - `base_exp * level / 7` times the 1.5x trainer-battle bonus, but nothing in - `data/trainers/parties.asm` stores them and they still vary with participation, so - they are not reproducible from the asm as written. The **money** figures all reproduce - exactly and are recorded above. -- **`EVENT_BEAT_BLAINE` has no reader.** Grepping `maps/`, `data/`, `engine/`, `home/` - and `constants/` finds it only at its definition (`constants/event_flags.asm:721`) and - its single `setevent` (`maps/SeafoamGym.asm:29`). If the port needs a "Blaine beaten" - predicate for anything, `ENGINE_VOLCANOBADGE` is the flag the cart actually branches on. diff --git a/docs/gold-walkthrough/asm-walk/section-28-routes-19-20-and-viridian-city-gym.md b/docs/gold-walkthrough/asm-walk/section-28-routes-19-20-and-viridian-city-gym.md deleted file mode 100644 index f7f5e4bf..00000000 --- a/docs/gold-walkthrough/asm-walk/section-28-routes-19-20-and-viridian-city-gym.md +++ /dev/null @@ -1,460 +0,0 @@ -# Section 28 - Routes 19-20 and Viridian City Gym - -Source: `../section-28-routes-19-20-and-viridian-city-gym.txt` (the FAQ numbers this chapter "34 > Routes 19-20 and Viridian City Gym") -Maps covered: `MAP_ROUTE_20`, `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_VIRIDIAN_CITY`, `MAP_VIRIDIAN_POKECENTER_1F`, `MAP_VIRIDIAN_GYM` -Badges / key milestones in this section: EARTHBADGE (`ENGINE_EARTHBADGE`) from Blue in Viridian Gym, `EVENT_BEAT_BLUE`, five optional Swimmer battles on Routes 19/20. This is the 16th badge, which is what `maps/OaksLab.asm` reads to open Mt. Silver in a later section. - -Map group / id pairs a bot needs (from `constants/map_constants.asm`): - -| Map constant | group | id | -|---|---|---| -| `MAP_ROUTE_19_FUCHSIA_GATE` | 6 (`CINNABAR`, `constants/map_constants.asm:175`) | 3 | -| `MAP_ROUTE_19` | 6 | 5 | -| `MAP_ROUTE_20` | 6 | 6 | -| `MAP_VIRIDIAN_CITY` | 23 (`VIRIDIAN`, `constants/map_constants.asm:420`) | 3 | -| `MAP_VIRIDIAN_GYM` | 23 | 4 | -| `MAP_VIRIDIAN_POKECENTER_1F` | 23 | 9 | - -Coordinate convention reminder: `warp_event` / `coord_event` / `bg_event` / `object_event` all take `x, y` in map cell coordinates, 0-based from the top-left (`macros/scripts/maps.asm:63`, `:79`, `:97`, `:113`). A map that is `W, H` blocks is `2W, 2H` cells. `changeblock` also takes cell coordinates and halves them internally (`engine/overworld/scripting.asm:2030` `Script_changeblock`, `home/map.asm:2099` `GetBlockLocation`). - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_SEAFOAM_GYM` | `maps/SeafoamGym.asm` (previous section) | - | its exit warp back to Route 20 warp 1 | Blaine's gym sits on Route 20; the previous section ends there | -| 1 | `MAP_ROUTE_20` | `maps/Route20.asm` | Seafoam Gym exit -> Route 20 warp 1 `(38, 7)`, or the west connection from Cinnabar Island | east map connection to Route 19 (`data/maps/attributes.asm:285`, offset -9) | "East on Route 20 and to Route 19 are optional ... Surf down and right to face the swimmer in the water" (Lori, then Nicole) | -| 2 | `MAP_ROUTE_19` | `maps/Route19.asm` | west map connection from Route 20 (`data/maps/attributes.asm:289`, offset 9) | Fly (walkthrough), or warp 1 `(7, 3)` -> `ROUTE_19_FUCHSIA_GATE` warp 3, or the north connection to Fuchsia City | "You'll find a drowning guy next to a girl" (Tucker, Dawn, then Jerome). "Fly back to Viridian City instead" | -| 2a | `MAP_ROUTE_19_FUCHSIA_GATE` | `maps/Route19FuchsiaGate.asm` | Route 19 warp 1 | warps 1/2 `(4, 0)` / `(5, 0)` -> `FUCHSIA_CITY` warps 10 / 11 | Only referenced by the walkthrough's "blocked ... south of Fuchsia City" line; see section 6 | -| 3 | `MAP_VIRIDIAN_CITY` | `maps/ViridianCity.asm` | Fly (`ENGINE_FLYPOINT_VIRIDIAN`, spawn `(23, 26)`) | warp 5 `(23, 25)` -> Pokecenter; warp 1 `(32, 7)` -> gym | "fly back to Viridian City ... First, heal at the Pokémon Center" | -| 4 | `MAP_VIRIDIAN_POKECENTER_1F` | `maps/ViridianPokecenter1F.asm` | Viridian warp 5 | warps 1/2 `(3, 7)` / `(4, 7)` -> Viridian warp 5 | Heal before and after Blue | -| 5 | `MAP_VIRIDIAN_GYM` | `maps/ViridianGym.asm` | Viridian warp 1 `(32, 7)` | warps 1/2 `(4, 17)` / `(5, 17)` -> Viridian warp 1 | "GYM LEADER BLUE ... You get: Earth Badge, 5,800G" | - -Spillover: the closing line "There are still a few things you can do before you clear Kanto" points at `maps/OaksLab.asm:26` (`readvar VAR_BADGES` / `ifequal NUM_BADGES, .OpenMtSilver`), which belongs to the next section. Nothing past the Viridian Gym exit is described here. - -## 2. Maps - -### MAP_ROUTE_20 - -- Script: `maps/Route20.asm` -- Blocks: `maps/Route20.blk` (`data/maps/blocks.asm:118` `Route20_Blocks`) -- Header: `data/maps/maps.asm:197` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_20`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_OCEAN` -- Dimensions: `constants/map_constants.asm:181` `map_const ROUTE_20, 30, 9` (30x9 blocks = 60x18 cells) -- Connections: `data/maps/attributes.asm:283-285` - west `CinnabarIsland` (`CINNABAR_ISLAND`, offset 0), east `Route19` (`ROUTE_19`, offset -9). No north/south. -- Map events block: `Route20_MapEvents`, sym `4e:4ecd` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 38 | 7 | `SEAFOAM_GYM` | 1 | - -**Coord events** (`def_coord_events`) - none (`def_coord_events` is empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 37 | 11 | `BGEVENT_READ` | `CinnabarGymSign` (sym `4e:4d3a`) -> `CinnabarGymSignText` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE20_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 52 | 8 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_GREEN` | `TrainerSwimmerfNicole` (sym `4e:4cfe`) | -1 (always present) | -| `ROUTE20_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 45 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_GREEN` | `TrainerSwimmerfLori` (sym `4e:4d12`) | -1 | -| `ROUTE20_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 12 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermCameron` (sym `4e:4d26`) | -1 | - -**Scripts of interest** - -- `Route20_MapScripts` (`maps/Route20.asm:6`): no scene scripts; one callback, `callback MAPCALLBACK_NEWMAP, Route20ClearRocksCallback`. -- `Route20ClearRocksCallback` (`maps/Route20.asm:12`, sym `4e:4cfa`): two opcodes, `setevent EVENT_CINNABAR_ROCKS_CLEARED` then `endcallback`. This is the single most load-bearing fact on this map: merely *entering* Route 20 for the first time permanently clears the Route 19 boulders. There is no other setter of that flag anywhere in `maps/`. -- The three trainer scripts are the stock pattern: `trainer <CLASS>, <ID>, <EVENT>, SeenText, BeatenText, 0, .Script`, and `.Script` is `endifjustbattled / opentext / writetext <AfterBattleText> / waitbutton / closetext / end`. No items, no flags beyond the trainer's own beat flag. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | set by `Route20ClearRocksCallback`; read by `Route19ClearRocksCallback`, `Route19Fisher1Script`, `Route19Fisher2Script`, `Route19FuchsiaGateOfficerScript` | Once set, the six Route 19 boulder blocks are never placed | -| `EVENT_BEAT_SWIMMERF_NICOLE` | `constants/event_flags.asm:487` | `TrainerSwimmerfNicole` | One-time battle | -| `EVENT_BEAT_SWIMMERF_LORI` | `constants/event_flags.asm:488` | `TrainerSwimmerfLori` | One-time battle | -| `EVENT_BEAT_SWIMMERM_CAMERON` | `constants/event_flags.asm:957` | `TrainerSwimmermCameron` | One-time battle; the walkthrough never mentions him | - -**Items** - none on this map (the only `bg_event` is a readable sign; no `hiddenitem`, no item balls). - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SWIMMERF` / `LORI` | 27 (`constants/trainer_constants.asm:375`) | LORI = `SwimmerFGroup` entry 15 | `data/trainers/parties.asm:1781` `SwimmerFGroup` (sym `0e:69c7`), `TRAINERTYPE_NORMAL`: L32 STARMIE, L32 STARMIE | `TrainerSwimmerfLori` | none (no `phone_call` / rematch entry) | -| `SWIMMERF` / `NICOLE` | 27 | NICOLE = `SwimmerFGroup` entry 14 | `TRAINERTYPE_NORMAL`: L29 MARILL, L29 MARILL, L32 LAPRAS | `TrainerSwimmerfNicole` | none | -| `SWIMMERM` / `CAMERON` | 26 (`constants/trainer_constants.asm:352`) | CAMERON = `SwimmerMGroup` entry 17 | `data/trainers/parties.asm:1640` `SwimmerMGroup` (sym `0e:68a8`), `TRAINERTYPE_NORMAL`: L34 MARILL | `TrainerSwimmermCameron` | none | - -Prize money: `data/trainers/attributes.asm:227` gives class Swimmerm base reward 2, `:233` gives Swimmerf base reward 5, no held items. `engine/battle/read_trainer_party.asm:300` `ComputeTrainerReward` = base x level of the last mon, and `engine/battle/core.asm:2340-2362` pays that quarter out four times, so the wallet figure is `4 x base x level`: Lori and Nicole `4 x 5 x 32 = 640` (matches the FAQ), Cameron `4 x 2 x 34 = 272`. - -**Wild encounters** - -- Surfing: `data/wild/kanto_water.asm:54` `def_water_wildmons ROUTE_20`, encounter rate `6 percent`: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL. No morn/day/nite split (water tables have a single slot list). -- No entry in `data/wild/kanto_grass.asm` (the map has no grass), none in `data/wild/treemons.asm`. -- Fishing: header fish group is `FISHGROUP_OCEAN` (`data/maps/maps.asm:197`), `data/wild/fish.asm` `.Ocean_Old` / `.Ocean_Good` / `.Ocean_Super` (lines 42-55): Old = MAGIKARP/MAGIKARP/TENTACOOL L10; Good = MAGIKARP L20, TENTACOOL L20, CHINCHOU L20, time group 2; Super = CHINCHOU L40, time group 3, TENTACRUEL L40, LANTURN L40. - -### MAP_ROUTE_19 - -- Script: `maps/Route19.asm` -- Blocks: `maps/Route19.blk` (`data/maps/blocks.asm:48` `Route19_Blocks`) -- Header: `data/maps/maps.asm:196` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_19`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:180` `map_const ROUTE_19, 10, 18` (10x18 blocks = 20x36 cells) -- Connections: `data/maps/attributes.asm:287-289` - north `FuchsiaCity` (`FUCHSIA_CITY`, offset 0), west `Route20` (`ROUTE_20`, offset 9). No south/east. -- Map events block: `Route19_MapEvents`, sym `4e:535c` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 3 | `ROUTE_19_FUCHSIA_GATE` | 3 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 11 | 13 | `BGEVENT_READ` | `Route19Sign` (sym `4e:4fa0`) -> `Route19SignText` | -| 11 | 1 | `BGEVENT_READ` | `CarefulSwimmingSign` (sym `4e:4fa3`) -> `CarefulSwimmingSignText` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE19_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 9 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight **0**, `PAL_NPC_GREEN` | `TrainerSwimmerfDawn` (sym `4e:4f28`) | -1 | -| `ROUTE19_SWIMMER_GUY1` | `SPRITE_SWIMMER_GUY` | 13 | 28 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermHarold` (sym `4e:4f3c`) | -1 | -| `ROUTE19_SWIMMER_GUY2` | `SPRITE_SWIMMER_GUY` | 11 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermJerome` (sym `4e:4f50`) | -1 | -| `ROUTE19_SWIMMER_GUY3` | `SPRITE_SWIMMER_GUY` | 8 | 23 | `SPRITEMOVEDATA_STANDING_UP` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight **0**, `PAL_NPC_RED` | `TrainerSwimmermTucker` (sym `4e:4f64`) | -1 | -| `ROUTE19_FISHER1` | `SPRITE_FISHER` | 9 | 5 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 0,0) | `OBJECTTYPE_SCRIPT` (sight field 1, unused) | `Route19Fisher1Script` (sym `4e:4f78`) | -1 | -| `ROUTE19_FISHER2` | `SPRITE_FISHER` | 11 | 5 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` (sight field 1, unused) | `Route19Fisher2Script` (sym `4e:4f8c`) | -1 | - -Note for a bot: Tucker `(8, 23)` and Dawn `(9, 23)` both have sight range 0, so they never initiate a battle by line of sight. They must be talked to, which is exactly the walkthrough's "Talk to the guy for a battle" / "talk to the girl to battle her". Harold and Jerome have sight 3 and will pull the player in. - -**Scripts of interest** - -- `Route19_MapScripts` (`maps/Route19.asm:9`): no scene scripts; one callback, `callback MAPCALLBACK_TILES, Route19ClearRocksCallback`. -- `Route19ClearRocksCallback` (`maps/Route19.asm:15`, sym `4e:4f09`): `checkevent EVENT_CINNABAR_ROCKS_CLEARED` / `iftrue .Done`, otherwise six `changeblock` calls writing block `$7a` (a rock) at cells `(6, 6)`, `(8, 6)`, `(10, 6)`, `(12, 8)`, `(4, 8)`, `(10, 10)` - i.e. blocks `(3, 3)`, `(4, 3)`, `(5, 3)`, `(6, 4)`, `(2, 4)`, `(5, 5)`. Block `$7a` in `data/tilesets/kanto_collision.asm:123` is `tilecoll WALL, WALL, WALL, WALL`, so all four quadrants are solid; these are impassable terrain, not Strength boulders. Because this is `MAPCALLBACK_TILES` it re-runs on every map load, and because `EVENT_CINNABAR_ROCKS_CLEARED` is set by simply walking onto Route 20, by the time this section runs the rocks are already gone. -- `Route19Fisher1Script` / `Route19Fisher2Script` (`maps/Route19.asm:71` / `:87`): `faceplayer / opentext / checkevent EVENT_CINNABAR_ROCKS_CLEARED / iftrue .RocksCleared`. Flavor only, no flags written, no items. -- The four trainer scripts follow the same `endifjustbattled` pattern as Route 20's. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | read by `Route19ClearRocksCallback` and both Fisher scripts | Gate on whether the north half of Route 19 is walkable | -| `EVENT_BEAT_SWIMMERF_DAWN` | `constants/event_flags.asm:485` | `TrainerSwimmerfDawn` | One-time battle | -| `EVENT_BEAT_SWIMMERM_HAROLD` | `constants/event_flags.asm:941` | `TrainerSwimmermHarold` | One-time battle; not mentioned by the walkthrough | -| `EVENT_BEAT_SWIMMERM_JEROME` | `constants/event_flags.asm:954` | `TrainerSwimmermJerome` | One-time battle | -| `EVENT_BEAT_SWIMMERM_TUCKER` | `constants/event_flags.asm:955` | `TrainerSwimmermTucker` | One-time battle | - -**Items** - none. Both `bg_event`s are `BGEVENT_READ` signs; there is no `hiddenitem` and no item ball on this map. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `SWIMMERM` / `TUCKER` | 26 | `SwimmerMGroup` entry 15 | `TRAINERTYPE_NORMAL`: L30 SHELLDER, L34 CLOYSTER | `TrainerSwimmermTucker` | none | -| `SWIMMERF` / `DAWN` | 27 | `SwimmerFGroup` entry 12 | `TRAINERTYPE_NORMAL`: L34 SEAKING | `TrainerSwimmerfDawn` | none | -| `SWIMMERM` / `JEROME` | 26 | `SwimmerMGroup` entry 14 | `TRAINERTYPE_NORMAL`: L26 SEADRA, L28 TENTACOOL, L30 TENTACRUEL, L28 GOLDEEN | `TrainerSwimmermJerome` | none | -| `SWIMMERM` / `HAROLD` | 26 | `SwimmerMGroup` entry 1 | `TRAINERTYPE_NORMAL`: L32 REMORAID, L30 SEADRA | `TrainerSwimmermHarold` | none | - -Prize money using the same `4 x base x last-mon level` rule: Tucker `4 x 2 x 34 = 272`, Dawn `4 x 5 x 34 = 680`, Jerome `4 x 2 x 28 = 224`, Harold `4 x 2 x 30 = 240`. The first three match the FAQ exactly. - -**Wild encounters** - -- Surfing: `data/wild/kanto_water.asm:47` `def_water_wildmons ROUTE_19`, encounter rate `6 percent`: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL. -- No grass table, no headbutt table. -- Fishing: `FISHGROUP_SHORE` (`data/maps/maps.asm:196`), `data/wild/fish.asm` `.Shore_Old` / `.Shore_Good` / `.Shore_Super` (lines 28-40): Old = MAGIKARP/MAGIKARP/KRABBY L10; Good = MAGIKARP L20, KRABBY L20 (x2), time group 0; Super = KRABBY L40, time group 1, KRABBY L40, KINGLER L40. - -### MAP_ROUTE_19_FUCHSIA_GATE - -Included because the walkthrough's "blocked by those boulders south of Fuchsia City" line points at it. The walkthrough never actually enters it. - -- Script: `maps/Route19FuchsiaGate.asm` -- Blocks: `maps/Route19FuchsiaGate.blk` (`data/maps/blocks.asm:204` `Route19FuchsiaGate_Blocks`) -- Header: `data/maps/maps.asm:194` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_19`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:178` `map_const ROUTE_19_FUCHSIA_GATE, 5, 4` (10x8 cells) -- Connections: none (`data/maps/attributes.asm:512` has no connection rows) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 0 | `FUCHSIA_CITY` | 10 | -| 2 | 5 | 0 | `FUCHSIA_CITY` | 11 | -| 3 | 4 | 7 | `ROUTE_19` | 1 | -| 4 | 5 | 7 | `ROUTE_19` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE19FUCHSIAGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 0,0) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `Route19FuchsiaGateOfficerScript` | -1 | - -**Scripts of interest** - -- `Route19FuchsiaGateOfficerScript` (`maps/Route19FuchsiaGate.asm:9`): `faceplayer / opentext / checkevent EVENT_CINNABAR_ROCKS_CLEARED / iftrue .RocksCleared`, then one of two texts. He is at `(0, 4)`, off to the side of the `(4, ...)` / `(5, ...)` walkway, so he blocks nothing. There is no coord event and no movement-blocking script in this gate: the officer is flavor, and the actual road closure is the `changeblock` rocks on Route 19 itself. - -### MAP_VIRIDIAN_CITY - -- Script: `maps/ViridianCity.asm` -- Blocks: `maps/ViridianCity.blk` (`data/maps/blocks.asm:235` `ViridianCity_Blocks`) -- Header: `data/maps/maps.asm:457` -> `TILESET_KANTO`, `TOWN`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:423` `map_const VIRIDIAN_CITY, 20, 18` (40x36 cells) -- Connections: `data/maps/attributes.asm:259-261` - north `Route2` (`ROUTE_2`, offset 5), south `Route1` (`ROUTE_1`, offset 10), west `Route22` (`ROUTE_22`, offset 4). No east. -- Map events block: `ViridianCity_MapEvents`, sym `4e:4486` -- Fly target: `data/maps/flypoints.asm:19` `db LANDMARK_VIRIDIAN_CITY, SPAWN_VIRIDIAN`; `data/maps/spawn_points.asm:15` `spawn VIRIDIAN_CITY, 23, 26` (the Pokecenter doorstep) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 32 | 7 | `VIRIDIAN_GYM` | 1 | -| 2 | 21 | 9 | `VIRIDIAN_NICKNAME_SPEECH_HOUSE` | 1 | -| 3 | 23 | 15 | `TRAINER_HOUSE_1F` | 1 | -| 4 | 29 | 19 | `VIRIDIAN_MART` | 2 | -| 5 | 23 | 25 | `VIRIDIAN_POKECENTER_1F` | 1 | - -**Coord events** (`def_coord_events`) - none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 17 | `BGEVENT_READ` | `ViridianCitySign` | -| 27 | 7 | `BGEVENT_READ` | `ViridianGymSign` (sym `4e:4053`) - "LEADER: ... The rest of the text is illegible" | -| 19 | 1 | `BGEVENT_READ` | `ViridianCityWelcomeSign` | -| 21 | 15 | `BGEVENT_READ` | `TrainerHouseSign` | -| 24 | 25 | `BGEVENT_READ` | `ViridianCityPokecenterSign` (`jumpstd PokecenterSignScript`) | -| 30 | 19 | `BGEVENT_READ` | `ViridianCityMartSign` (`jumpstd MartSignScript`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIRIDIANCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 5 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `ViridianCityCoffeeGramps` | -1 | -| `VIRIDIANCITY_GRAMPS2` | `SPRITE_GRAMPS` | 30 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianCityGrampsNearGym` (sym `4e:401e`) | -1 | -| `VIRIDIANCITY_FISHER` | `SPRITE_FISHER` | 6 | 23 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `ViridianCityDreamEaterFisher` (sym `4e:4032`) | -1 | -| `VIRIDIANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 21 | `SPRITEMOVEDATA_WANDER` (radius 3,3) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_GREEN` | `ViridianCityYoungsterScript` | -1 | - -**Scripts of interest** - -- `ViridianCityFlypointCallback` (`maps/ViridianCity.asm:13`, sym `4e:4005`), hooked as `callback MAPCALLBACK_NEWMAP`: `setflag ENGINE_FLYPOINT_VIRIDIAN / endcallback`. A bot cannot Fly here until it has physically entered Viridian City once. -- `ViridianCityGrampsNearGym` (`maps/ViridianCity.asm:34`): `checkevent EVENT_BLUE_IN_CINNABAR`; if true, the "Are you going to battle the LEADER?" line. This is a free read-out of whether Blue has been moved into the gym - see the gate table. -- `ViridianCityDreamEaterFisher` (`maps/ViridianCity.asm:50`): `checkevent EVENT_GOT_TM42_DREAM_EATER / iftrue .GotDreamEater`, else `writetext ... / promptbutton / verbosegiveitem TM_DREAM_EATER / iffalse .NoRoomForDreamEater / setevent EVENT_GOT_TM42_DREAM_EATER`. The walkthrough never mentions this; it is a free TM on the map the walkthrough tells you to fly to. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_VIRIDIAN` | `constants/engine_flags.asm:68` | set by `ViridianCityFlypointCallback` | Precondition for the walkthrough's "fly back to Viridian City" | -| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | read by `ViridianCityGrampsNearGym` and `ViridianPokecenter1FCooltrainerMScript`; it is also `CINNABARISLAND_BLUE`'s object event flag | While clear, Blue is standing on Cinnabar Island and has not yet moved to the gym | -| `EVENT_GOT_TM42_DREAM_EATER` | `constants/event_flags.asm:223` | `ViridianCityDreamEaterFisher` | One-time TM42 | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_DREAM_EATER` (TM42) | Talk to `VIRIDIANCITY_FISHER` at `(6, 23)` | `ViridianCityDreamEaterFisher` `verbosegiveitem` | `EVENT_GOT_TM42_DREAM_EATER` | - -**Trainers** - none on the overworld map. - -**Wild encounters** - no `kanto_grass.asm` or `kanto_water.asm` entry for `VIRIDIAN_CITY`. Fishing group is `FISHGROUP_POND` (`data/maps/maps.asm:457`). - -### MAP_VIRIDIAN_POKECENTER_1F - -- Script: `maps/ViridianPokecenter1F.asm` -- Blocks: `data/maps/blocks.asm:361` `ViridianPokecenter1F_Blocks` -- Header: `data/maps/maps.asm:463` -> `TILESET_POKECENTER`, `INDOOR`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:429` `map_const VIRIDIAN_POKECENTER_1F, 5, 4` (10x8 cells) -- Connections: none -- Respawn point: `data/maps/spawn_points.asm:12` `spawn VIRIDIAN_POKECENTER_1F, 5, 3` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `VIRIDIAN_CITY` | 5 | -| 2 | 4 | 7 | `VIRIDIAN_CITY` | 5 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events** - none. **BG events** - none. - -**Object events** - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIRIDIANPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianPokecenter1FNurseScript` | -1 | -| `VIRIDIANPOKECENTER1F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 8 | 4 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `ViridianPokecenter1FCooltrainerMScript` | -1 | -| `VIRIDIANPOKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 5 | 3 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianPokecenter1FCooltrainerFScript` | -1 | -| `VIRIDIANPOKECENTER1F_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 1 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_GREEN` | `ViridianPokecenter1FBugCatcherScript` | -1 | - -**Scripts of interest** - -- `ViridianPokecenter1FNurseScript` (`maps/ViridianPokecenter1F.asm:12`): `jumpstd PokecenterNurseScript` (`engine/events/std_scripts.asm:54`). This is the "heal at the Pokémon Center" beat, both before and after Blue. -- `ViridianPokecenter1FCooltrainerMScript` (`maps/ViridianPokecenter1F.asm:15`): another `checkevent EVENT_BLUE_IN_CINNABAR` read-out ("There are no GYM TRAINERS at the VIRIDIAN GYM"), which corroborates the walkthrough's "There aren't any other trainers in this gym". - -### MAP_VIRIDIAN_GYM - -- Script: `maps/ViridianGym.asm` -- Blocks: `maps/ViridianGym.blk` (`data/maps/blocks.asm:877` `ViridianGym_Blocks`) -- Header: `data/maps/maps.asm:458` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`, `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:424` `map_const VIRIDIAN_GYM, 5, 9` (10x18 cells) -- Connections: none (`data/maps/attributes.asm:657`) -- Map events block: `ViridianGym_MapEvents`, sym `5f:43de` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 17 | `VIRIDIAN_CITY` | 1 | -| 2 | 5 | 17 | `VIRIDIAN_CITY` | 1 | - -**Coord events** (`def_coord_events`) - none. There is no trip-wire in this gym; Blue is a pure talk-to trigger. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 3 | 13 | `BGEVENT_READ` | `ViridianGymStatue` (sym `5f:4047`) | -| 6 | 13 | `BGEVENT_READ` | `ViridianGymStatue` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIRIDIANGYM_BLUE` | `SPRITE_BLUE` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 0,0) | `OBJECTTYPE_SCRIPT`, palette 0 | `ViridianGymBlueScript` (sym `5f:4002`) | `EVENT_VIRIDIAN_GYM_BLUE` | -| `VIRIDIANGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianGymGuideScript` (sym `5f:4033`) | `EVENT_VIRIDIAN_GYM_BLUE` | - -Both objects carry `EVENT_VIRIDIAN_GYM_BLUE` as their event flag, so while that flag is **set** the gym is completely empty. `EVENT_VIRIDIAN_GYM_BLUE` is set by `InitializeEventsScript` at new game (`engine/events/std_scripts.asm:438`, the `setevent` on line 552) and is cleared only by `CinnabarIslandBlue`. - -**Scripts of interest** - -- `ViridianGymBlueScript` (`maps/ViridianGym.asm:10`, sym `5f:4002`), the whole badge beat, in order: - 1. `faceplayer` / `opentext` - 2. `checkflag ENGINE_EARTHBADGE` / `iftrue .FightDone` (sym `5f:402d`) - repeat visits get `LeaderBlueEpilogueText` only - 3. `writetext LeaderBlueBeforeText` / `waitbutton` / `closetext` - 4. `winlosstext LeaderBlueWinText, 0` (loss text pointer 0 = use the default) - 5. `loadtrainer BLUE, BLUE1` / `startbattle` / `reloadmapafterbattle` - 6. `setevent EVENT_BEAT_BLUE` - 7. `opentext` / `writetext Text_ReceivedEarthBadge` / `playsound SFX_GET_BADGE` / `waitsfx` - 8. `setflag ENGINE_EARTHBADGE` - 9. `writetext LeaderBlueAfterText` / `waitbutton` / `closetext` / `end` - - No `verbosegiveitem`: unlike Johto leaders, the Kanto leaders in GSC hand out no TM. The badge itself is the only reward besides money. -- `ViridianGymGuideScript` (`maps/ViridianGym.asm:39`, sym `5f:4033`): `checkevent EVENT_BEAT_BLUE / iftrue .ViridianGymGuideWinScript`. Pure flavor, no `learnmove`, no items. -- `ViridianGymStatue` (`maps/ViridianGym.asm:55`, sym `5f:4047`): `checkflag ENGINE_EARTHBADGE / iftrue .Beaten`; before the badge, `jumpstd GymStatue1Script` (`engine/events/std_scripts.asm:638`), after it `gettrainername STRING_BUFFER_4, BLUE, BLUE1` then `jumpstd GymStatue2Script` (`:646`). A cheap read-back a bot can use to confirm the badge landed. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_EARTHBADGE` | `constants/engine_flags.asm:54` (in `wKantoBadges`) | read by `ViridianGymBlueScript` and `ViridianGymStatue`; set by `ViridianGymBlueScript` | The 16th badge. Post-condition of this section | -| `EVENT_BEAT_BLUE` | `constants/event_flags.asm:722` | set by `ViridianGymBlueScript`, read by `ViridianGymGuideScript` | Set *before* the badge flag, between `reloadmapafterbattle` and `playsound` | -| `EVENT_VIRIDIAN_GYM_BLUE` | `constants/event_flags.asm:1304` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:552`); cleared by `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:23`) | While set, both gym objects are absent. This is the real gate on the whole section | -| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | object flag of `CINNABARISLAND_BLUE` (`maps/CinnabarIsland.asm:143`), set by that script's `disappear` | Once set, Blue is gone from Cinnabar and (necessarily) present in the gym | - -**Items** - none. No item balls, no `hiddenitem`, no TM reward. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `BLUE` / `BLUE1` | 40 (`constants/trainer_constants.asm:640`) | `BLUE1` (`:641`) | `data/trainers/parties.asm:3154` `BlueGroup` (sym `0e:75ff`), `TRAINERTYPE_MOVES` | `ViridianGymBlueScript` | none (no phone entry; the map header's phone column is `TRUE` but no `phone_call` targets him) | - -`BlueGroup` party, verbatim: - -| # | level | species | moves | -|---|---|---|---| -| 1 | 56 | PIDGEOT | QUICK_ATTACK, WHIRLWIND, WING_ATTACK, MIRROR_MOVE | -| 2 | 54 | ALAKAZAM | DISABLE, RECOVER, PSYCHIC_M, REFLECT | -| 3 | 56 | RHYDON | FURY_ATTACK, SANDSTORM, ROCK_SLIDE, EARTHQUAKE | -| 4 | 58 | GYARADOS | TWISTER, HYDRO_PUMP, RAIN_DANCE, HYPER_BEAM | -| 5 | 58 | EXEGGUTOR | SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM | -| 6 | 58 | ARCANINE | ROAR, SWIFT, FLAMETHROWER, EXTREMESPEED | - -Class attributes (`data/trainers/attributes.asm:383`): items `FULL_RESTORE, FULL_RESTORE` (the AI will use two Full Restores), base reward 25, AI `AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`, `CONTEXT_USE | SWITCH_SOMETIMES`. Payout `4 x 25 x 58 = 5800`, matching the FAQ. `BLUE` is listed in `KantoGymLeaders` (`data/trainers/leaders.asm:31`), which is what selects the gym-leader battle/victory music and awards `HAPPINESS_GYMBATTLE`. - -Walkthrough claims worth flagging against the party data: Pidgeot's flying move is `WING_ATTACK`, not Air Slash (which does not exist in Gen 2); Exeggutor's moves are `SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM`, so it has neither Psychic nor Hypnosis; Arcanine's switch move is `ROAR`, Pidgeot's is `WHIRLWIND`. - -**Wild encounters** - none (indoor map). - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Reaching Routes 19/20 at all | `engine/events/overworld.asm:322` `SurfFunction` -> `.TrySurf` (`ld de, ENGINE_FOGBADGE / call CheckBadge`), and the tile-facing path `engine/events/overworld.asm:469` `TrySurfOW` (`ENGINE_FOGBADGE` via `CheckEngineFlag`, then `CheckPartyMove` for `SURF`) | FOGBADGE + a party member knowing SURF | Both routes are open water; without Surf there is no approach | -| Boulders across northern Route 19 | `maps/Route19.asm:15` `Route19ClearRocksCallback`, six `changeblock ..., $7a` under `checkevent EVENT_CINNABAR_ROCKS_CLEARED`; block `$7a` is `WALL` on all four quadrants (`data/tilesets/kanto_collision.asm:123`) | `EVENT_CINNABAR_ROCKS_CLEARED` | Set unconditionally by `maps/Route20.asm:12` `Route20ClearRocksCallback` on `MAPCALLBACK_NEWMAP`, i.e. by setting foot on Route 20 | -| Blue is not in the Viridian Gym | Object event flag `EVENT_VIRIDIAN_GYM_BLUE` on both `maps/ViridianGym.asm:183-184` objects; set at new game by `engine/events/std_scripts.asm:552` inside `InitializeEventsScript` | Flag must be **clear** | `maps/CinnabarIsland.asm:14` `CinnabarIslandBlue`: talk to Blue on Cinnabar Island; the script runs `disappear CINNABARISLAND_BLUE` (setting `EVENT_BLUE_IN_CINNABAR`) then `clearevent EVENT_VIRIDIAN_GYM_BLUE`. This happens in an earlier section, but a bot that skipped it will find an empty gym | -| Flying to Viridian City | `maps/ViridianCity.asm:13` `ViridianCityFlypointCallback` (`setflag ENGINE_FLYPOINT_VIRIDIAN`), plus `engine/events/overworld.asm:529` `FlyFunction` (`ld de, ENGINE_STORMBADGE`) and the landmark row `data/maps/flypoints.asm:19` | STORMBADGE + a party member knowing FLY + Viridian visited once on foot | Walk into Viridian City once (Route 1 north, Route 2 south, or Route 22 east). Otherwise reach the gym overland | -| Re-fighting Blue | `maps/ViridianGym.asm:13` `checkflag ENGINE_EARTHBADGE / iftrue .FightDone` | - | Not possible; the battle is one-shot and there is no rematch or phone entry | -| (Next section) Mt. Silver | `maps/OaksLab.asm:26` `readvar VAR_BADGES / ifequal NUM_BADGES, .OpenMtSilver` -> `setevent EVENT_OPENED_MT_SILVER` | All 16 badges, EARTHBADGE last | Beating Blue here is what satisfies it | - -Note on the Route 19 approach: the officer in `maps/Route19FuchsiaGate.asm` never blocks a tile, and `maps/Route19.asm` has no coord events. There is no NPC blocker anywhere in this section - every obstacle is either terrain (`changeblock`) or a hidden object (`EVENT_VIRIDIAN_GYM_BLUE`). - -## 4. Bot checklist - -Preconditions for the whole section: `ENGINE_FOGBADGE` + SURF in party, `EVENT_VIRIDIAN_GYM_BLUE` clear, `ENGINE_FLYPOINT_VIRIDIAN` set (or an overland plan to Viridian). - -| # | Map | Target | Input intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_20` | anywhere on the map | enter the map (warp from Seafoam Gym warp 1 at `(38, 7)`, or the Cinnabar Island east connection) | - | `EVENT_CINNABAR_ROCKS_CLEARED` set by `Route20ClearRocksCallback` | -| 2 | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GIRL2` at `(45, 13)` | surf into her sight cone (range 3) or talk | `EVENT_BEAT_SWIMMERF_LORI` clear | battle Lori (2x L32 STARMIE); `EVENT_BEAT_SWIMMERF_LORI` set, +640 | -| 3 | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GIRL1` at `(52, 8)` | surf into sight (range 3) or talk | `EVENT_BEAT_SWIMMERF_NICOLE` clear | battle Nicole (L29 MARILL, L29 MARILL, L32 LAPRAS); flag set, +640 | -| 3b | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GUY` at `(12, 13)` | optional, west end, skipped by the FAQ | `EVENT_BEAT_SWIMMERM_CAMERON` clear | battle Cameron (L34 MARILL); flag set, +272 | -| 4 | `MAP_ROUTE_20` -> `MAP_ROUTE_19` | east map edge | surf east across the connection (offset -9) | - | now on `MAP_ROUTE_19` | -| 5 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY3` at `(8, 23)` | **talk** (sight 0, will not initiate) | `EVENT_BEAT_SWIMMERM_TUCKER` clear | battle Tucker (L30 SHELLDER, L34 CLOYSTER); flag set, +272 | -| 6 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY3` again | talk | `EVENT_BEAT_SWIMMERM_TUCKER` set | `SwimmermTuckerAfterBattleText` (the "drowning" line) | -| 7 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GIRL` at `(9, 23)` | **talk** (sight 0) | `EVENT_BEAT_SWIMMERF_DAWN` clear | battle Dawn (L34 SEAKING); flag set, +680 | -| 8 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY2` at `(11, 17)` | surf north into sight (range 3) or talk | `EVENT_BEAT_SWIMMERM_JEROME` clear | battle Jerome (L26 SEADRA, L28 TENTACOOL, L30 TENTACRUEL, L28 GOLDEEN); flag set, +224 | -| 8b | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY1` at `(13, 28)` | optional, south-east, not in the FAQ | `EVENT_BEAT_SWIMMERM_HAROLD` clear | battle Harold (L32 REMORAID, L30 SEADRA); flag set, +240 | -| 9 | `MAP_ROUTE_19` -> `MAP_VIRIDIAN_CITY` | Pokegear map -> Fly -> Viridian | use FLY | `ENGINE_STORMBADGE`, FLY in party, `ENGINE_FLYPOINT_VIRIDIAN` | player at `VIRIDIAN_CITY (23, 26)` (`data/maps/spawn_points.asm:15`) | -| 10 | `MAP_VIRIDIAN_CITY` | warp 5 at `(23, 25)` | step onto the warp | - | in `MAP_VIRIDIAN_POKECENTER_1F` | -| 11 | `MAP_VIRIDIAN_POKECENTER_1F` | `VIRIDIANPOKECENTER1F_NURSE` at `(3, 1)` | talk, accept heal | - | party healed (`PokecenterNurseScript`) | -| 11b | `MAP_VIRIDIAN_CITY` | `VIRIDIANCITY_FISHER` at `(6, 23)` | talk (optional, not in the FAQ) | `EVENT_GOT_TM42_DREAM_EATER` clear, bag space | receive `TM_DREAM_EATER`; flag set | -| 12 | `MAP_VIRIDIAN_CITY` | warp 1 at `(32, 7)` | step onto the warp | - | in `MAP_VIRIDIAN_GYM` at `(4, 17)` / `(5, 17)` | -| 13 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_GYM_GUIDE` at `(7, 13)` | talk (optional) | `EVENT_VIRIDIAN_GYM_BLUE` clear | advice text only | -| 14 | `MAP_VIRIDIAN_GYM` | save | save the game (the FAQ says so explicitly) | - | - | -| 15 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_BLUE` at `(5, 3)`, approach from below | talk | `EVENT_VIRIDIAN_GYM_BLUE` clear, `ENGINE_EARTHBADGE` clear | battle `BLUE / BLUE1` (6 mons, L54-58, 2 Full Restores); on win `EVENT_BEAT_BLUE` set, `ENGINE_EARTHBADGE` set, +5800 | -| 16 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_BLUE` again | talk | `ENGINE_EARTHBADGE` set | `LeaderBlueEpilogueText` ("You'd better not lose until I beat you") | -| 17 | `MAP_VIRIDIAN_GYM` | `bg_event (3, 13)` or `(6, 13)` | read the statue | `ENGINE_EARTHBADGE` set | `GymStatue2Script` with Blue's name - a cheap badge assertion | -| 18 | `MAP_VIRIDIAN_GYM` -> `MAP_VIRIDIAN_CITY` -> Pokecenter | warp 1/2 `(4, 17)` / `(5, 17)`, then Viridian warp 5 | walk, warp, heal | - | 16 badges, section complete | - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map header, blocks, warps, coord/bg/object events for all six maps | `src/import/RomExtractorGen2.lua:782` `readMapEvents`, `:899` (map assembly) | implemented - the tables are read out of the ROM generically, so no per-map data is hand-written | -| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES` dispatch (needed for both rock callbacks and the Viridian fly point) | `src/world/gen2/World.lua:5659`, `:5664` | implemented | -| `changeblock` (Route 19 boulders) | `src/script/gen2/Vm.lua:1002` (halves x/y to block coords, calls `changeBlockFn`) | implemented | -| `setevent` / `clearevent` / `checkevent`, object-event hide flags | `src/world/gen2/Events.lua`, `src/script/gen2/Vm.lua` | implemented | -| `setflag` / `checkflag` on `ENGINE_*` (EARTHBADGE, FLYPOINT_VIRIDIAN) | `src/script/gen2/Vm.lua:212-223`, `:1584` | implemented | -| Surf field move + FOGBADGE check | `src/world/gen2/FieldMoves.lua:101` (badge table), `:482` (`SurfFunction` port) | implemented | -| Fly field move + STORMBADGE + flypoint gating | `src/world/gen2/FieldMoves.lua:501-505` | implemented | -| Trainer sight cones / `OBJECTTYPE_TRAINER` (sight 3 swimmers, sight 0 Tucker and Dawn) | `src/world/gen2/Trainers.lua:98` `Trainers.sees` | implemented | -| `SPRITEMOVEDATA_SPINRANDOM_FAST` swimmers | `src/world/gen2/Npc.lua:23`, `:37`, `:68` | implemented | -| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `endifjustbattled` | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented | -| Prize money, including the 4x split and Bank of Mom | `src/battle/gen2/Prize.lua:82` `Prize.reward`, `:181` (four quarters) | implemented | -| Gym-leader battle/victory music via `GymLeaders` / `KantoGymLeaders` | `src/battle/gen2/BattleMusic.lua:15-33` | implemented | -| Kanto badge page on the trainer card (EARTHBADGE displayed) | `src/ui/gen2/TrainerCard.lua:90`, `:161` | implemented | -| `verbosegiveitem` (TM42 in Viridian City) | `src/script/gen2/Vm.lua` (`verbosegiveitem`) | implemented | -| A driver that actually plays this stretch (Routes 19/20 surf trainers, Blue) | none - `tests/drivers/` has `gold_trainer_smoke.lua`, `gold_water_moves.lua`, `gold_map_callbacks.lua`, but nothing that reaches Kanto Route 19/20 or Viridian Gym | missing | -| Section-specific regression coverage for `EVENT_CINNABAR_ROCKS_CLEARED` sequencing | not found by grep in `tests/` | missing | - -Every "implemented" row above is a generic engine capability, not a hand-port of these maps. Nothing in `src/` names `ROUTE_19`, `ROUTE_20` or `VIRIDIAN_GYM` (the only grep hits are `src/save_convert/data/event_flags_yellow.lua`, which is Gen 1 save conversion data and unrelated). So this section should work if the extractor and VM work, but it has never been exercised end to end. - -## 6. Unresolved / verify by hand - -1. **The walkthrough's boulder claim contradicts the asm.** "You can't go further north because you'll be blocked by those boulders south of Fuchsia City" is only true while `EVENT_CINNABAR_ROCKS_CLEARED` is clear. `maps/Route20.asm:12` `Route20ClearRocksCallback` sets that event on `MAPCALLBACK_NEWMAP` with no condition at all, and the walkthrough routes the player through Route 20 to reach Route 19. By the time the player is standing on Route 19 the six `changeblock` rocks are never placed, so northern Route 19 and the Fuchsia gate at `(7, 3)` are open. Treat the FAQ line as stale prose, not a gate. (What is genuinely true is that the FAQ's suggested Fly is faster than swimming back.) -2. **Route 19's north connection vs. the gate warp.** `data/maps/attributes.asm:288` declares `connection north, FuchsiaCity, FUCHSIA_CITY, 0`, and there is *also* a warp to `ROUTE_19_FUCHSIA_GATE`. I did not decode `maps/Route19.blk` to determine whether the top block row is actually walkable, so I cannot say whether the north connection is reachable or purely decorative. Verify by hand before a bot relies on either route out. -3. **Two trainers the walkthrough omits.** `TrainerSwimmermCameron` on Route 20 at `(12, 13)` (L34 MARILL) and `TrainerSwimmermHarold` on Route 19 at `(13, 28)` (L32 REMORAID, L30 SEADRA) both exist with sight range 3. The FAQ says "that's all the trainers" for Route 19, which is wrong. A bot surfing the full map will get pulled into both. -4. **Blue's move set does not match the FAQ's strategy text.** The FAQ names Air Slash (Pidgeot) and Psychic + Hypnosis (Exeggutor); `data/trainers/parties.asm:3154` gives Pidgeot `QUICK_ATTACK, WHIRLWIND, WING_ATTACK, MIRROR_MOVE` and Exeggutor `SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM`. Air Slash does not exist in Gen 2. Do not build AI expectations from the FAQ prose here. -5. **The FAQ's "Blue is one of only two trainers tougher than Champion Lance"** is a subjective claim with no asm counterpart; nothing in `data/trainers/` ranks trainers. -6. **`EVENT_VIRIDIAN_GYM_BLUE` is set in a prior section.** The walkthrough never mentions meeting Blue on Cinnabar Island, but `maps/CinnabarIsland.asm:23` is the only `clearevent` for that flag anywhere in the tree. If an earlier section's document does not cover `CinnabarIslandBlue`, this is an orphaned precondition and a bot will find an empty Viridian Gym with no in-game hint beyond `ViridianCityGrampsNearGym`. -7. **Blue's map header sets the phone column to `TRUE`** (`data/maps/maps.asm:458`, the `ViridianGym` row). I found no `phone_call` or rematch entry naming `BLUE`, so I believe this is inert, but I did not exhaustively read `data/phone/`. diff --git a/docs/gold-walkthrough/asm-walk/section-29-routes-22-28.md b/docs/gold-walkthrough/asm-walk/section-29-routes-22-28.md deleted file mode 100644 index 501009d6..00000000 --- a/docs/gold-walkthrough/asm-walk/section-29-routes-22-28.md +++ /dev/null @@ -1,785 +0,0 @@ -# Section 29 - Routes 22-28 - -Source: `../section-29-routes-22-28.txt` (the FAQ numbers this chapter "35 > Routes 22-28") -Maps covered: `MAP_PALLET_TOWN`, `MAP_OAKS_LAB`, `MAP_VIRIDIAN_CITY`, `MAP_ROUTE_22`, -`MAP_VICTORY_ROAD_GATE`, `MAP_ROUTE_28`, `MAP_ROUTE_28_STEEL_WING_HOUSE`, and the -east edge of `MAP_SILVER_CAVE_OUTSIDE` (+ `MAP_SILVER_CAVE_POKECENTER_1F`). -Badges / key milestones in this section: no badge. The milestone is -`EVENT_OPENED_MT_SILVER`, set by Prof. Oak once `VAR_BADGES == NUM_BADGES` (16). -Items: TM42 Dream Eater, TM47 Steel Wing, hidden Rare Candy. - -Two structural facts the walkthrough hides, both verified against the block data -and both load-bearing for a bot: - -1. **Oak's line is not flavour - it is the gate.** `EVENT_OPENED_MT_SILVER` - *removes* the black belt standing at `VICTORY_ROAD_GATE` (7,5). That NPC - occupies the only tile joining the gate's central corridor to its western - vestibule, i.e. to the two `ROUTE_28` warps. Until Oak sets the flag, Route 28 - and Mt. Silver are physically unreachable. See section 3. -2. **`object_event`'s trailing `EVENT_*` HIDES the object when the flag is SET.** - `CheckObjectFlag` (`engine/overworld/map_objects_2.asm:32-61`) jumps to - `.masked` when `EventFlagAction`/`CHECK_FLAG` returns non-zero. The macro - comment in `macros/scripts/maps.asm:132` ("event flag: an `EVENT_*` constant, - or -1 to always appear") does not state the polarity and is easy to read - backwards. The port already has this right - (`src/world/gen2/Events.lua:40-44`). - -Coordinates below are walk cells (the raw numbers in the `warp_event` / -`bg_event` / `object_event` rows), origin top-left, x east, y south. - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_PALLET_TOWN` | `maps/PalletTown.asm` | Fly, `SPAWN_PALLET` = (5,6) (`data/maps/spawn_points.asm:14`) | warp 3 at (12,11) -> `OAKS_LAB` warp 1 | "fly back to Pallet Town" | -| 1 | `MAP_OAKS_LAB` | `maps/OaksLab.asm` | warp 1 at (4,11) (warp 2 at (5,11) is the twin tile) | same warps back to `PALLET_TOWN` warp 3 | talk to `Oak` at (4,2); with 16 badges this runs `.OpenMtSilver` and sets `EVENT_OPENED_MT_SILVER` | -| 2 | `MAP_VIRIDIAN_CITY` | `maps/ViridianCity.asm` | Fly, `SPAWN_VIRIDIAN` = (23,26) (`data/maps/spawn_points.asm:15`), which is the Pokecenter warp tile | west map edge, cells y=14..17 at x=0 | "fly back to Viridian City, and then head west to Route 22" | -| 2a | `MAP_VIRIDIAN_CITY` (Cut pocket) | same | Cut the tree at (8,22), approached from (9,22) facing LEFT | the same tile back east | the TM42 Dream Eater fisher at (6,23). The FAQ files this under "Items on Route 22"; it is **not** on Route 22 | -| 3 | `MAP_ROUTE_22` | `maps/Route22.asm` | east map edge at x=39, y=6..9 (`connection east, ViridianCity, VIRIDIAN_CITY, -4`) | warp 1 at (13,5) -> `VICTORY_ROAD_GATE` warp 1 | "keep heading north, west, then north" | -| 4 | `MAP_VICTORY_ROAD_GATE` | `maps/VictoryRoadGate.asm` | warp 1 at (17,7) (warp 2 at (18,7) is the twin) | warp 7 at (1,7) / warp 8 at (2,7) -> `ROUTE_28` warp 2 | "heading west instead of north" | -| 5 | `MAP_ROUTE_28` (main body) | `maps/Route28.asm` | warp 2 at (33,5) | west map edge at x=0, y=12..13 (`connection west, SilverCaveOutside, SILVER_CAVE_OUTSIDE, -9`) | "head west towards Mount Silver"; the Route 28 sign is read from (30,5) | -| 6 | `MAP_SILVER_CAVE_OUTSIDE` | `maps/SilverCaveOutside.asm` | east map edge at x=39, y=30..31 | warp 1 at (23,19) -> `SILVER_CAVE_POKECENTER_1F` warp 1 | "There's also a Pokémon Center here". Sets `ENGINE_FLYPOINT_SILVER_CAVE` on entry | -| 7 | `MAP_SILVER_CAVE_POKECENTER_1F` | `maps/SilverCavePokecenter1F.asm` | warp 1 at (3,7) (twin at (4,7)) | same | heal; the last Pokecenter before Mt. Silver | -| 8 | `MAP_SILVER_CAVE_OUTSIDE` (east shelf) | same | Cut (31,24) then Cut (34,23) | east map edge at x=39, y=21 | "cut the trees east" | -| 9 | `MAP_ROUTE_28` (isolated west strip) | `maps/Route28.asm` | west map edge at x=0, y=3 | warp 1 at (7,3) -> `ROUTE_28_STEEL_WING_HOUSE` warp 1 | the strip that holds the Steel Wing house door and the hidden Rare Candy | -| 10 | `MAP_ROUTE_28_STEEL_WING_HOUSE` | `maps/Route28SteelWingHouse.asm` | warp 1 at (2,7) (twin at (3,7)) | same | `Celebrity` at (2,3) gives TM47 Steel Wing | -| 11 | `MAP_SILVER_CAVE_OUTSIDE` warp 2 at (18,11) -> `SILVER_CAVE_ROOM_1` | `maps/SilverCaveRoom1.asm` | - | - | **next section** (Mt. Silver / Red). Stop here. | - -The FAQ's chapter title says "Routes 22-28" but the prose never enters Route 23, -24, 25, 26 or 27. Routes 26/27 are section 16's, Route 23 is section 17/18's. - -## 2. Maps - -### MAP_PALLET_TOWN - -- Script: `maps/PalletTown.asm` (included from `data/maps/scripts.asm:171`) -- Blocks: `maps/PalletTown.blk` (`data/maps/blocks.asm:88`) -- Header: `data/maps/maps.asm:312` - `map PalletTown, TILESET_KANTO, TOWN, LANDMARK_PALLET_TOWN, MUSIC_PALLET_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN` -- Attributes: `data/maps/attributes.asm:271` `map_attributes PalletTown, PALLET_TOWN, $0f` -- Dimensions: `constants/map_constants.asm:288` `map_const PALLET_TOWN, 10, 9` -> 10x9 blocks = 20x18 cells. Group `PALLET` (13), map id 2. -- Connections: `connection north, Route1, ROUTE_1, 0`; `connection south, Route21, ROUTE_21, 0` -- Callbacks: `callback MAPCALLBACK_NEWMAP, PalletTownFlypointCallback` (sym `4e:46ac`) -> `setflag ENGINE_FLYPOINT_PALLET` - -**Warps** (`def_warp_events`, `maps/PalletTown.asm:73-76`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 5 | `REDS_HOUSE_1F` | 1 | -| 2 | 13 | 5 | `BLUES_HOUSE` | 1 | -| 3 | 12 | 11 | `OAKS_LAB` | 1 | - -**Coord events** - `def_coord_events` is empty. - -**BG events** (`def_bg_events`, `maps/PalletTown.asm:80-84`) - -| x | y | type | script/item | -|---|---|---|---| -| 7 | 9 | `BGEVENT_READ` | `PalletTownSign` (sym `4e:46b6`) | -| 3 | 5 | `BGEVENT_READ` | `RedsHouseSign` | -| 13 | 13 | `BGEVENT_READ` | `OaksLabSign` (sym `4e:46bc`) | -| 11 | 5 | `BGEVENT_READ` | `BluesHouseSign` | - -**Object events** (`def_object_events`, `maps/PalletTown.asm:86-88`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `PALLETTOWN_TEACHER` | `SPRITE_TEACHER` | 3 | 8 | `SPRITEMOVEDATA_WANDER` (r 2,2) | `OBJECTTYPE_SCRIPT` | `PalletTownTeacherScript` | -1 | -| `PALLETTOWN_FISHER` | `SPRITE_FISHER` | 12 | 14 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (r 2,0) | `OBJECTTYPE_SCRIPT` | `PalletTownFisherScript` | -1 | - -**Route from the Fly landing to the lab door** (BFS over `PalletTown.blk` + -`data/tilesets/kanto_collision.asm`): (5,6) -> (9,6) -> (9,12) -> (12,12) -> -(12,11). No ledges, no field moves. - ---- - -### MAP_OAKS_LAB - -- Script: `maps/OaksLab.asm` (`data/maps/scripts.asm:341`) -- Blocks: `maps/OaksLab.blk` (`data/maps/blocks.asm:892`) -- Header: `data/maps/maps.asm:316` - `map OaksLab, TILESET_LAB, INDOOR, LANDMARK_PALLET_TOWN, MUSIC_POKEMON_TALK, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Attributes: `data/maps/attributes.asm:579` `map_attributes OaksLab, OAKS_LAB, $00`, no connections -- Dimensions: `constants/map_constants.asm:292` `map_const OAKS_LAB, 5, 6` -> 5x6 blocks = 10x12 cells. Group `PALLET` (13), map id 6. -- Callbacks / scene scripts: both empty (`OaksLabNoopScene` is marked unreferenced). - -**Warps** (`maps/OaksLab.asm:260-262`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 4 | 11 | `PALLET_TOWN` | 3 | -| 2 | 5 | 11 | `PALLET_TOWN` | 3 | - -**Coord events** - none. - -**BG events** (`maps/OaksLab.asm:266-282`) - -| x | y | type | script/item | -|---|---|---|---| -| 6,7,8,9 | 1 | `BGEVENT_READ` | `OaksLabBookshelf` -> `jumpstd DifficultBookshelfScript` | -| 0,1,2,3 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` | -| 6,7,8,9 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` | -| 4 | 0 | `BGEVENT_READ` | `OaksLabPoster1` | -| 5 | 0 | `BGEVENT_READ` | `OaksLabPoster2` | -| 9 | 3 | `BGEVENT_READ` | `OaksLabTrashcan` | -| 0 | 1 | `BGEVENT_READ` | `OaksLabPC` | - -**Object events** (`maps/OaksLab.asm:284-288`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `OAKSLAB_OAK` | `SPRITE_OAK` | 4 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Oak` (sym `59:58c3`) | -1 | -| `OAKSLAB_SCIENTIST1` | `SPRITE_SCIENTIST` | 1 | 8 | `WALK_LEFT_RIGHT` (r 1,0), `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `OaksAssistant1Script` (sym `59:590a`) | -1 | -| `OAKSLAB_SCIENTIST2` | `SPRITE_SCIENTIST` | 8 | 9 | `WALK_UP_DOWN` (r 0,1), `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `OaksAssistant2Script` | -1 | -| `OAKSLAB_SCIENTIST3` | `SPRITE_SCIENTIST` | 1 | 4 | `WANDER` (r 1,1), `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `OaksAssistant3Script` | -1 | - -Oak stands at (4,2); the player talks to him from (4,3) facing UP. Cell (4,3) is -`COLL_FLOOR` in `data/tilesets/lab_collision.asm`, and (4,11)/(5,11) are the -`WARP_CARPET_DOWN` pair, so the approach is a straight walk up the middle of the -room. - -**Scripts of interest** - -`Oak` (`maps/OaksLab.asm:15-54`, sym `59:58c3`) - the only script in this section -that writes a story flag: - -``` -faceplayer / opentext -checkevent EVENT_OPENED_MT_SILVER ; already done -> .CheckPokedex -iftrue .CheckPokedex -checkevent EVENT_TALKED_TO_OAK_IN_KANTO -iftrue .CheckBadges -writetext OakWelcomeKantoText / promptbutton -setevent EVENT_TALKED_TO_OAK_IN_KANTO -.CheckBadges: -readvar VAR_BADGES -ifequal NUM_BADGES, .OpenMtSilver ; 16 -ifequal NUM_JOHTO_BADGES, .Complain ; 8 -> "you're not collecting KANTO BADGES" -sjump .AhGood ; 9..15 -> "come see me when you get them all" -.OpenMtSilver: -writetext OakOpenMtSilverText / promptbutton -setevent EVENT_OPENED_MT_SILVER -sjump .CheckPokedex -.CheckPokedex: -writetext OakLabDexCheckText / waitbutton -special ProfOaksPCBoot -writetext OakLabGoodbyeText / waitbutton / closetext / end -``` - -Bot-relevant: the branch is `ifequal`, not `ifgreater`, and `VAR_BADGES` is the -**popcount of both badge bytes** (`engine/overworld/variables.asm` -> -`CountSetBits` over `wJohtoBadges` + `wKantoBadges`; the port mirrors this at -`src/world/gen2/World.lua:1240-1245`). So the flag is only set on a visit with -exactly 16 badges; 9-15 badges gives `.AhGood` and no flag. Every arm falls -through to `special ProfOaksPCBoot`, which prints the Pokedex rating - budget -several textbox advances for it. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_OPENED_MT_SILVER` | `constants/event_flags.asm:1265` | set by `Oak`; read by `Oak` and by the `VICTORYROADGATE_BLACK_BELT1` object row | **the section's gate.** While clear, the black belt at `VICTORY_ROAD_GATE` (7,5) exists and blocks the ROUTE_28 warps | -| `EVENT_TALKED_TO_OAK_IN_KANTO` | `constants/event_flags.asm:224` | set/read by `Oak` | first-visit greeting only, no gameplay effect | - ---- - -### MAP_VIRIDIAN_CITY - -- Script: `maps/ViridianCity.asm` (`data/maps/scripts.asm:168`) -- Blocks: `maps/ViridianCity.blk` (`data/maps/blocks.asm:235`) -- Header: `data/maps/maps.asm:457` - `map ViridianCity, TILESET_KANTO, TOWN, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Attributes: `data/maps/attributes.asm:259` `map_attributes ViridianCity, VIRIDIAN_CITY, $0f` -- Dimensions: `constants/map_constants.asm:423` `map_const VIRIDIAN_CITY, 20, 18` -> 20x18 blocks = 40x36 cells. Group `VIRIDIAN` (23), map id 3. -- Connections: `connection north, Route2, ROUTE_2, 5`; `connection south, Route1, ROUTE_1, 10`; `connection west, Route22, ROUTE_22, 4` -- Callbacks: `callback MAPCALLBACK_NEWMAP, ViridianCityFlypointCallback` (sym `4e:4005`) -> `setflag ENGINE_FLYPOINT_VIRIDIAN` - -**Warps** (`maps/ViridianCity.asm:219-224`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 32 | 7 | `VIRIDIAN_GYM` | 1 | -| 2 | 21 | 9 | `VIRIDIAN_NICKNAME_SPEECH_HOUSE` | 1 | -| 3 | 23 | 15 | `TRAINER_HOUSE_1F` | 1 | -| 4 | 29 | 19 | `VIRIDIAN_MART` | 2 | -| 5 | 23 | 25 | `VIRIDIAN_POKECENTER_1F` | 1 | - -**Coord events** - `def_coord_events` is empty. - -**BG events** (`maps/ViridianCity.asm:228-234`) - -| x | y | type | script/item | -|---|---|---|---| -| 17 | 17 | `BGEVENT_READ` | `ViridianCitySign` | -| 27 | 7 | `BGEVENT_READ` | `ViridianGymSign` | -| 19 | 1 | `BGEVENT_READ` | `ViridianCityWelcomeSign` | -| 21 | 15 | `BGEVENT_READ` | `TrainerHouseSign` | -| 24 | 25 | `BGEVENT_READ` | `ViridianCityPokecenterSign` -> `jumpstd PokecenterSignScript` | -| 30 | 19 | `BGEVENT_READ` | `ViridianCityMartSign` -> `jumpstd MartSignScript` | - -**Object events** (`maps/ViridianCity.asm:236-240`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VIRIDIANCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 5 | `WANDER` (r 2,2) | `OBJECTTYPE_SCRIPT` | `ViridianCityCoffeeGramps` (sym `4e:4009`) | -1 | -| `VIRIDIANCITY_GRAMPS2` | `SPRITE_GRAMPS` | 30 | 8 | `STANDING_DOWN`, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `ViridianCityGrampsNearGym` (sym `4e:401e`) | -1 | -| `VIRIDIANCITY_FISHER` | `SPRITE_FISHER` | 6 | 23 | `STANDING_DOWN`, `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `ViridianCityDreamEaterFisher` (sym `4e:4032`) | -1 | -| `VIRIDIANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 21 | `WANDER` (r 3,3), `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `ViridianCityYoungsterScript` (sym `4e:404d`) | -1 | - -**Scripts of interest** - -`ViridianCityDreamEaterFisher` (`maps/ViridianCity.asm:50-65`, sym `4e:4032`): - -``` -faceplayer / opentext -checkevent EVENT_GOT_TM42_DREAM_EATER -iftrue .GotDreamEater -writetext ViridianCityDreamEaterFisherText / promptbutton -verbosegiveitem TM_DREAM_EATER -iffalse .NoRoomForDreamEater ; bag full -> flag NOT set, retryable -setevent EVENT_GOT_TM42_DREAM_EATER -.GotDreamEater: writetext ...GotDreamEaterText / waitbutton -.NoRoomForDreamEater: closetext / end -``` - -`TM_DREAM_EATER` is item `$ea` (`constants/item_constants.asm:263`, the 42nd -`add_tm` after `DEF TM01 EQU const_value` at line 219 - the two bare `const` -rows `ITEM_C3` and `ITEM_DC` do not consume TM numbers, which is why TM42 is -Dream Eater and TM47 is Steel Wing). - -**Cut gate (verified from block data)** - -`ViridianCity.blk` block (4,11) is `$34`, whose `tilecoll` row is -`CUT_TREE, FLOOR, WALL, WALL` (`data/tilesets/kanto_collision.asm`, block `34`), -so the `COLL_CUT_TREE` quadrant is the top-left = **cell (8,22)**. A BFS over the -collision grid says the fisher's pocket (x=4..7, y=22..26) has exactly one -non-ledge entrance and it is that tile. Route: - -- Pokecenter door (23,25) -> step down to (23,26) -- (23,26) -> (14,26) -> (14,23) -> (10,23) -> (10,22) -> (9,22) -- face LEFT at (9,22), Cut (8,22) -- (8,22) -> (7,22) -> (6,22) -> (6,23) faces the fisher at... he *is* at (6,23); - stand at (6,22) and face DOWN, or (7,23) and face LEFT. - -The pocket's south edge (x=4..7, y=26) is `COLL_HOP_DOWN`, i.e. one-way out. - -The only other `$34`/`$60`/`$32`/`$33`/`$35` block on this map is (7,2) = -cell (14,4), which is not on any route in this section. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| TM42 Dream Eater | talk to the fisher at (6,23) after Cutting (8,22) | `ViridianCityDreamEaterFisher` | `EVENT_GOT_TM42_DREAM_EATER` (`constants/event_flags.asm:223`) | - -**Trainers** - none on this map. - -**Wild encounters** - `FISHGROUP_POND` per the header; no `def_grass_wildmons -VIRIDIAN_CITY` row is needed by this section. - ---- - -### MAP_ROUTE_22 - -- Script: `maps/Route22.asm` (`data/maps/scripts.asm:169`) - 28 lines, one sign and one warp, nothing else -- Blocks: `maps/Route22.blk` (`data/maps/blocks.asm:310`, 180 bytes) -- Header: `data/maps/maps.asm:456` - `map Route22, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_22, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Attributes: `data/maps/attributes.asm:264` `map_attributes Route22, ROUTE_22, $2c` -- Dimensions: `constants/map_constants.asm:422` `map_const ROUTE_22, 20, 9` -> 20x9 blocks = 40x18 cells. Group `VIRIDIAN` (23), map id 2. -- Connections: `connection east, ViridianCity, VIRIDIAN_CITY, -4` (only one). Crossing east from `(39, y)` lands on Viridian City `(0, y + 8)`; crossing west from Viridian `(0, y)` lands on Route 22 `(39, y - 8)`. -- Scene scripts / callbacks: both empty. - -**Warps** (`maps/Route22.asm:19-20`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 5 | `VICTORY_ROAD_GATE` | 1 | - -**Coord events** - none. - -**BG events** (`maps/Route22.asm:24-25`) - -| x | y | type | script/item | -|---|---|---|---| -| 15 | 7 | `BGEVENT_READ` | `VictoryRoadEntranceSign` (sym `4e:44f9`) - read from (14,7) facing RIGHT | - -**Object events** - `def_object_events` is empty. **There are no trainers and no -item balls on Route 22**, which matches the FAQ. The FAQ's "Items on Route 22: -TM42 Dream Eater" is wrong; that TM is a Viridian City NPC gift (above). - -**Traversal (verified by BFS over `Route22.blk` + `kanto_collision.asm`)** - -The map is a set of shelves joined by one-way ledges. Entering from Viridian at -`(39, 6..9)`, the *only* path to the gate warp is: - -``` -(39,7) -> (35,7) -> (35,11) -(35,12) is COLL_HOP_DOWN -> hop to (35,14) [ledge, one-way] -(35,14) -> (33,14) -> (33,13) -> (33,12) [the one non-ledge gap] -(33,12) -> (33,11) grass -> (31,8) -> (31,7) -> (31,5) [the y=4..5 plaza] -(31,5) -> (18,5) -(18,6) is COLL_HOP_DOWN -> hop to (18,8) [ledge, one-way] -(18,8) grass -> (18,12) -> (14,12) -> (14,8) -> (13,7) -> (13,6) -> (13,5) warp 1 -``` - -Ledge semantics: a `COLL_HOP_*` tile is `LAND_TILE` and can be *walked onto*; -pressing the matching direction while standing on it jumps **two** cells, over -the wall beneath. There is no ledge-free route: `(18,7)` and `(35,13)` are -`COLL_WALL`. - -Reachable tall grass in that component: `(18..21, 8..11)` and `(30..33, 8..11)`. -Surfable water adjacent to it: `(22..25, 6)` from the plaza row y=5, and -`(22..25, 9)` from the (22..25,10..11) shelf. - -**Wild encounters** - `data/wild/kanto_grass.asm:1033`, `def_grass_wildmons ROUTE_22`, -encounter rate `10 percent` in all three time slots. Slot probabilities are -30/30/20/10/5/4/1 (`data/wild/probabilities.asm:6-15`). - -| slot | morn | day | nite | -|---|---|---|---| -| 1 (30%) | L3 RATTATA | L3 RATTATA | L3 RATTATA | -| 2 (30%) | L3 SPEAROW | L3 SPEAROW | L3 RATTATA | -| 3 (20%) | L5 SPEAROW | L5 SPEAROW | L5 RATTATA | -| 4 (10%) | L4 DODUO | L4 DODUO | L4 RATTATA | -| 5 (5%) | L6 PONYTA | L6 PONYTA | L6 PONYTA | -| 6 (4%) | L7 FEAROW | L7 FEAROW | L7 RATTATA | -| 7 (1%) | L7 FEAROW | L7 FEAROW | L7 RATTATA | - -Water (`data/wild/kanto_water.asm:68`, `def_water_wildmons ROUTE_22`, rate -`2 percent`, slot probabilities 60/30/10): L10 POLIWAG, L5 POLIWAG, L10 POLIWHIRL. -The FAQ's "#060 Poliwag (surfing)" is right; it omits Doduo, Ponyta and Fearow -from the grass list. - -Fishing: `FISHGROUP_POND` (`data/wild/fish.asm:72-85`) - Old rod MAGIKARP/POLIWAG, -Good rod MAGIKARP/POLIWAG + a time group, Super rod POLIWAG/MAGIKARP + a time group. - -Headbutt: Route 22 has **no** row in `data/wild/treemon_maps.asm`. - ---- - -### MAP_VICTORY_ROAD_GATE - -- Script: `maps/VictoryRoadGate.asm` (`data/maps/scripts.asm:452`) -- Blocks: `maps/VictoryRoadGate.blk` (`data/maps/blocks.asm:943`, 90 bytes) -- Header: `data/maps/maps.asm:467` - `map VictoryRoadGate, TILESET_GATE, GATE, LANDMARK_ROUTE_26, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE` - (note the landmark is `LANDMARK_ROUTE_26`, not Route 22 or 28 - relevant if a - bot keys anything off the Pokegear location readout) -- Attributes: `data/maps/attributes.asm:666` `map_attributes VictoryRoadGate, VICTORY_ROAD_GATE, $00`, no connections -- Dimensions: `constants/map_constants.asm:433` `map_const VICTORY_ROAD_GATE, 10, 9` -> 10x9 blocks = 20x18 cells. Group `VIRIDIAN` (23), map id 13. -- Scene variable: `data/maps/scenes.asm:16` `scene_var VICTORY_ROAD_GATE, wVictoryRoadGateSceneID` (sym `01:d6bf`) -- Scene scripts (`maps/VictoryRoadGate.asm:7-9`): `SCENE_VICTORYROADGATE_BADGE_CHECK` = 0 -> `VictoryRoadGateNoop1Scene` (bare `end`); `SCENE_VICTORYROADGATE_NOOP` = 1 -> `VictoryRoadGateNoop2Scene` (bare `end`). Both constants are minted by the `scene_script` macro's `scene_const` (`macros/scripts/maps.asm:12-33`), which is why grepping `constants/` for them finds nothing. - -**Warps** (`maps/VictoryRoadGate.asm:101-109`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 7 | `ROUTE_22` | 1 | -| 2 | 18 | 7 | `ROUTE_22` | 1 | -| 3 | 9 | 17 | `ROUTE_26` | 1 | -| 4 | 10 | 17 | `ROUTE_26` | 1 | -| 5 | 9 | 0 | `VICTORY_ROAD` | 1 | -| 6 | 10 | 0 | `VICTORY_ROAD` | 1 | -| 7 | 1 | 7 | `ROUTE_28` | 2 | -| 8 | 2 | 7 | `ROUTE_28` | 2 | - -**Coord events** (`maps/VictoryRoadGate.asm:111-112`) - -| scene | x | y | script label | effect | -|---|---|---|---|---| -| `SCENE_VICTORYROADGATE_BADGE_CHECK` (0) | 10 | 11 | `VictoryRoadGateBadgeCheckScript` (sym `5f:4fdc`) | `turnobject PLAYER, LEFT` then the officer's speech; `< 8` Johto badges -> `applymovement PLAYER, VictoryRoadGateStepDownMovement` (one step DOWN); 8 badges -> `setscene SCENE_VICTORYROADGATE_NOOP` and the trip-wire never fires again | - -A bot arriving from Route 22 (warp 1/2) never crosses (10,11): the east-west hall -is at y=5 and the badge check sits in the southern stub from Route 26. The check -is therefore *not* a gate on this section's route. - -**BG events** - `def_bg_events` is empty. - -**Object events** (`maps/VictoryRoadGate.asm:116-119`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `VICTORYROADGATE_OFFICER` | `SPRITE_OFFICER` | 8 | 11 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateOfficerScript` (sym `5f:4fe2`) | -1 | -| `VICTORYROADGATE_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 7 | 5 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateLeftBlackBeltScript` (sym `5f:5000`) | `EVENT_OPENED_MT_SILVER` | -| `VICTORYROADGATE_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 12 | 5 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateRightBlackBeltScript` (sym `5f:5003`) | `EVENT_FOUGHT_SNORLAX` | - -**Geometry (from `VictoryRoadGate.blk` + `data/tilesets/gate_collision.asm`)** - -The building is a plus: one 20-wide, **one-cell-tall** hall at y=5, a north stub -(x=8..11, y=0..4) to Victory Road, a south stub (x=8..11, y=6..17) to Route 26 -with the officer's counter at y=10..13, and two 4-wide vestibules at y=6..7 - -west (x=0..3, the Route 28 warps) and east (x=16..19, the Route 22 warps). -Cells (7,5) and (12,5) are the only tiles joining the centre to the west and east -vestibules respectively, and each is exactly where a black belt stands. - -BFS from the Route 22 warp (17,7), toggling the two NPCs: - -| NPCs present | reach `ROUTE_28` warp (1,7) | reach Victory Road door (9,0) | reach badge coord (10,11) | -|---|---|---|---| -| neither | yes | yes | yes | -| left only (`EVENT_OPENED_MT_SILVER` clear) | **no** | yes | yes | -| right only (`EVENT_FOUGHT_SNORLAX` clear) | no | no | no | -| both | no | no | no | - -**Trainers** - none. - ---- - -### MAP_ROUTE_28 - -- Script: `maps/Route28.asm` (`data/maps/scripts.asm:131`) - one sign, one hidden item, no NPCs -- Blocks: `maps/Route28.blk` (`data/maps/blocks.asm:15`, 180 bytes) -- Header: `data/maps/maps.asm:396` - `map Route28, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_28, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Attributes: `data/maps/attributes.asm:174` `map_attributes Route28, ROUTE_28, $2c` -- Dimensions: `constants/map_constants.asm:366` `map_const ROUTE_28, 20, 9` -> 20x9 blocks = 40x18 cells. Group `SILVER` (19), map id 1. -- Connections: `connection west, SilverCaveOutside, SILVER_CAVE_OUTSIDE, -9`. Crossing west from `(0, y)` lands on Silver Cave Outside `(39, y + 18)`; crossing east from Silver Cave Outside `(39, y)` lands on Route 28 `(0, y - 18)`. -- Scene scripts / callbacks: both empty. **Route 28 has no flypoint callback** - the Fly destination near Mt. Silver is `SILVER_CAVE_OUTSIDE`. - -**Warps** (`maps/Route28.asm:19-21`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 7 | 3 | `ROUTE_28_STEEL_WING_HOUSE` | 1 | -| 2 | 33 | 5 | `VICTORY_ROAD_GATE` | 7 | - -**Coord events** - none. - -**BG events** (`maps/Route28.asm:25-27`) - -| x | y | type | script/item | -|---|---|---|---| -| 31 | 5 | `BGEVENT_READ` | `Route28Sign` (sym `4a:4d4d`) - read from (30,5) facing RIGHT | -| 25 | 2 | `BGEVENT_ITEM` | `Route28HiddenRareCandy` (sym `4a:4d50`) = `hiddenitem RARE_CANDY, EVENT_ROUTE_28_HIDDEN_RARE_CANDY` | - -`RARE_CANDY` is item `$20` (`constants/item_constants.asm:40`). The hidden-item -row is three bytes (`dwb flag, item`), not a script; the port carries it on the -bg event as `hiddenItem = { item, event }` (`src/world/gen2/HiddenItems.lua:7-12`). - -**Object events** - `def_object_events` is empty. No trainers, no item balls. - -**Geometry: Route 28 is two disconnected halves** - -BFS over `Route28.blk` from the Victory Road Gate landing (33,6): - -- the **main body** reaches the sign approach (30,5) and the west edge only at - y=12..13 -> Silver Cave Outside (39, 30..31); -- it does **not** reach (7,3), (25,3) or any of y=3..4. - -BFS from the west edge at (0,3) - the **isolated north-west strip** - -reaches the house door (7,3) and the hidden-item approach (25,3), and then falls -one-way into the main body over the `COLL_HOP_DOWN` row at y=4 (x=4..9, landing -in the grass at y=6). So: - -- entering the strip: only from `SILVER_CAVE_OUTSIDE (39,21)` -> `ROUTE_28 (0,3)`, - which itself needs two Cuts (see below); -- inside the strip: (0,3) -> (5,3) -> (5,4) -> (7,4) -> north into the door (7,3); - and (7,4) -> (21,4) -> (21,3) -> (25,3), then face UP for the hidden Rare Candy - at (25,2); -- leaving the strip: hop any of the y=4 ledges (x=4..9) down into the main body, - which is one-way. Re-entering means going round through Silver Cave Outside again. - -Main-body route from the gate to Silver Cave Outside: (33,6) -> (28,6) -> (28,8), -hop (27,8)->(27,10), west along y=10 to (18,10) -> (18,9) -> (10,9) -> (10,10) -> -(8,10), hop (8,10)->(8,12), west along y=12 to (0,12). - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| Rare Candy | hidden, face (25,2) from (25,3) and press A, or use the Itemfinder | `Route28HiddenRareCandy` bg_event | `EVENT_ROUTE_28_HIDDEN_RARE_CANDY` (`constants/event_flags.asm:173`) | - -The FAQ lists "Items: TM47 Steel Wing" for Route 28 and never mentions the Rare -Candy. TM47 is in the house, not on the route. - -**Wild encounters** - `data/wild/kanto_grass.asm:1227`, `def_grass_wildmons ROUTE_28`, -rate `10 percent` all day. The table is `IF DEF(_GOLD) / ELIF DEF(_SILVER)`: - -| slot | Gold morn/day | Gold nite | Silver morn/day | Silver nite | -|---|---|---|---|---| -| 1 (30%) | L39 TANGELA | L39 TANGELA | L39 TANGELA | L39 TANGELA | -| 2 (30%) | L40 PONYTA | L40 PONYTA | L40 PONYTA | L40 PONYTA | -| 3 (20%) | L40 URSARING | L40 URSARING | L40 DONPHAN | L40 DONPHAN | -| 4 (10%) | L42 RAPIDASH | L40 SNEASEL | L42 RAPIDASH | L40 SNEASEL | -| 5 (5%) | L41 DODUO | L42 RAPIDASH | L41 DODUO | L42 RAPIDASH | -| 6 (4%) | L43 DODRIO | L42 RAPIDASH | L43 DODRIO | L42 RAPIDASH | -| 7 (1%) | L43 DODRIO | L42 RAPIDASH | L43 DODRIO | L42 RAPIDASH | - -So on **Gold** the FAQ's Donphan does not appear (Ursaring takes that slot), and -**Sneasel is nite-only**. Tangela is the 30% lead slot at every time of day, -which makes the FAQ's "consider catching a Tangela" cheap to satisfy. - -Water (`data/wild/kanto_water.asm:110`, rate `2 percent`): L40 POLIWAG, L35 -POLIWAG, L40 POLIWHIRL. Fishing: `FISHGROUP_POND`, same table as Route 22. -Headbutt: `data/wild/treemon_maps.asm:9` `treemon_map ROUTE_28, TREEMON_SET_NONE` -- headbutting a Route 28 tree yields nothing. - ---- - -### MAP_ROUTE_28_STEEL_WING_HOUSE - -- Script: `maps/Route28SteelWingHouse.asm` (`data/maps/scripts.asm:388`) -- Blocks: `data/maps/blocks.asm:181` (`Route28SteelWingHouse_Blocks`) -- Header: `data/maps/maps.asm:399` - `map Route28SteelWingHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_28, MUSIC_AZALEA_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Attributes: `data/maps/attributes.asm:624`, no connections -- Dimensions: `constants/map_constants.asm:369` `map_const ROUTE_28_STEEL_WING_HOUSE, 4, 4` -> 8x8 cells. Group `SILVER` (19), map id 4. -- Scene scripts: one `scene_script Route28SteelWingHouseNoopScene` marked "unusable" in the source; body is a bare `end`. - -**Warps** (`maps/Route28SteelWingHouse.asm:74-76`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 2 | 7 | `ROUTE_28` | 1 | -| 2 | 3 | 7 | `ROUTE_28` | 1 | - -**Coord events** - none. - -**BG events** (`maps/Route28SteelWingHouse.asm:80-82`) - -| x | y | type | script/item | -|---|---|---|---| -| 0 | 1 | `BGEVENT_READ` | `CelebrityHouseBookshelf` -> `jumpstd MagazineBookshelfScript` | -| 1 | 1 | `BGEVENT_READ` | `CelebrityHouseBookshelf` | - -**Object events** (`maps/Route28SteelWingHouse.asm:84-86`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE28STEELWINGHOUSE_CELEBRITY` | `SPRITE_COOLTRAINER_F` | 2 | 3 | `STANDING_DOWN`, `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `Celebrity` (sym `5c:4be9`) | -1 | -| `ROUTE28STEELWINGHOUSE_FEAROW` | `SPRITE_MOLTRES` | 6 | 5 | `SPRITEMOVEDATA_POKEMON`, `PAL_NPC_BROWN` | `OBJECTTYPE_SCRIPT` | `CelebritysFearow` (sym `5c:4c06`) | -1 | - -The pet is a Fearow but reuses `SPRITE_MOLTRES` recoloured with `PAL_NPC_BROWN`; -`CelebritysFearow` does `cry FEAROW`. Worth knowing if a bot asserts on sprite ids. - -**Scripts of interest** - -`Celebrity` (`maps/Route28SteelWingHouse.asm:14-31`, sym `5c:4be9`): - -``` -faceplayer / opentext -checkevent EVENT_GOT_TM47_STEEL_WING -iftrue .AlreadyGotItem -writetext CelebrityText1 / promptbutton -verbosegiveitem TM_STEEL_WING -iffalse .Done ; bag full -> flag NOT set, retryable -setevent EVENT_GOT_TM47_STEEL_WING -.Done: closetext / end -``` - -Note the shape differs from the Dream Eater fisher: there is no "thanks" text -after a successful give, the script just closes. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| TM47 Steel Wing (`$ef`) | talk to `Celebrity` at (2,3) from (2,4) facing UP | `Celebrity` | `EVENT_GOT_TM47_STEEL_WING` (`constants/event_flags.asm:125`) | - ---- - -### MAP_SILVER_CAVE_OUTSIDE (east edge only - the rest is the next section) - -- Script: `maps/SilverCaveOutside.asm` (`data/maps/scripts.asm:124`) -- Blocks: `maps/SilverCaveOutside.blk` (`data/maps/blocks.asm:559`, 360 bytes) -- Header: `data/maps/maps.asm:397` - `map SilverCaveOutside, TILESET_KANTO, TOWN, LANDMARK_SILVER_CAVE, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_AUTO, FISHGROUP_POND` -- Attributes: `data/maps/attributes.asm:164` `map_attributes SilverCaveOutside, SILVER_CAVE_OUTSIDE, $2c` -- Dimensions: `constants/map_constants.asm:367` `map_const SILVER_CAVE_OUTSIDE, 20, 18` -> 40x36 cells. Group `SILVER` (19), map id 2. -- Connections: `connection east, Route28, ROUTE_28, 9` -- Callbacks: `callback MAPCALLBACK_NEWMAP, SilverCaveOutsideFlypointCallback` (sym `49:5f1a`) -> `setflag ENGINE_FLYPOINT_SILVER_CAVE`. Spawn `SPAWN_MT_SILVER` = (23,20) (`data/maps/spawn_points.asm:39`), i.e. the tile below the Pokecenter door - this is the "fly back to the Pokémon Center on Route 28" the FAQ means. - -**Warps** (`maps/SilverCaveOutside.asm:27-29`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 19 | `SILVER_CAVE_POKECENTER_1F` | 1 | -| 2 | 18 | 11 | `SILVER_CAVE_ROOM_1` | 1 | - -**BG events** (`maps/SilverCaveOutside.asm:33-36`) - -| x | y | type | script/item | -|---|---|---|---| -| 24 | 19 | `BGEVENT_READ` | `MtSilverPokecenterSign` (sym `49:5f1e`) | -| 17 | 13 | `BGEVENT_READ` | `MtSilverSign` (sym `49:5f21`) | -| 9 | 25 | `BGEVENT_ITEM` | `SilverCaveOutsideHiddenFullRestore` (sym `49:5f24`) = `hiddenitem FULL_RESTORE, EVENT_SILVER_CAVE_OUTSIDE_HIDDEN_FULL_RESTORE` (`constants/event_flags.asm:194`) - **next section's**, and not reachable from the east side without Surf | - -**Coord events / object events** - both empty. - -**The two Cut trees (verified from block data)** - -`data/collision/field_move_blocks.asm:24-31`, `CutTreeBlockPointers.kanto`, lists -the cuttable blocks for `TILESET_KANTO`: `$0b -> $0a` (grass, animation 1) and -`$32 -> $6d`, `$33 -> $6c`, `$34 -> $6f`, `$35 -> $4c`, `$60 -> $6e` (trees, -animation 0). `SilverCaveOutside.blk` contains exactly two of them: - -| block coord | block id | `tilecoll` row | `COLL_CUT_TREE` cell | replacement block | -|---|---|---|---|---| -| (17,11) | `$60` | `FLOOR, WALL, CUT_TREE, WALL` | **(34,23)** | `$6e` | -| (15,12) | `$35` | `FLOOR, CUT_TREE, WALL, FLOOR` | **(31,24)** | `$4c` | - -BFS from the Route 28 landing (39,30): - -- with no cuts: the Pokecenter door (23,19), the cave mouth (18,11) and the - approach tile (30,24) are reachable; (39,21) is **not**; -- after cutting (31,24) only: (34,24) becomes reachable, (39,21) still not; -- after cutting both: (39,21) is reachable -> `ROUTE_28 (0,3)`. - -Route from the Pokecenter door to the house, waypoint by waypoint: - -``` -(23,19) door -> (23,20) -> (25,20) -> (25,22) -> (30,22) -> (30,24) -face RIGHT, Cut (31,24) -(31,24) -> (31,25) -> (34,25) -> (34,24) -face UP, Cut (34,23) -(34,23) -> (34,21) -> (39,21) -> cross east -> ROUTE_28 (0,3) -(0,3) -> (5,3) -> (5,4) -> (7,4) -> face UP into warp 1 at (7,3) -``` - ---- - -### MAP_SILVER_CAVE_POKECENTER_1F - -- Script: `maps/SilverCavePokecenter1F.asm` (`data/maps/scripts.asm:387`) -- Header: `data/maps/maps.asm:398` - `map SilverCavePokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_SILVER_CAVE, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Attributes: `data/maps/attributes.asm:623` -- Dimensions: `constants/map_constants.asm:368` `map_const SILVER_CAVE_POKECENTER_1F, 5, 4` -> 10x8 cells. Group `SILVER` (19), map id 3. - -**Warps** (`maps/SilverCavePokecenter1F.asm:33-36`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `SILVER_CAVE_OUTSIDE` | 1 | -| 2 | 4 | 7 | `SILVER_CAVE_OUTSIDE` | 1 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Object events** (`maps/SilverCavePokecenter1F.asm:42-44`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILVERCAVEPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SilverCavePokecenter1FNurseScript` (sym `5c:4b2b`) -> `jumpstd PokecenterNurseScript` | -1 | -| `SILVERCAVEPOKECENTER1F_GRANNY` | `SPRITE_GRANNY` | 1 | 5 | `STANDING_LEFT` (r 2,1) | `OBJECTTYPE_SCRIPT` | `SilverCavePokecenter1FGrannyScript` (sym `5c:4b2e`) | -1 | - -No mart clerk on this map - the FAQ's "fly to Mahogany Town's Pokemart to buy -some" is correct precisely because there is nothing to buy here. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Route 28 / Mt. Silver | `maps/VictoryRoadGate.asm:118` object row `VICTORYROADGATE_BLACK_BELT1` at (7,5) + `engine/overworld/map_objects_2.asm:32` `CheckObjectFlag` | `EVENT_OPENED_MT_SILVER` set, which hides the NPC and frees the only tile between the gate's centre and its west vestibule | talk to `Oak` (`maps/OaksLab.asm:15`) with `VAR_BADGES == NUM_BADGES` (16; `constants/ram_constants.asm:273`) | -| Route 22 from the gate | `maps/VictoryRoadGate.asm:119` object row `VICTORYROADGATE_BLACK_BELT2` at (12,5) | `EVENT_FOUGHT_SNORLAX` (`constants/event_flags.asm:1266`) | set long before this section (Kanto). Listed because it uses the same mechanism and a bot that clears flags for testing will wall itself in | -| Victory Road Gate south corridor | `maps/VictoryRoadGate.asm:112` coord event (10,11) -> `VictoryRoadGateBadgeCheckScript` | `readvar VAR_BADGES` `ifgreater NUM_JOHTO_BADGES - 1` i.e. >= 8 badges | already satisfied; and the Route 22 -> Route 28 path never touches (10,11) | -| Dream Eater fisher (Viridian) | `data/collision/field_move_blocks.asm:24` `.kanto` block `$34` at Viridian block (4,11) -> `COLL_CUT_TREE` at cell (8,22) | HM01 Cut in the party **and** `ENGINE_HIVEBADGE` | `engine/events/overworld.asm:1741` `TryCutOW`: `CheckPartyMove CUT`, then `ld de, ENGINE_HIVEBADGE / call CheckEngineFlag`; failure runs `CantCutScript` | -| Steel Wing house / Route 28 west strip | two `COLL_CUT_TREE` cells on `SILVER_CAVE_OUTSIDE`: (31,24) (block `$35`) and (34,23) (block `$60`) | same Cut + `ENGINE_HIVEBADGE` check | as above. Both trees are required - cutting only (31,24) does not open (39,21) | -| Fly to Pallet / Viridian / Mt. Silver | `engine/events/overworld.asm:543-545` `FlyFunction.TryFly`: `ld de, ENGINE_STORMBADGE / call CheckBadge`, then `GetMapEnvironment` / `CheckOutdoorMap` | STORMBADGE + HM02 Fly + the destination's `ENGINE_FLYPOINT_*` bit | `ENGINE_FLYPOINT_PALLET` (`constants/engine_flags.asm:67`), `ENGINE_FLYPOINT_VIRIDIAN` (68), `ENGINE_FLYPOINT_SILVER_CAVE` (90), each set by that map's `MAPCALLBACK_NEWMAP` the first time you walk in | -| Surf (Route 22 / Route 28 water) | `engine/events/overworld.asm:340` and `:490`, `ld de, ENGINE_FOGBADGE` | FOGBADGE + HM03 Surf | optional in this section | -| Route 22 traversal | `Route22.blk` collision: `(35,12)` and `(18,6)` are `COLL_HOP_DOWN` with `COLL_WALL` beneath | ledge hopping (walk onto the ledge tile, then press the matching direction to jump two cells) | no item needed, but there is **no** ledge-free route to the gate warp | -| Route 28 west strip re-entry | `Route28.blk` y=4 ledge row (x=4..9) | one-way; falling off it drops you into the main body | re-enter via `SILVER_CAVE_OUTSIDE (39,21)` | - -## 4. Bot checklist - -Preconditions for the whole section: 16 badges, HM01 Cut + HIVEBADGE, HM02 Fly + -STORMBADGE, `EVENT_FOUGHT_SNORLAX` set, `ENGINE_FLYPOINT_PALLET` and -`ENGINE_FLYPOINT_VIRIDIAN` already visited. - -| # | Map | Target | Intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | anywhere outdoor | `SPAWN_PALLET` | Fly | STORMBADGE, `ENGINE_FLYPOINT_PALLET` | player at `PALLET_TOWN` (5,6) | -| 2 | `PALLET_TOWN` | (12,11) | walk (5,6)->(9,6)->(9,12)->(12,12)->(12,11) | - | warp to `OAKS_LAB` (4,11) | -| 3 | `OAKS_LAB` | (4,3) facing UP | walk, then talk to `OAKSLAB_OAK` | `VAR_BADGES == 16` | `EVENT_OPENED_MT_SILVER` set; expect `EVENT_TALKED_TO_OAK_IN_KANTO` on the first visit and a `ProfOaksPCBoot` dex-rating textbox chain | -| 4 | `OAKS_LAB` | (4,11) | walk into warp 1 | - | back on `PALLET_TOWN` (12,11) | -| 5 | anywhere outdoor | `SPAWN_VIRIDIAN` | Fly | `ENGINE_FLYPOINT_VIRIDIAN` | player at `VIRIDIAN_CITY` (23,26) | -| 6 (optional) | `VIRIDIAN_CITY` | (9,22) facing LEFT | walk (23,26)->(14,26)->(14,23)->(10,23)->(10,22)->(9,22), then Cut | Cut + HIVEBADGE, `EVENT_GOT_TM42_DREAM_EATER` clear | block at (8,22) replaced with `$6f` | -| 7 (optional) | `VIRIDIAN_CITY` | `VIRIDIANCITY_FISHER` at (6,23), talk from (6,22) facing DOWN | talk | bag has room | TM42 Dream Eater; `EVENT_GOT_TM42_DREAM_EATER` set | -| 8 | `VIRIDIAN_CITY` | west edge, x=0 at y=15 | walk (0,17)->(0,15) then step LEFT | - | map change to `ROUTE_22` (39,7) | -| 9 | `ROUTE_22` | (35,12) | walk (39,7)->(35,7)->(35,11)->(35,12) | - | standing on a `COLL_HOP_DOWN` tile | -| 10 | `ROUTE_22` | press DOWN | ledge hop | - | player at (35,14) | -| 11 | `ROUTE_22` | (33,12) | walk (35,14)->(33,14)->(33,13)->(33,12) | - | - | -| 12 | `ROUTE_22` | (31,5) | walk (33,12)->(33,11)->(31,11)->(31,8)->(31,7)->(31,6)->(31,5) - crosses the tall grass at (30..33, 8..11) | - | expect wild encounters, 10% per step in grass | -| 13 | `ROUTE_22` | (18,6) | walk west along y=5 to (18,5), then DOWN onto (18,6) | - | standing on a ledge tile | -| 14 | `ROUTE_22` | press DOWN | ledge hop | - | player at (18,8), in the west grass patch | -| 15 | `ROUTE_22` | (13,5) | walk (18,8)->(18,12)->(14,12)->(14,8)->(13,8)->(13,7)->(13,6)->(13,5) | - | warp to `VICTORY_ROAD_GATE` (17,7) | -| 16 | `VICTORY_ROAD_GATE` | (1,7) | walk (17,7)->(17,5)->west along y=5->(1,5)->(1,7) | `EVENT_OPENED_MT_SILVER` **and** `EVENT_FOUGHT_SNORLAX` set (both black belts hidden) | warp to `ROUTE_28` (33,5) | -| 17 | `ROUTE_28` | (0,12) | walk (33,5)->(33,6)->(28,6)->(28,8), hop (27,8)->(27,10), west y=10 to (18,10)->(18,9)->(10,9)->(10,10)->(8,10), hop ->(8,12), west to (0,12) | - | map change to `SILVER_CAVE_OUTSIDE` (39,30) | -| 18 | `SILVER_CAVE_OUTSIDE` | (23,20) | walk north-west to the Pokecenter door | - | `ENGINE_FLYPOINT_SILVER_CAVE` set by `MAPCALLBACK_NEWMAP` | -| 19 | `SILVER_CAVE_OUTSIDE` | (23,19) | step UP into warp 1 | - | `SILVER_CAVE_POKECENTER_1F` (3,7) | -| 20 | `SILVER_CAVE_POKECENTER_1F` | nurse at (3,1), talk from (3,2) facing UP | heal | - | party restored | -| 21 | `SILVER_CAVE_POKECENTER_1F` | (3,7) | step DOWN into warp 1 | - | `SILVER_CAVE_OUTSIDE` (23,19) | -| 22 | `SILVER_CAVE_OUTSIDE` | (30,24) facing RIGHT | walk (23,20)->(25,20)->(25,22)->(30,22)->(30,24), then Cut | Cut + HIVEBADGE | (31,24) replaced with `$4c` | -| 23 | `SILVER_CAVE_OUTSIDE` | (34,24) facing UP | walk (31,24)->(31,25)->(34,25)->(34,24), then Cut | Cut + HIVEBADGE | (34,23) replaced with `$6e` | -| 24 | `SILVER_CAVE_OUTSIDE` | (39,21) | walk (34,23)->(34,21)->(39,21), step RIGHT | - | map change to `ROUTE_28` (0,3) | -| 25 | `ROUTE_28` | (25,3) facing UP | walk (0,3)->(5,3)->(5,4)->(7,4)->(21,4)->(21,3)->(25,3), press A | `EVENT_ROUTE_28_HIDDEN_RARE_CANDY` clear | Rare Candy; flag set | -| 26 | `ROUTE_28` | (7,3) | walk back west along y=4 to (7,4), step UP | - | warp to `ROUTE_28_STEEL_WING_HOUSE` (2,7) | -| 27 | `ROUTE_28_STEEL_WING_HOUSE` | `Celebrity` at (2,3), talk from (2,4) facing UP | talk | bag has room, `EVENT_GOT_TM47_STEEL_WING` clear | TM47 Steel Wing; flag set | -| 28 | `ROUTE_28_STEEL_WING_HOUSE` | (2,7) | step DOWN into warp 1 | - | `ROUTE_28` (7,3) | -| 29 | `ROUTE_28` | any y=4 ledge (x=4..9) | walk to it and press DOWN | - | one-way drop into the Route 28 main body (grass at y=6) - the strip cannot be re-entered from here | -| 30 | - | shopping run | Fly to Mahogany, buy, Fly back to `SPAWN_MT_SILVER` | `ENGINE_FLYPOINT_SILVER_CAVE` set in step 18 | ready for `SILVER_CAVE_OUTSIDE` warp 2 at (18,11) - next section | - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers, dimensions, warps / coord / bg / object event tables for all of these maps | `src/import/RomExtractorGen2.lua:787-862` (`warps`, `coordEvents`, `bgEvents`, `objects`), consumed by `src/world/gen2/Map.lua` | implemented, and generic - no per-map work needed | -| Map connections and the offset landing maths | `src/world/gen2/Map.lua:71-92` `Map.connectionLanding` | implemented (same block-offset maths this doc's route uses) | -| `object_event` event-flag masking (the black-belt gate) | `src/world/gen2/Events.lua:40-44` `Events:objectVisible`, called from `src/world/gen2/World.lua:5106` and `:5122` | implemented, with the correct "flag set -> hidden" polarity | -| `Oak`'s opcode chain (`checkevent` / `readvar` / `setevent` / `special`) | `src/script/gen2/Vm.lua:187` (`checkevent`), `:670` (`readvar`), `src/world/gen2/World.lua:1240-1245` (`VAR_BADGES` = popcount of both badge bytes) | implemented | -| `special ProfOaksPCBoot` (the dex rating after every Oak conversation) | `src/script/gen2/Specials.lua:1778` `H.ProfOaksPCBoot` | implemented, including the `OakRatings` table and the fanfare wait | -| `verbosegiveitem` for TM42 / TM47, including the bag-full `iffalse` arm | `src/script/gen2/Vm.lua:490-498` | implemented | -| `hiddenitem` (Route 28 Rare Candy) via `BGEVENT_ITEM` | `src/world/gen2/HiddenItems.lua` (whole file), wired at `src/world/gen2/World.lua:30` and `:3423` | implemented | -| `jumpstd` for `PokecenterNurseScript` / `MagazineBookshelfScript` / `DifficultBookshelfScript` / `PokecenterSignScript` / `MartSignScript` | `src/script/gen2/Vm.lua:742-747` | implemented (std scripts are extracted and run through the same VM) | -| Cut: badge + party-move check, `CutTreeBlockPointers` lookup, block replacement | `src/world/gen2/FieldMoves.lua:101-107` (badge map), `:183-237` (`field_move_blocks.asm` transcribed), `:454`, `:605-619` | implemented | -| Fly: STORMBADGE check, flypoint table, `ENGINE_FLYPOINT_*` bits | `src/world/gen2/FieldMoves.lua:336-380` (`FLYPOINTS`, incl. `SPAWN_PALLET` 52, `SPAWN_VIRIDIAN` 53, `SPAWN_MT_SILVER` 75), `src/ui/TownMap.lua:146-208`, `src/world/gen2/World.lua:4292` | implemented | -| Scene scripts / `setscene` / coord-event dispatch (Victory Road Gate badge check) | `src/world/gen2/World.lua:470` (`mapScenes`), `:5013-5033` | implemented | -| `cry FEAROW` on the celebrity's pet | `src/script/gen2/Vm.lua:621` | implemented | -| **Ledge hopping (`COLL_HOP_DOWN` / `HOP_LEFT` / `HOP_RIGHT` / `HOP_DOWN_LEFT` / `HOP_DOWN_RIGHT`)** | `src/world/gen2/Player.lua:50-83` `Player:tryMove` - plain single-cell step, no `CheckLedge` equivalent; `src/world/gen2/Permissions.lua` maps `$a0..$a7` to `LAND` so the tiles are walkable but never jump | **missing.** Route 22 cannot be crossed without it: `(35,12)` and `(18,6)` are ledges over `COLL_WALL`, and there is no alternative route to the gate warp. Route 28's west strip also exits only by ledge | -| Route 22 / Route 28 / Silver Cave wild tables | generic (`data/wild/*` extracted by `RomExtractorGen2.lua`) | implemented as data; not separately verified for these three maps | -| A driver that walks any of this | `tests/drivers/gold_*.lua` (25 drivers; none touches Kanto or Mt. Silver) | missing | - -## 6. Unresolved / verify by hand - -- **"Items on Route 22: TM42 Dream Eater"** - the asm puts the Dream Eater NPC on - `MAP_VIRIDIAN_CITY` at (6,23) (`maps/ViridianCity.asm:239`). `maps/Route22.asm` - has an empty `def_object_events` and no item bg event at all. The FAQ's prose - ("If you cut the tree to the west of Viridian City") is right; its item header - is wrong. -- **"There's also a Pokémon Center here" (Route 28)** - there is no Pokecenter on - `MAP_ROUTE_28`. The nearest one is `MAP_SILVER_CAVE_POKECENTER_1F`, entered from - `MAP_SILVER_CAVE_OUTSIDE` warp 1 at (23,19), one map west. -- **"Items [Route 28]: TM47 Steel Wing"** - TM47 is inside - `MAP_ROUTE_28_STEEL_WING_HOUSE`. Route 28 itself has one item, the hidden Rare - Candy at (25,2), which the FAQ never mentions. -- **"#232 Donphan (Silver only)"** - correct as far as it goes, but the FAQ omits - what Gold puts in that slot (Ursaring, 20%) and omits Doduo/Dodrio entirely. - It also does not say Sneasel is nite-only. -- **Route 22 Pokemon list** - the FAQ lists only Spearow, Rattata and Poliwag. - `data/wild/kanto_grass.asm:1033` also has Doduo (10%), Ponyta (5%) and Fearow - (4%+1%) in the morn/day tables. -- **The FAQ's "keep heading north, west, then north"** is a fair prose summary but - omits the two mandatory ledge hops. The waypoint list in section 4 was derived - by BFS over `Route22.blk` against `data/tilesets/kanto_collision.asm` with the - ledge rule "step onto a `COLL_HOP_*` tile, then a matching press moves two - cells". That rule was inferred from the collision permission table - (`$a0..$a7` are `LAND_TILE`) and from the fact that every ledge on these maps - has `COLL_WALL` directly beyond it; the exact `DoPlayerMovement` ledge branch - in `engine/overworld/player_movement.asm` was **not** read line by line. - Worth confirming before a driver depends on the two-cell jump distance. -- **Route 28's "unreachable" rows** - `SILVER_CAVE_OUTSIDE`'s east edge is also - walkable at y=14..18 and y=33..35, which map to `ROUTE_28` y=-4..0 and y=15..17. - Those Route 28 rows are outside the component reachable from either the gate or - the west strip, so they look like filler; not chased further. -- **Mt. Silver entry itself** - `maps/SilverCaveOutside.asm` has no coord event, - no callback check and no NPC guarding warp 2 at (18,11), and - `EVENT_OPENED_MT_SILVER` is referenced in exactly two places - (`maps/OaksLab.asm:18,43` and `maps/VictoryRoadGate.asm:118`). So the flag gates - the *corridor*, not the cave mouth. If a save is hacked past the gate NPC, the - cave is open. -- **Party/level requirements** - the FAQ's "at least 10 Revives, 10 Hyper Potions, - 5 Escape Ropes, 5 Max Repels, and 50 Ultra Balls" is advice, not a coded gate. - Nothing in the asm checks the bag here. diff --git a/docs/gold-walkthrough/asm-walk/section-30-lapras.md b/docs/gold-walkthrough/asm-walk/section-30-lapras.md deleted file mode 100644 index bc41d5f6..00000000 --- a/docs/gold-walkthrough/asm-walk/section-30-lapras.md +++ /dev/null @@ -1,611 +0,0 @@ -# Section 30 - Lapras - -Source: `../section-30-lapras.txt` -Maps covered: `MAP_UNION_CAVE_1F`, `MAP_UNION_CAVE_B1F`, `MAP_UNION_CAVE_B2F` -(approach only: `MAP_ROUTE_32`, whose warp into the cave is cited but which -belongs to section 03) -Badges / key milestones in this section: no badge. The milestone is the **static -level-20 Lapras** on Union Cave B2F, which only exists on a Friday and only once -per in-game day (`ENGINE_UNION_CAVE_LAPRAS`). Five optional trainers -(Andrew, Calvin, Nick, Gwen, Emma) and two item balls sit on the way in. - -This is a *revisit* section: Union Cave 1F/B1F were first walked in section 03. -Everything here is reachable only with Surf, i.e. after the Fog Badge. - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_32` | `maps/Route32.asm` | walked south from Violet City (`connection south, Route32, ROUTE_32, 0` in `data/maps/attributes.asm`) | `warp_event 6, 79, UNION_CAVE_1F, 4` | "head to Violet City and then go south through Route 32 into Union Cave" | -| 2 | `MAP_UNION_CAVE_1F` | `maps/UnionCave1F.asm` | warp 4 at `17, 3` (from Route 32) | `warp_event 3, 33, UNION_CAVE_B1F, 4` | walk past Daniel / Russell / Bill, surf the south-west lake, take the bottom-left ladder | -| 3 | `MAP_UNION_CAVE_B1F` | `maps/UnionCaveB1F.asm` | warp 4 at `3, 33` | `warp_event 17, 31, UNION_CAVE_B2F, 1` | Pokemaniac Andrew stands at `5, 32` right next to the arrival ladder; surf east to Calvin at `17, 30`, then the ladder beside him | -| 4 | `MAP_UNION_CAVE_B2F` | `maps/UnionCaveB2F.asm` | warp 1 at `5, 3` | same warp back up, or Escape Rope / Dig | Hyper Potion, Nick, Gwen, Emma, then the Lapras object at `11, 31` | - -The walkthrough ends inside Union Cave B2F ("You can then use Escape Rope to -leave Union Cave"); it does not spill into a neighbouring section's map. - -## 2. Maps - -### MAP_UNION_CAVE_1F - -- Script: `maps/UnionCave1F.asm` -- Blocks: `maps/UnionCave1F.blk` -- Header: `data/maps/maps.asm:107` -> `map UnionCave1F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` - (the `TRUE` field is the phone-service flag: **no phone calls in Union Cave**) -- Dimensions: `constants/map_constants.asm:94` -> `map_const UNION_CAVE_1F, 10, 18` (10x18 blocks = 20x36 walk cells) -- Attributes: `data/maps/attributes.asm:426` -> `map_attributes UnionCave1F, UNION_CAVE_1F, $09` -- Connections: none (no `connection` rows follow the attributes line) -- Map group: `MapGroup_Dungeons` (`data/maps/maps.asm:77`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 19 | `UNION_CAVE_B1F` | 3 | -| 2 | 3 | 33 | `UNION_CAVE_B1F` | 4 | -| 3 | 17 | 31 | `ROUTE_33` | 1 | -| 4 | 17 | 3 | `ROUTE_32` | 4 | - -**Coord events** (`def_coord_events`) - -None - the block is empty. - -**BG events** (`def_bg_events`) - -None. (`UnionCave1FUnusedSign` / `UnionCave1FUnusedSignText` exist in the file -but are marked `; unreferenced` and are not in any `bg_event` row.) - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVE1F_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 4 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerHikerDaniel` | -1 | -| `UNIONCAVE1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 4 | 21 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerPokemaniacLarry` | -1 | -| `UNIONCAVE1F_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 15 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerHikerRussell` | -1 | -| `UNIONCAVE1F_FISHER1` | `SPRITE_FISHER` | 16 | 31 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerFirebreatherRay` | -1 | -| `UNIONCAVE1F_FISHER2` | `SPRITE_FISHER` | 15 | 15 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerFirebreatherBill` | -1 | -| `UNIONCAVE1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 17 | 21 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FGreatBall` | `EVENT_UNION_CAVE_1F_GREAT_BALL` | -| `UNIONCAVE1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 4 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FPotion` | `EVENT_UNION_CAVE_1F_POTION` | -| `UNIONCAVE1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 4 | 17 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FXAttack` | `EVENT_UNION_CAVE_1F_X_ATTACK` | -| `UNIONCAVE1F_POKE_BALL4` | `SPRITE_POKE_BALL` | 12 | 33 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCave1FAwakening` | `EVENT_UNION_CAVE_1F_AWAKENING` | - -**Scripts of interest** - -- `UnionCave1F_MapScripts` - `def_scene_scripts` and `def_callbacks` are both - empty. Nothing on 1F is time- or flag-gated; the floor is pure walking. -- `PokemaniacLarryAfterBattleText` and `FirebreatherBillAfterBattleText` are the - in-game hints the walkthrough is acting on: *"Every Friday, you can hear - #MON roars from deep inside the cave."* and *"On weekends, you can hear - strange roars from deep in the cave."* Only Larry's Friday line matches the - code (see B2F callback below). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_UNION_CAVE_1F_GREAT_BALL` | `constants/event_flags.asm` | itemball object row | set once the ball is taken; masks the object | -| `EVENT_UNION_CAVE_1F_POTION` | `constants/event_flags.asm` | itemball object row | same | -| `EVENT_UNION_CAVE_1F_X_ATTACK` | `constants/event_flags.asm` | itemball object row | same | -| `EVENT_UNION_CAVE_1F_AWAKENING` | `constants/event_flags.asm` | itemball object row | same | -| `EVENT_BEAT_HIKER_DANIEL`, `EVENT_BEAT_HIKER_RUSSELL`, `EVENT_BEAT_POKEMANIAC_LARRY`, `EVENT_BEAT_FIREBREATHER_BILL`, `EVENT_BEAT_FIREBREATHER_RAY` | `constants/event_flags.asm` | 3rd argument of each `trainer` macro row | set = already beaten, no eyesight trigger | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `GREAT_BALL` | item ball at 17,21 | `UnionCave1FGreatBall` (`itemball GREAT_BALL`) | `EVENT_UNION_CAVE_1F_GREAT_BALL` | -| `POTION` | item ball at 4,2 | `UnionCave1FPotion` | `EVENT_UNION_CAVE_1F_POTION` | -| `X_ATTACK` | item ball at 4,17 | `UnionCave1FXAttack` | `EVENT_UNION_CAVE_1F_X_ATTACK` | -| `AWAKENING` | item ball at 12,33 | `UnionCave1FAwakening` | `EVENT_UNION_CAVE_1F_AWAKENING` | - -**Trainers** (all optional on this pass; the walkthrough only mentions walking past them) - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `HIKER`, `DANIEL` | `HIKER` | `DANIEL` | `"DANIEL@"` | `TrainerHikerDaniel` | phone service disabled on this map (`TRUE` in the header) | -| `HIKER`, `RUSSELL` | `HIKER` | `RUSSELL` | `"RUSSELL@"` | `TrainerHikerRussell` | as above | -| `POKEMANIAC`, `LARRY` | `POKEMANIAC` | `LARRY` | `"LARRY@"` | `TrainerPokemaniacLarry` | as above | -| `FIREBREATHER`, `BILL` | `FIREBREATHER` | `BILL` | `"BILL@"` | `TrainerFirebreatherBill` | as above | -| `FIREBREATHER`, `RAY` | `FIREBREATHER` | `RAY` | `"RAY@"` | `TrainerFirebreatherRay` | as above | - -(Party rows for these five were not transcribed - they belong to section 03 and -the walkthrough tells the bot to walk past them, not fight them.) - -**Wild encounters** - -Grass/cave, `data/wild/johto_grass.asm:444` `def_grass_wildmons UNION_CAVE_1F`, -rate `6 percent` for morn/day/nite alike. Gold list (identical across all three -time slots): - -| slot | level | species | -|---|---|---| -| 1 | 6 | `GEODUDE` | -| 2 | 6 | `SANDSHREW` (Silver: `RATTATA`) | -| 3 | 5 | `ZUBAT` | -| 4 | 4 | `RATTATA` | -| 5 | 7 | `ZUBAT` | -| 6 | 6 | `ONIX` | -| 7 | 6 | `ONIX` | - -Water, `data/wild/johto_water.asm:12` `def_water_wildmons UNION_CAVE_1F`, rate -`2 percent`: `15 WOOPER`, `20 QUAGSIRE`, `15 QUAGSIRE`. - -Fishing group is `FISHGROUP_LAKE` (header row). - -### MAP_UNION_CAVE_B1F - -- Script: `maps/UnionCaveB1F.asm` -- Blocks: `maps/UnionCaveB1F.blk` -- Header: `data/maps/maps.asm:108` -> `map UnionCaveB1F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:95` -> `map_const UNION_CAVE_B1F, 10, 18` -- Attributes: `data/maps/attributes.asm:427` -> `map_attributes UnionCaveB1F, UNION_CAVE_B1F, $09` -- Connections: none -- Symbols: `44:5415 UnionCaveB1F_MapScripts`, `44:56df UnionCaveB1F_MapEvents` (`pokegold.sym`) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 3 | `RUINS_OF_ALPH_OUTSIDE` | 7 | -| 2 | 3 | 11 | `RUINS_OF_ALPH_OUTSIDE` | 8 | -| 3 | 7 | 19 | `UNION_CAVE_1F` | 1 | -| 4 | 3 | 33 | `UNION_CAVE_1F` | 2 | -| 5 | 17 | 31 | `UNION_CAVE_B2F` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -None. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVEB1F_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 10 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerHikerPhillip` | -1 | -| `UNIONCAVEB1F_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 17 | 10 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerHikerLeonard` | -1 | -| `UNIONCAVEB1F_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 32 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPokemaniacAndrew` | -1 | -| `UNIONCAVEB1F_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 17 | 30 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPokemaniacCalvin` | -1 | -| `UNIONCAVEB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 2 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB1FTMSwift` | `EVENT_UNION_CAVE_B1F_TM_SWIFT` | -| `UNIONCAVEB1F_BOULDER` | `SPRITE_BOULDER` | 7 | 10 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `UnionCaveB1FBoulder` | -1 | -| `UNIONCAVEB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 17 | 23 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB1FXDefend` | `EVENT_UNION_CAVE_B1F_X_DEFEND` | - -**Scripts of interest** - -- `UnionCaveB1F_MapScripts` - both `def_scene_scripts` and `def_callbacks` are - empty. Nothing on B1F is gated. -- `TrainerPokemaniacAndrew` (`44:5417`) - - `trainer POKEMANIAC, ANDREW, EVENT_BEAT_POKEMANIAC_ANDREW, PokemaniacAndrewSeenText, PokemaniacAndrewBeatenText, 0, .Script`. - `.Script` is the boilerplate after-battle path: - `endifjustbattled / opentext / writetext PokemaniacAndrewAfterBattleText / waitbutton / closetext / end`. - No flags beyond the beaten flag, no items, no warps. -- `TrainerPokemaniacCalvin` (`44:542b`) - identical shape with - `EVENT_BEAT_POKEMANIAC_CALVIN` and `PokemaniacCalvinAfterBattleText` - ("You demonstrated on me!" is `PokemaniacCalvinBeatenText`, matching the - walkthrough quote). -- `UnionCaveB1FBoulder` - `jumpstd StrengthBoulderScript`. Not on the - walkthrough's path to Lapras (it sits at `7, 10`, up near the Ruins of Alph - ladders), but it is the one Strength-gated object on the floor. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_BEAT_POKEMANIAC_ANDREW` | `constants/event_flags.asm:751` | `TrainerPokemaniacAndrew` | set after the win; suppresses the eyesight trigger | -| `EVENT_BEAT_POKEMANIAC_CALVIN` | `constants/event_flags.asm:752` | `TrainerPokemaniacCalvin` | same | -| `EVENT_BEAT_HIKER_PHILLIP`, `EVENT_BEAT_HIKER_LEONARD` | `constants/event_flags.asm` | the two Hiker `trainer` rows | not on the Lapras path | -| `EVENT_UNION_CAVE_B1F_TM_SWIFT` | `constants/event_flags.asm` | itemball object row | one-time TM_SWIFT | -| `EVENT_UNION_CAVE_B1F_X_DEFEND` | `constants/event_flags.asm` | itemball object row | one-time X_DEFEND | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `TM_SWIFT` | item ball at 2,16 | `UnionCaveB1FTMSwift` | `EVENT_UNION_CAVE_B1F_TM_SWIFT` | -| `X_DEFEND` | item ball at 17,23 | `UnionCaveB1FXDefend` | `EVENT_UNION_CAVE_B1F_X_DEFEND` | - -Neither is mentioned by the walkthrough; both are on the way to the B2F ladder. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `POKEMANIAC`, `ANDREW` | `POKEMANIAC` (`$1e`, `constants/trainer_constants.asm:245`) | `ANDREW` (id 2) | `"ANDREW@", TRAINERTYPE_NORMAL` at `data/trainers/parties.asm:1086`: L24 `MAROWAK`, L24 `MAROWAK` | `TrainerPokemaniacAndrew` | no phone (map header phone flag `TRUE`) | -| `POKEMANIAC`, `CALVIN` | `POKEMANIAC` | `CALVIN` (id 3) | `"CALVIN@", TRAINERTYPE_NORMAL` at `data/trainers/parties.asm:1092`: L26 `KANGASKHAN` | `TrainerPokemaniacCalvin` | no phone | -| `HIKER`, `PHILLIP` | `HIKER` | `PHILLIP` | `"PHILLIP@"` | `TrainerHikerPhillip` | off-path | -| `HIKER`, `LEONARD` | `HIKER` | `LEONARD` | `"LEONARD@"` | `TrainerHikerLeonard` | off-path | - -`TRAINERTYPE_NORMAL` means level+species only: no custom moves, no held items. - -Prize money: `POKEMANIAC` base reward is `db 15` (`data/trainers/attributes.asm:179` -`; Pokemaniac`). `ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) -computes `base * wCurPartyLevel` where the level is the **last** party row, and -`WinTrainerBattle` (`engine/battle/core.asm:2292`) pays that out four times -(`ld c, 4` at the `.okay` loop). So Andrew = 15*24*4 = **1440**, Calvin = -15*26*4 = **1560** - both match the walkthrough exactly. - -**Wild encounters** - -Grass/cave, `data/wild/johto_grass.asm:499` `def_grass_wildmons UNION_CAVE_B1F`, -rate `6 percent` morn/day/nite. Gold list (same in all three slots): - -| slot | level | species | -|---|---|---| -| 1 | 8 | `GEODUDE` | -| 2 | 8 | `SANDSHREW` (Silver: `RATTATA`) | -| 3 | 7 | `ZUBAT` | -| 4 | 8 | `ONIX` | -| 5 | 9 | `ZUBAT` | -| 6 | 6 | `RATTATA` | -| 7 | 6 | `RATTATA` | - -Water, `data/wild/johto_water.asm:19`, rate `2 percent`: `15 WOOPER`, -`20 QUAGSIRE`, `15 QUAGSIRE`. - -### MAP_UNION_CAVE_B2F - -This is the section's real content. - -- Script: `maps/UnionCaveB2F.asm` -- Blocks: `maps/UnionCaveB2F.blk` (`2b:466d UnionCaveB2F_Blocks`) -- Header: `data/maps/maps.asm:109` -> `map UnionCaveB2F, TILESET_CAVE, CAVE, LANDMARK_UNION_CAVE, MUSIC_UNION_CAVE, TRUE, PALETTE_NITE, FISHGROUP_SHORE` - (note: **`FISHGROUP_SHORE` here, not `FISHGROUP_LAKE` like the two floors above**) -- Dimensions: `constants/map_constants.asm:96` -> `map_const UNION_CAVE_B2F, 10, 18` (20x36 walk cells) -- Attributes: `data/maps/attributes.asm:428` -> `map_attributes UnionCaveB2F, UNION_CAVE_B2F, $09` -- Connections: none -- Symbols: `44:5759 UnionCaveB2F_MapScripts`, `44:575e UnionCaveB2FLaprasCallback`, `44:5770 UnionCaveLapras`, `44:59cb UnionCaveB2F_MapEvents`, `25:595d UnionCaveB2F_MapAttributes` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 5 | 3 | `UNION_CAVE_B1F` | 5 | - -One warp. There is no second exit: leaving is either back up this ladder, or -Escape Rope / Dig (the walkthrough's suggestion). - -**Coord events** (`def_coord_events`) - -None - the block is empty. Nothing on B2F is a trip-wire; every event is an -object you walk into the sight line of or press A on. - -**BG events** (`def_bg_events`) - -None. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `UNIONCAVEB2F_ROCKER` | `SPRITE_ROCKER` | 17 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5, `PAL_NPC_RED` | `TrainerCooltrainermNick` | -1 | -| `UNIONCAVEB2F_COOLTRAINER_F1` | `SPRITE_COOLTRAINER_F` | 5 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 1, `PAL_NPC_RED` | `TrainerCooltrainerfGwen` | -1 | -| `UNIONCAVEB2F_COOLTRAINER_F2` | `SPRITE_COOLTRAINER_F` | 3 | 28 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2, `PAL_NPC_RED` | `TrainerCooltrainerfEmma` | -1 | -| `UNIONCAVEB2F_POKE_BALL1` | `SPRITE_POKE_BALL` | 16 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB2FElixer` | `EVENT_UNION_CAVE_B2F_ELIXER` | -| `UNIONCAVEB2F_POKE_BALL2` | `SPRITE_POKE_BALL` | 12 | 19 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `UnionCaveB2FHyperPotion` | `EVENT_UNION_CAVE_B2F_HYPER_POTION` | -| `UNIONCAVEB2F_LAPRAS` | `SPRITE_SURF` | 11 | 31 | `SPRITEMOVEDATA_SWIM_WANDER`, radius x=1 y=1, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `UnionCaveLapras` | `EVENT_UNION_CAVE_B2F_LAPRAS` | - -Notes a bot needs: - -- Lapras uses `SPRITE_SURF` (`constants/sprite_constants.asm:87`, id 53) - the - same overworld sprite the player rides. It is not a Lapras sprite. -- `SPRITEMOVEDATA_SWIM_WANDER` with radius 1,1 means the object **drifts within - one cell of `11, 31`**. A driver must not hard-code an exact tile to face; it - should walk to the neighbourhood and press A at whichever adjacent water cell - the sprite is occupying. -- Its event flag is `EVENT_UNION_CAVE_B2F_LAPRAS`. `CheckObjectFlag` - (`engine/overworld/map_objects_2.asm:31`) masks an object when the flag is - **set**, so "Lapras present" = flag clear. - -**Scripts of interest** - -`UnionCaveB2F_MapScripts` (`44:5759`) - -``` - def_scene_scripts ; empty - def_callbacks - callback MAPCALLBACK_OBJECTS, UnionCaveB2FLaprasCallback -``` - -`UnionCaveB2FLaprasCallback` (`44:575e`) - the whole Friday rule, six opcodes: - -``` - checkflag ENGINE_UNION_CAVE_LAPRAS - iftrue .NoAppear - readvar VAR_WEEKDAY - ifequal FRIDAY, .Appear -.NoAppear: - disappear UNIONCAVEB2F_LAPRAS - endcallback -.Appear: - appear UNIONCAVEB2F_LAPRAS - endcallback -``` - -Control flow, spelled out: - -1. `checkflag ENGINE_UNION_CAVE_LAPRAS` - a **daily** engine flag - (`constants/engine_flags.asm:107`, in the `wDailyFlags2` block; bit - `DAILYFLAGS2_UNION_CAVE_LAPRAS_F` = 1, `constants/ram_constants.asm:315`). - Set means "already caught/fought it today" -> `.NoAppear`. -2. `readvar VAR_WEEKDAY` (`constants/script_constants.asm:59`, id `$0b`) then - `ifequal FRIDAY` (`constants/ram_constants.asm:218`, `FRIDAY` = 5, Sunday-based). - Only Friday reaches `.Appear`. -3. `appear` clears the object's event flag (`Script_appear`, - `engine/overworld/scripting.asm:879`, `ld b, 0 ; clear`); `disappear` sets it - (`Script_disappear`, `:887`, `ld b, 1 ; set`). - -Because the callback is `MAPCALLBACK_OBJECTS`, it re-runs on **every map load** -of B2F (`engine/overworld/map_setup.asm:81` `farcall LoadObjectMasks` follows -it). So a bot that walks out and back in on a non-Friday will find Lapras gone. - -`UnionCaveLapras` (`44:5770`) - the encounter itself: - -``` - faceplayer - cry LAPRAS - loadwildmon LAPRAS, 20 - startbattle - disappear UNIONCAVEB2F_LAPRAS - setflag ENGINE_UNION_CAVE_LAPRAS - reloadmapafterbattle - end -``` - -Load-bearing details: - -- `loadwildmon LAPRAS, 20` - a level **20 wild** Lapras, matching the - walkthrough. There is no `writevar VAR_BATTLETYPE` here, so unlike Ho-Oh / - Lugia / the Red Gyarados this is an **ordinary wild battle**: no forced item, - no forced shiny, no `BATTLETYPE_CANLOSE` mercy. -- `disappear` and `setflag` run **unconditionally after the battle**, with no - `iffalse` / `checkflag` guard on the outcome. So Lapras is consumed for the - day whether you caught it, KO'd it, ran, or blacked out mid-battle. That is - exactly why the walkthrough says to save first - a soft reset is the only - recovery. -- `setflag ENGINE_UNION_CAVE_LAPRAS` is the daily flag, not a permanent event. - `CheckDailyResetTimer` (`engine/overworld/time.asm:88`) zeroes `wDailyFlags1` - and `wDailyFlags2` whole once a day has elapsed, so it comes back next Friday. - -`TrainerCooltrainermNick` (`44:577f`), `TrainerCooltrainerfGwen` (`44:5793`), -`TrainerCooltrainerfEmma` (`44:57a7`) - all three are the plain -`trainer <class>, <id>, EVENT_BEAT_*, <seen>, <beaten>, 0, .Script` shape with -an `endifjustbattled / opentext / writetext ...AfterBattleText / waitbutton / -closetext / end` follow-up. No items, no warps, no flags beyond their own -`EVENT_BEAT_*`. - -`CooltrainerfEmmaAfterBattleText` is the in-map hint for the whole section: -*"Just once a week, a #MON comes to the water's edge. / I wanted to see that -#MON..."* - -`UnionCaveB2FElixer` = `itemball ELIXER`; `UnionCaveB2FHyperPotion` = -`itemball HYPER_POTION` (`macros/scripts/maps.asm:155`, two raw bytes -`db item, quantity`, not bytecode). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_UNION_CAVE_LAPRAS` | `constants/engine_flags.asm:107` (backed by `data/events/engine_flags.asm:117` -> `wDailyFlags2` bit `DAILYFLAGS2_UNION_CAVE_LAPRAS_F`) | read by `UnionCaveB2FLaprasCallback`, set by `UnionCaveLapras` | "Lapras already used up today". Cleared wholesale by `CheckDailyResetTimer` (`engine/overworld/time.asm:88-97`) | -| `EVENT_UNION_CAVE_B2F_LAPRAS` | `constants/event_flags.asm:1282` | written by the callback's `appear`/`disappear` | object visibility mask. **Clear = visible** (`engine/overworld/map_objects_2.asm:31` `CheckObjectFlag`) | -| `VAR_WEEKDAY` | `constants/script_constants.asm:59` (`$0b`) | `readvar` in the callback | `wCurDay`, 0 = `SUNDAY` .. 6 = `SATURDAY`; `FRIDAY` = 5 (`constants/ram_constants.asm:218`) | -| `EVENT_BEAT_COOLTRAINERM_NICK` | `constants/event_flags.asm:857` | `TrainerCooltrainermNick` | beaten flag | -| `EVENT_BEAT_COOLTRAINERF_GWEN` | `constants/event_flags.asm:877` | `TrainerCooltrainerfGwen` | beaten flag | -| `EVENT_BEAT_COOLTRAINERF_EMMA` | `constants/event_flags.asm:891` | `TrainerCooltrainerfEmma` | beaten flag | -| `EVENT_UNION_CAVE_B2F_ELIXER` | `constants/event_flags.asm:1025` | itemball object row | one-time | -| `EVENT_UNION_CAVE_B2F_HYPER_POTION` | `constants/event_flags.asm:1026` | itemball object row | one-time | - -There are no `SCENE_*` values on this map: `def_scene_scripts` is empty, so -`wMapScene` is never consulted here. - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `ELIXER` | item ball at 16,2 | `UnionCaveB2FElixer` (`itemball ELIXER`) | `EVENT_UNION_CAVE_B2F_ELIXER` | -| `HYPER_POTION` | item ball at 12,19 | `UnionCaveB2FHyperPotion` (`itemball HYPER_POTION`) | `EVENT_UNION_CAVE_B2F_HYPER_POTION` | - -The walkthrough names only the Hyper Potion ("Land on the right side to grab a -Hyper Potion"). The Elixer at `16, 2` sits near the arrival ladder at `5, 3` and -is never mentioned - free pickup for a bot on the way in. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `COOLTRAINERM`, `NICK` | `COOLTRAINERM` (`$1b`, `constants/trainer_constants.asm:184`) | `NICK` (id 1) | `"NICK@", TRAINERTYPE_MOVES` at `data/trainers/parties.asm:742` | `TrainerCooltrainermNick` | no phone (map phone flag `TRUE`) | -| `COOLTRAINERF`, `GWEN` | `COOLTRAINERF` (`$1c`, `:205`) | `GWEN` (id 1) | `"GWEN@", TRAINERTYPE_NORMAL` at `:867` | `TrainerCooltrainerfGwen` | no phone | -| `COOLTRAINERF`, `EMMA` | `COOLTRAINERF` | `EMMA` (id 15) | `"EMMA@", TRAINERTYPE_NORMAL` at `:954` | `TrainerCooltrainerfEmma` | no phone | - -Parties verbatim: - -`NICK` (`TRAINERTYPE_MOVES` - custom movesets, no items): - -| # | level | species | moves | -|---|---|---|---| -| 1 | 26 | `CHARMANDER` | `EMBER`, `SMOKESCREEN`, `RAGE`, `SCARY_FACE` | -| 2 | 26 | `SQUIRTLE` | `WITHDRAW`, `WATER_GUN`, `BITE`, `CURSE` | -| 3 | 26 | `BULBASAUR` | `LEECH_SEED`, `POISONPOWDER`, `SLEEP_POWDER`, `RAZOR_LEAF` | - -`GWEN` (`TRAINERTYPE_NORMAL`): - -| # | level | species | -|---|---|---| -| 1 | 26 | `EEVEE` | -| 2 | 22 | `FLAREON` | -| 3 | 22 | `VAPOREON` | -| 4 | 22 | `JOLTEON` | - -`EMMA` (`TRAINERTYPE_NORMAL`): L28 `POLIWHIRL`, single mon. - -Prize money (`base * last-party-level * 4`, see the B1F note): `COOLTRAINERM` -and `COOLTRAINERF` both have `db 12 ; base reward` -(`data/trainers/attributes.asm:161` `; Cooltrainerm`, `:167` `; Cooltrainerf`). -Nick = 12*26*4 = **1248**, Gwen = 12*22*4 = **1056** (last row is the L22 -Jolteon), Emma = 12*28*4 = **1344**. All three match the walkthrough. - -AI: both Cooltrainer classes carry the full -`AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY` -set and `SWITCH_SOMETIMES`; Pokemaniac is the weaker -`AI_BASIC | AI_SETUP | AI_OFFENSIVE | AI_AGGRESSIVE | AI_STATUS`. None of the -five hold items (`db NO_ITEM, NO_ITEM`). - -**Wild encounters** - -Grass/cave, `data/wild/johto_grass.asm:554` `def_grass_wildmons UNION_CAVE_B2F`, -rate `4 percent`. This table is **not** split by version, and all three -time-of-day slots are identical: - -| slot | level | species | -|---|---|---| -| 1 | 22 | `ZUBAT` | -| 2 | 22 | `RATICATE` | -| 3 | 22 | `GOLBAT` | -| 4 | 21 | `GEODUDE` | -| 5 | 20 | `RATTATA` | -| 6 | 23 | `ONIX` | -| 7 | 23 | `ONIX` | - -Water (the surf legs the walkthrough spends most of its time on), -`data/wild/johto_water.asm:26` `def_water_wildmons UNION_CAVE_B2F`, rate -`4 percent` (double the 1F/B1F water rate): - -| slot | level | species | -|---|---|---| -| 1 | 15 | `TENTACOOL` | -| 2 | 20 | `QUAGSIRE` | -| 3 | 20 | `TENTACRUEL` | - -Fishing group is `FISHGROUP_SHORE` (`data/maps/maps.asm:109`). No headbutt or -rock-smash tables apply - it is a cave with no trees. - -The walkthrough's Max Repel advice: `REPEL_STEPS` in the port mirrors -`RepelEffect / SuperRepelEffect / MaxRepelEffect`; MAX_REPEL is 250 steps. With -the B2F encounters capped at level 23 and the lead mon typically above that, -Repel suppresses essentially all of the grass rolls. - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Cannot cross any of the water on 1F / B1F / B2F | `engine/events/overworld.asm:322` `SurfFunction.TrySurf` (`ld de, ENGINE_FOGBADGE / call CheckBadge / jr c, .nofogbadge`), and the walk-into-water path `TrySurfOW` at `:469` | **Fog Badge** plus a party mon that knows `SURF` | beat Morty in Ecruteak Gym (section 07) | -| Lapras object is absent 6 days out of 7 | `maps/UnionCaveB2F.asm:15` `UnionCaveB2FLaprasCallback` -> `readvar VAR_WEEKDAY / ifequal FRIDAY` | in-game weekday must equal `FRIDAY` (`constants/ram_constants.asm:218`, value 5) | set the GB clock / wait for Friday. Re-checked on every B2F map load (`MAPCALLBACK_OBJECTS`) | -| Lapras is absent again after one encounter | same callback, first two opcodes: `checkflag ENGINE_UNION_CAVE_LAPRAS / iftrue .NoAppear`; the flag is set by `UnionCaveLapras` | flag must be clear | `CheckDailyResetTimer` (`engine/overworld/time.asm:88`) zeroes `wDailyFlags1`+`wDailyFlags2` once a real day has passed | -| Lapras is consumed even on a failed attempt | `UnionCaveLapras`: `startbattle` is followed unconditionally by `disappear` + `setflag` - no `iffalse` on `wBattleResult` | none - this is the failure mode, not a gate | save before pressing A; soft-reset on a bad outcome | -| Only one way out of B2F | `maps/UnionCaveB2F.asm` `def_warp_events` has exactly one row (`5, 3 -> UNION_CAVE_B1F, 5`) | - | Escape Rope / Dig short-circuits the return walk (B2F's tileset is `TILESET_CAVE`) | -| Boulder at B1F `7, 10` | `UnionCaveB1FBoulder` -> `jumpstd StrengthBoulderScript` | Strength / Plain Badge | not on the Lapras path; noted for completeness | - -Nothing in this section checks a badge count, a key item, or an `EVENT_*` -story flag. The only two real gates are **Surf** and **Friday**. - -## 4. Bot checklist - -Preconditions for the whole section: Fog Badge, a party mon with `SURF`, the -in-game weekday equal to `FRIDAY`, `ENGINE_UNION_CAVE_LAPRAS` clear, several -Ultra Balls, a status move (Sleep/Paralysis) and a **save written immediately -before the Lapras press**. - -| # | Map | Target | Input intent | Precondition | Postcondition | -|---|---|---|---|---|---| -| 1 | `MAP_ROUTE_32` | warp at `6, 79` | walk onto the warp | on Route 32 | now in `MAP_UNION_CAVE_1F` at `17, 3` (warp 4) | -| 2 | `MAP_UNION_CAVE_1F` | - | use `MAX_REPEL` from the pack | have one | 250 steps of encounter suppression | -| 3 | `MAP_UNION_CAVE_1F` | avoid sight lines of `UNIONCAVE1F_POKEFAN_M2` (`15, 8`, sight 3, facing left) and `UNIONCAVE1F_FISHER2` (`15, 15`, sight 2, spinner) | walk south-west | - | `EVENT_BEAT_*` untouched if avoided; the spinner is not reliably avoidable, budget for the battle | -| 4 | `MAP_UNION_CAVE_1F` | south-west water | press A facing water, or walk into it (`TrySurfOW`) | Fog Badge + `SURF` | `wPlayerState = PLAYER_SURF` | -| 5 | `MAP_UNION_CAVE_1F` | warp 2 at `3, 33` | step onto it | - | now in `MAP_UNION_CAVE_B1F` at `3, 33` (warp 4) | -| 6 | `MAP_UNION_CAVE_B1F` | `UNIONCAVEB1F_SUPER_NERD1` at `5, 32` (facing left, sight 3) | walk into the sight line / talk | - | battle Pokemaniac Andrew (2x L24 Marowak); sets `EVENT_BEAT_POKEMANIAC_ANDREW`, +1440 | -| 7 | `MAP_UNION_CAVE_B1F` | surf east along the y~30 water | walk east on water | surfing | reach the eastern shelf | -| 8 | `MAP_UNION_CAVE_B1F` | `UNIONCAVEB1F_SUPER_NERD2` at `17, 30` (facing left, sight 3) | walk into the sight line | - | battle Pokemaniac Calvin (L26 Kangaskhan); sets `EVENT_BEAT_POKEMANIAC_CALVIN`, +1560 | -| 9 | `MAP_UNION_CAVE_B1F` | optional item ball `17, 23` | press A | `EVENT_UNION_CAVE_B1F_X_DEFEND` clear | `X_DEFEND` in bag, flag set | -| 10 | `MAP_UNION_CAVE_B1F` | warp 5 at `17, 31` | step onto it | - | now in `MAP_UNION_CAVE_B2F` at `5, 3`; `MAPCALLBACK_OBJECTS` runs `UnionCaveB2FLaprasCallback` | -| 11 | `MAP_UNION_CAVE_B2F` | **assert** `UNIONCAVEB2F_LAPRAS` is present | read the object list | weekday == FRIDAY and `ENGINE_UNION_CAVE_LAPRAS` clear | if absent, abort the run - nothing later will fix it | -| 12 | `MAP_UNION_CAVE_B2F` | item ball `16, 2` | press A | `EVENT_UNION_CAVE_B2F_ELIXER` clear | `ELIXER` in bag (walkthrough omits this one) | -| 13 | `MAP_UNION_CAVE_B2F` | surf south from the entrance shelf | walk south on water | surfing | approach the y~19-23 shelves | -| 14 | `MAP_UNION_CAVE_B2F` | item ball `12, 19` (right-hand shore) | land, press A | `EVENT_UNION_CAVE_B2F_HYPER_POTION` clear | `HYPER_POTION` in bag | -| 15 | `MAP_UNION_CAVE_B2F` | `UNIONCAVEB2F_ROCKER` at `17, 23` (facing left, sight 5 - the longest on the floor) | walk into the sight line | - | battle Cooltrainer Nick (L26 Charmander/Squirtle/Bulbasaur, custom moves); sets `EVENT_BEAT_COOLTRAINERM_NICK`, +1248 | -| 16 | `MAP_UNION_CAVE_B2F` | cross to the left shore, head north to `UNIONCAVEB2F_COOLTRAINER_F1` at `5, 13` (spinner, sight 1) | walk adjacent | - | battle Cooltrainer Gwen (L26 Eevee, L22 Flareon/Vaporeon/Jolteon); sets `EVENT_BEAT_COOLTRAINERF_GWEN`, +1056 | -| 17 | `MAP_UNION_CAVE_B2F` | walk the left path south to `UNIONCAVEB2F_COOLTRAINER_F2` at `3, 28` (facing down, sight 2) | walk into the sight line | - | battle Cooltrainer Emma (L28 Poliwhirl); sets `EVENT_BEAT_COOLTRAINERF_EMMA`, +1344 | -| 18 | `MAP_UNION_CAVE_B2F` | **save** | open menu -> SAVE | Lapras still present | recoverable checkpoint | -| 19 | `MAP_UNION_CAVE_B2F` | surf east/south-east from `3, 28` toward `11, 31` | walk on water | surfing | the Lapras object drifts within +/-1 cell of `11, 31` (`SWIM_WANDER`, radius 1,1) - re-scan its live position each step | -| 20 | `MAP_UNION_CAVE_B2F` | press A facing `UNIONCAVEB2F_LAPRAS` | talk | object present | `UnionCaveLapras` runs: `cry LAPRAS`, `loadwildmon LAPRAS, 20`, `startbattle` | -| 21 | battle | L20 wild Lapras | inflict sleep/paralysis, chip to yellow/red, throw `ULTRA_BALL`s | - | on any exit: `disappear` + `setflag ENGINE_UNION_CAVE_LAPRAS`, then `reloadmapafterbattle` | -| 22 | - | verify Lapras in party/box | read party | - | if not caught, **reload the step-18 save**; do not walk out and back in, the callback will not re-place it | -| 23 | `MAP_UNION_CAVE_B2F` | use `ESCAPE_ROPE` | pack -> use | in a cave tileset | back at the last Pokemon Center door | - -Sight-line arithmetic for steps 6/8/15/16/17: `Trainers.sees` in the port -(`src/world/gen2/Trainers.lua:98`) is the transcription of `home/trainers.asm` - -the NPC must be facing the player's direction and the Chebyshev-free straight -distance must be `<= sight`. - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Union Cave 1F/B1F/B2F map data (headers, blocks, warps, objects) | `src/import/RomExtractorGen2.lua` (generic map/manifest extraction; per-map entries land in the private `data/generated/` cache) | implemented - data-driven, no per-map code | -| `MAPCALLBACK_OBJECTS` dispatch on map load | `src/world/gen2/World.lua:5700` (`self:runMapCallback("MAPCALLBACK_OBJECTS")`, deliberately *before* the sprite rebuild) | implemented | -| `readvar VAR_WEEKDAY` | `src/world/gen2/World.lua:101` (`VAR_WEEKDAY = 0x0b`) and `:1222` (`if varId == VAR_WEEKDAY then return self:weekday() end`) | implemented | -| `checkflag` / `setflag` on `ENGINE_*` | `src/script/gen2/Vm.lua:195` / `:208` | implemented | -| `ENGINE_UNION_CAVE_LAPRAS` as a daily flag | `src/core/gen2/Apricorns.lua:84` (`{ id = 88, name = "ENGINE_UNION_CAVE_LAPRAS" }` in the wDailyFlags1/2 id list) and `:344` (`save.dailyFlags = {}` on the daily wipe) | implemented | -| `appear` / `disappear` object masking | `src/script/gen2/Opcodes.lua:115-116`, consumed by the World object rebuild | implemented | -| `cry` | `src/script/gen2/Vm.lua:621` | implemented | -| `loadwildmon` + `startbattle` + `reloadmapafterbattle` (the whole static-encounter shape) | `src/script/gen2/Vm.lua:836`, `:817`, `:886` | implemented | -| Wild catch rate / Ultra Ball / status bonus | `src/battle/gen2/Catching.lua` (transcribes `PokeBallEffect`, including the two cart bugs) | implemented | -| Water encounter tables while surfing | `src/world/gen2/FieldMoves.lua:175` ("standing in a cave rolls the grass list; surfing rolls the water" ...) + `src/battle/gen2/Encounter.lua` | implemented | -| Surf gate on `ENGINE_FOGBADGE` | `src/world/gen2/FieldMoves.lua:106` (`SURF = "FOG"`) and `FieldMoves.surfFromMenu` at `:481` | implemented | -| Trainer eyesight approach + battle | `src/world/gen2/Trainers.lua:98` (`Trainers.sees`), `src/world/gen2/World.lua:5212` / `:5227` / `:5242` | implemented | -| Trainer prize money (base x level x 4, Mom's split) | `src/battle/gen2/Prize.lua:82` (`Prize.reward`), `:92` (`Prize.rewardLevel`) | implemented - and it already documents the `ld c, 4` that makes the walkthrough's figures right | -| Repel step counting (the Max Repel advice) | `src/world/gen2/World.lua:317` (`REPEL_STEPS = { REPEL = 100, SUPER_REPEL = 200, MAX_REPEL = 250 }`), `:3366` | implemented | -| **Item ball pickup** (`OBJECTTYPE_ITEMBALL`, i.e. the B2F Elixer / Hyper Potion and every ball on 1F/B1F) | extractor stores the item at `src/import/RomExtractorGen2.lua:2969` (`obj.itemball = readItemBall(...)`) and deliberately assigns **no** `scriptKey`; `World:talk` only dispatches on `npc.def.scriptKey` (`src/world/gen2/World.lua:5276`) | **missing** - no consumer of `obj.itemball` exists anywhere under `src/`. Pressing A on a Poke Ball object currently does nothing | -| Escape Rope from a cave | `src/ui/PartyMenu.lua:634` is the Gen 1 DIG/ESCAPE_ROPE path; no Gen 2 equivalent found under `src/core/gen2/` or `src/world/gen2/` | **unverified / likely missing for Gen 2** - a driver should warp out manually | -| A driver exercising the weekday-gated `MAPCALLBACK_OBJECTS` shape | `tests/drivers/gold_map_callbacks.lua:117-156` covers the same pattern on Route 29 (Tuscany, Tuesday) | implemented for the pattern, **not** for Union Cave B2F specifically - no `tests/drivers/gold_*` driver mentions Union Cave or Lapras | - -Honest summary: everything the Lapras encounter itself needs is in place -(callback dispatch, weekday var, daily flags, `loadwildmon`/`startbattle`, -catching). The two gaps that would bite a bot on this route are **item balls -are not pickable** and **no Gen 2 Escape Rope**, neither of which blocks Lapras. - -## 6. Unresolved / verify by hand - -- **"Lapras only appears on Friday" vs. the 1F NPC hints.** The code checks - exactly `FRIDAY` (`UnionCaveB2FLaprasCallback`). But - `FirebreatherBillAfterBattleText` on 1F says *"On weekends, you can hear - strange roars from deep in the cave"* while `PokemaniacLarryAfterBattleText` - says *"Every Friday"*. Larry is right, Bill's line is flavour that contradicts - the callback. The walkthrough follows Larry and is correct. -- **"once you've gotten HM Surf".** The asm imposes no HM/badge check on the - Lapras callback or on `UnionCaveLapras`; Surf is a *reachability* requirement - (B2F's Lapras sits at `11, 31` surrounded by water and the approach legs on - 1F/B1F cross water), enforced only by `SurfFunction.TrySurf`'s - `ENGINE_FOGBADGE` check. The walkthrough's phrasing implies a scripted gate - that does not exist. -- **EXP figures.** The walkthrough quotes per-mon EXP (637 for a L24 Marowak, - 975 for a L26 Kangaskhan, 355/511/924/928/933/786, and two "?" entries). EXP - is computed at runtime from base EXP x level, not stored per trainer, so none - of these could be pinned to a table row. They are plausible but unverified - here; `data/pokemon/base_stats/*.asm` would be the place to check. -- **Party ordering.** The walkthrough lists Nick as Charmander / Bulbasaur / - Squirtle and Gwen as Eevee / Vaporeon / Jolteon / Flareon. `parties.asm` has - Nick as Charmander / **Squirtle** / **Bulbasaur** (`:742`) and Gwen as Eevee / - **Flareon** / **Vaporeon** / **Jolteon** (`:867`). The sets match; the order - does not. A bot planning lead matchups should use the asm order (it is also - what decides the prize level: Gwen's last row is Jolteon L22). -- **"Head almost all the way through till you see the guy on the cliff still - spinning around, which is the third trainer that you spot."** Read against the - 1F object table this is `UNIONCAVE1F_FISHER2` (Firebreather Bill, - `15, 15`, `SPRITEMOVEDATA_SPINRANDOM_FAST`), with Hiker Daniel (`4, 4`) and - Hiker Russell (`15, 8`) as the first two. That reading is an inference from - coordinates and movement data, not something the asm states; the exact walking - order depends on the `.blk` layout, which was not decoded here. -- **Which 1F ladder the walkthrough means.** "Down and to the left is a lake. - Surf past it and head down the ladder" is read as 1F warp 2 (`3, 33` -> - `UNION_CAVE_B1F` warp 4 at `3, 33`), because Andrew stands at B1F `5, 32`, - immediately beside that arrival point. 1F warp 1 (`5, 19` -> B1F warp 3 at - `7, 19`) would land nowhere near him. Verify against the block layout if a - driver's pathing disagrees. -- **B2F "right side" / "left side" landings.** The prose distinguishes a right - and a left shore; the object coordinates (`12, 19` Hyper Potion and `17, 23` - Nick on the right, `5, 13` Gwen and `3, 28` Emma on the left) are consistent - with that, but the actual water/land split comes from `UnionCaveB2F.blk`, - which was not decoded. -- **Elixer at B2F `16, 2`.** Present in the asm, absent from the walkthrough. - Not a contradiction, just an omission worth picking up. diff --git a/docs/gold-walkthrough/asm-walk/section-31-mount-silver-and-red.md b/docs/gold-walkthrough/asm-walk/section-31-mount-silver-and-red.md deleted file mode 100644 index b04fe29d..00000000 --- a/docs/gold-walkthrough/asm-walk/section-31-mount-silver-and-red.md +++ /dev/null @@ -1,715 +0,0 @@ -# Section 31 - Mount Silver and Red - -Source: `../section-31-mount-silver-and-red.txt` -Maps covered: `MAP_SILVER_CAVE_OUTSIDE`, `MAP_SILVER_CAVE_POKECENTER_1F`, -`MAP_SILVER_CAVE_ROOM_1`, `MAP_SILVER_CAVE_ROOM_2`, `MAP_SILVER_CAVE_ITEM_ROOMS`, -`MAP_SILVER_CAVE_ROOM_3` (plus `MAP_VICTORY_ROAD_GATE`, cited only as the gate -that lets you reach Route 28 in the first place). -Badges / key milestones in this section: no badge. The milestone is the `RED` -trainer battle in `maps/SilverCaveRoom3.asm` and the `credits` opcode it ends -on - the last scripted event in the game. - -Conventions reminder: disassembly paths are relative to the pokegold checkout -root; port paths are relative to this repo root; coordinates are the raw asm -values, i.e. **map cell coordinates**, 0-based from the map's top-left, the same -space `warp_event` / `bg_event` / `coord_event` use. `object_event` rows are -written in that same space (the `+ 4` in the `object_event` macro, -`macros/scripts/maps.asm` line 130, is the internal RAM border offset, not -something you add when reading the file). - ---- - -## 1. Route order - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_ROUTE_28` | `maps/Route28.asm` (previous section) | `VictoryRoadGate` warps 7/8 at (1,7)/(2,7) | west map connection | "Head west and up from there and you'll be on Mt. Silver." Route 28 itself belongs to the previous section; only the hop off its west edge is in scope here. | -| 1 | `MAP_SILVER_CAVE_OUTSIDE` | `maps/SilverCaveOutside.asm` | east connection from `Route28` (`data/maps/attributes.asm:165`, `connection east, Route28, ROUTE_28, 9`) | warp 2 at (18,11) | Arrive on Mt. Silver; `MAPCALLBACK_NEWMAP` registers the fly point. Optional detour into the Pokémon Center. | -| 2 | `MAP_SILVER_CAVE_POKECENTER_1F` | `maps/SilverCavePokecenter1F.asm` | `SilverCaveOutside` warp 1 at (23,19) | warps 1/2 at (3,7)/(4,7) | Heal before the climb; the granny is pure flavour. | -| 3 | `MAP_SILVER_CAVE_ROOM_1` | `maps/SilverCaveRoom1.asm` | `SilverCaveOutside` warp 2 at (18,11) | warp 2 at (15,1) | "When you get in, use HM Flash." Escape Rope, X Accuracy, Max Elixer, two hidden items. | -| 4 | `MAP_SILVER_CAVE_ROOM_2` | `maps/SilverCaveRoom2.asm` | `SilverCaveRoom1` warp 2 at (15,1) -> lands on warp 1 at (17,31) | warp 2 at (11,5) | The Surf + Waterfall floor. Two side warps into `SilverCaveItemRooms`. | -| 5 | `MAP_SILVER_CAVE_ITEM_ROOMS` | `maps/SilverCaveItemRooms.asm` | `SilverCaveRoom2` warp 3 at (13,21) or warp 4 at (23,3) | warps 1/2 at (13,3)/(7,15) | "use Surf on the lake, and then use Waterfall, you can go through an entrance to grab a Full Restore." Two *disconnected* rooms in one map. | -| 6 | `MAP_SILVER_CAVE_ROOM_3` | `maps/SilverCaveRoom3.asm` | `SilverCaveRoom2` warp 2 at (11,5) -> lands on warp 1 at (9,33) | (no exit warp other than back to Room 2) | "keep going up, and you'll find a trainer." Red at (9,10); beating him runs `credits`. | - -After `credits` the game does not return to the overworld normally: `Script_credits` -(`engine/overworld/scripting.asm:2215`) farcalls `RedCredits` -(`engine/events/halloffame.asm:34`), which sets `wSpawnAfterChampion = SPAWN_RED` -and jumps to `Credits`. `FinishContinueFunction` (`engine/menus/intro_menu.asm`) -sees `SPAWN_RED` on the way back out and calls `SpawnAfterRed`, which sets -`wDefaultSpawnpoint = SPAWN_MT_SILVER` and `hMapEntryMethod = MAPSETUP_WARP`, i.e. -you respawn at `data/maps/spawn_points.asm` -> `spawn SILVER_CAVE_OUTSIDE, 23, 20`, -the cell directly below the Pokémon Center door. - ---- - -## 2. Maps - -### MAP_SILVER_CAVE_OUTSIDE - -- Script: `maps/SilverCaveOutside.asm` -- Blocks: `maps/SilverCaveOutside.blk` (20 x 18 blocks = 40 x 36 cells) -- Header: `data/maps/maps.asm:397` -> - `map SilverCaveOutside, TILESET_KANTO, TOWN, LANDMARK_SILVER_CAVE, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_AUTO, FISHGROUP_POND` - (fields per the `map` macro at `data/maps/maps.asm:1`: tileset, environment, - location, music, phone-service flag, time-of-day palette, fishing group) -- Dimensions / attributes: `constants/map_constants.asm:367` - (`map_const SILVER_CAVE_OUTSIDE, 20, 18`), group `SILVER` = 19, map id 2 -- Connections: `data/maps/attributes.asm:164-165` - - `map_attributes SilverCaveOutside, SILVER_CAVE_OUTSIDE, $2c` / - `connection east, Route28, ROUTE_28, 9`. No north/south/west connection. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 23 | 19 | `SILVER_CAVE_POKECENTER_1F` | 1 | -| 2 | 18 | 11 | `SILVER_CAVE_ROOM_1` | 1 | - -**Coord events** (`def_coord_events`) - -None (the `def_coord_events` block is empty). - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 24 | 19 | `BGEVENT_READ` | `MtSilverPokecenterSign` -> `jumpstd PokecenterSignScript` | -| 17 | 13 | `BGEVENT_READ` | `MtSilverSign` -> `MtSilverSignText` ("MT.SILVER") | -| 9 | 25 | `BGEVENT_ITEM` | `SilverCaveOutsideHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_SILVER_CAVE_OUTSIDE_HIDDEN_FULL_RESTORE` | - -**Object events** (`def_object_events`) - -None. `def_object_events` is the last line of the file with no rows after it - -there is not a single NPC or item ball on the outdoor map. - -**Scripts of interest** - -- `SilverCaveOutside_MapScripts` (`47`-bank sibling; symbol `49:5f15`) has an - empty `def_scene_scripts` and one callback: - `callback MAPCALLBACK_NEWMAP, SilverCaveOutsideFlypointCallback`. -- `SilverCaveOutsideFlypointCallback` (`49:5f1a`) is two opcodes: - `setflag ENGINE_FLYPOINT_SILVER_CAVE` / `endcallback`. Simply *entering* the - map unlocks Fly to Mt. Silver; there is no NPC or sign to talk to. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `ENGINE_FLYPOINT_SILVER_CAVE` | `constants/engine_flags.asm:90` | set by `SilverCaveOutsideFlypointCallback` | Fly destination `LANDMARK_SILVER_CAVE` (`data/maps/flypoints.asm:16` -> `SPAWN_MT_SILVER`) becomes selectable. Postcondition of first entry. | -| `EVENT_SILVER_CAVE_OUTSIDE_HIDDEN_FULL_RESTORE` | `constants/event_flags.asm:194` | `hiddenitem` at bg_event (9,25) | One-shot guard on the hidden Full Restore. | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `FULL_RESTORE` | hidden, needs a face-and-A or ITEMFINDER sweep at (9,25) | `bg_event 9, 25, BGEVENT_ITEM, SilverCaveOutsideHiddenFullRestore` | `EVENT_SILVER_CAVE_OUTSIDE_HIDDEN_FULL_RESTORE` | - -**Trainers** - -None. - -**Wild encounters** - -`data/wild/johto_grass.asm:2319`, `def_grass_wildmons SILVER_CAVE_OUTSIDE`, -encounter rate `10 percent` for morn/day/nite alike. Gold column -(`IF DEF(_GOLD)`); the Silver column swaps `URSARING` -> `DONPHAN` and is -otherwise identical. - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 41 TANGELA | 41 TANGELA | 41 TANGELA | -| 2 | 42 PONYTA | 42 PONYTA | 42 PONYTA | -| 3 | 42 URSARING (Silver: DONPHAN) | 42 URSARING (Silver: DONPHAN) | 42 URSARING (Silver: DONPHAN) | -| 4 | 44 RAPIDASH | 44 RAPIDASH | 44 RAPIDASH | -| 5 | 41 DODUO | 41 DODUO | 38 SNEASEL | -| 6 | 43 DODRIO | 43 DODRIO | 42 SNEASEL | -| 7 | 43 DODRIO | 43 DODRIO | 42 SNEASEL | - -Water (`data/wild/johto_water.asm:271`, `def_water_wildmons SILVER_CAVE_OUTSIDE`, -`2 percent`): 35 POLIWHIRL / 40 POLIWHIRL / 35 POLIWAG. -Fishing group `FISHGROUP_POND` (`data/wild/fish.asm:15`). Headbutt: -`data/wild/treemon_maps.asm:39` -> `TREEMON_SET_NONE`, so no headbutt table. - -### MAP_SILVER_CAVE_POKECENTER_1F - -- Script: `maps/SilverCavePokecenter1F.asm` -- Blocks: `maps/SilverCavePokecenter1F.blk` (symbol `2b:42e8 SilverCavePokecenter1F_Blocks`) -- Header: `data/maps/maps.asm:398` -> - `map SilverCavePokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_SILVER_CAVE, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:368` (`map_const SILVER_CAVE_POKECENTER_1F, 5, 4`), group `SILVER` = 19, map id 3 -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 3 | 7 | `SILVER_CAVE_OUTSIDE` | 1 | -| 2 | 4 | 7 | `SILVER_CAVE_OUTSIDE` | 1 | -| 3 | 0 | 7 | `POKECENTER_2F` | 1 | - -**Coord events** / **BG events**: both blocks are empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILVERCAVEPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_SCRIPT` | `SilverCavePokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 (always) | -| `SILVERCAVEPOKECENTER1F_GRANNY` | `SPRITE_GRANNY` | 1 | 5 | `SPRITEMOVEDATA_STANDING_LEFT`, radius 2/1, hours -1/-1, pal 0 | `OBJECTTYPE_SCRIPT` | `SilverCavePokecenter1FGrannyScript` (`jumptextfaceplayer`) | -1 (always) | - -**Flags and events / Items / Trainers**: none. **Wild encounters**: none. - -### MAP_SILVER_CAVE_ROOM_1 - -- Script: `maps/SilverCaveRoom1.asm` (symbols `47:41e9 SilverCaveRoom1_MapScripts`, `47:41f7 SilverCaveRoom1_MapEvents`) -- Blocks: `maps/SilverCaveRoom1.blk` (10 x 18 blocks = 20 x 36 cells) -- Header: `data/maps/maps.asm:144` -> - `map SilverCaveRoom1, TILESET_DARK_CAVE, CAVE, LANDMARK_SILVER_CAVE, MUSIC_LIGHTHOUSE, TRUE, PALETTE_DARK, FISHGROUP_LAKE` - (the `TRUE` is the phone-service flag: no phone calls in here) -- Dimensions: `constants/map_constants.asm:131` (`map_const SILVER_CAVE_ROOM_1, 10, 18`), group `DUNGEONS` = 3, map id 66 -- Connections: none (dungeon interior) - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 33 | `SILVER_CAVE_OUTSIDE` | 2 | -| 2 | 15 | 1 | `SILVER_CAVE_ROOM_2` | 1 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 16 | 23 | `BGEVENT_ITEM` | `SilverCaveRoom1HiddenDireHit` -> `hiddenitem DIRE_HIT, EVENT_SILVER_CAVE_ROOM_1_HIDDEN_DIRE_HIT` | -| 17 | 12 | `BGEVENT_ITEM` | `SilverCaveRoom1HiddenUltraBall` -> `hiddenitem ULTRA_BALL, EVENT_SILVER_CAVE_ROOM_1_HIDDEN_ULTRA_BALL` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILVERCAVEROOM1_POKE_BALL1` | `SPRITE_POKE_BALL` | 4 | 9 | `SPRITEMOVEDATA_STILL`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_ITEMBALL` | `SilverCaveRoom1MaxElixer` (`itemball MAX_ELIXER`) | `EVENT_SILVER_CAVE_ROOM_1_MAX_ELIXER` | -| `SILVERCAVEROOM1_POKE_BALL2` | `SPRITE_POKE_BALL` | 15 | 29 | `SPRITEMOVEDATA_STILL`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_ITEMBALL` | `SilverCaveRoom1XAccuracy` (`itemball X_ACCURACY`) | `EVENT_SILVER_CAVE_ROOM_1_X_ACCURACY` | -| `SILVERCAVEROOM1_POKE_BALL3` | `SPRITE_POKE_BALL` | 5 | 30 | `SPRITEMOVEDATA_STILL`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_ITEMBALL` | `SilverCaveRoom1EscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_SILVER_CAVE_ROOM_1_ESCAPE_ROPE` | - -**Scripts of interest** - -`SilverCaveRoom1_MapScripts` has an empty `def_scene_scripts` and an empty -`def_callbacks`. Nothing scripted happens on this floor; the three labels above -are two-byte `itemball` payloads, not scripts (the `itemball`/`hiddenitem` -macros lay down data that the object/bg dispatcher reads, they are not `ScriptVM` -entry points). - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_SILVER_CAVE_ROOM_1_MAX_ELIXER` | `constants/event_flags.asm:1082` | object_event row (hide-when-set) | Set once the ball at (4,9) is taken. | -| `EVENT_SILVER_CAVE_ROOM_1_X_ACCURACY` | `constants/event_flags.asm:1083` | object_event row | Ball at (15,29). | -| `EVENT_SILVER_CAVE_ROOM_1_ESCAPE_ROPE` | `constants/event_flags.asm:1084` | object_event row | Ball at (5,30). | -| `EVENT_SILVER_CAVE_ROOM_1_HIDDEN_DIRE_HIT` | `constants/event_flags.asm:164` | `hiddenitem` | Hidden Dire Hit at (16,23). | -| `EVENT_SILVER_CAVE_ROOM_1_HIDDEN_ULTRA_BALL` | `constants/event_flags.asm:165` | `hiddenitem` | Hidden Ultra Ball at (17,12). | - -Polarity note that matters for every object row in this section: -`CheckObjectFlag` (`engine/overworld/map_objects_2.asm:31-58`) masks (hides) the -object when its `MAPOBJECT_EVENT_FLAG` is **set**, and `Script_disappear` -(`engine/overworld/scripting.asm:887`) is exactly "set that flag". So a set flag -means gone, a clear flag means present. - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_ELIXER` | item ball at (4,9) | `SilverCaveRoom1MaxElixer` | `EVENT_SILVER_CAVE_ROOM_1_MAX_ELIXER` | -| `X_ACCURACY` | item ball at (15,29) | `SilverCaveRoom1XAccuracy` | `EVENT_SILVER_CAVE_ROOM_1_X_ACCURACY` | -| `ESCAPE_ROPE` | item ball at (5,30) | `SilverCaveRoom1EscapeRope` | `EVENT_SILVER_CAVE_ROOM_1_ESCAPE_ROPE` | -| `DIRE_HIT` | hidden at (16,23) | bg_event | `EVENT_SILVER_CAVE_ROOM_1_HIDDEN_DIRE_HIT` | -| `ULTRA_BALL` | hidden at (17,12) | bg_event | `EVENT_SILVER_CAVE_ROOM_1_HIDDEN_ULTRA_BALL` | - -**Trainers**: none. - -**Wild encounters** - -`data/wild/johto_grass.asm:1297`, `def_grass_wildmons SILVER_CAVE_ROOM_1`, -`6 percent` morn/day/nite. All three time slices are identical here. - -| slot | all times (Gold) | Silver difference | -|---|---|---| -| 1 | 42 ONIX | - | -| 2 | 44 URSARING | 44 DONPHAN | -| 3 | 43 GRAVELER | - | -| 4 | 43 GRAVELER | - | -| 5 | 45 GOLBAT | - | -| 6 | 20 LARVITAR | - | -| 7 | 15 LARVITAR | - | - -No water table for Room 1. Fishing group `FISHGROUP_LAKE`. - -**Terrain notes** (derived by expanding `maps/SilverCaveRoom1.blk` through -`data/tilesets/cave_collision.asm`; `TilesetDarkCaveColl` and `TilesetCaveColl` -are the same label pair in `gfx/tilesets.asm:106-107`, so the dark-cave floor -uses the ordinary cave collision table) - -- Entry from outside lands on (9,33); a short corridor runs up to the long - east-west floor strip at y=32 spanning x=2..16. -- The Escape Rope at (5,30) sits **on a ledge cell**. Gen 2 ledges are not - "hop over the tile in front"; `.TryJump` - (`engine/overworld/player_movement.asm:354`) reads `wPlayerTileCollision`, the - tile the player is *standing on*, so you walk onto the ledge row, grab the - ball, then press the ledge's direction (`DOWN` for `COLL_HOP_DOWN`) to jump - two cells down onto y=32. That is exactly the walkthrough's "drop down the - ledge to get the Escape Rope. Jump back down." -- (11,27) and (13,27) carry `WARP_CARPET_DOWN` collision but there is **no** - `warp_event` at either coordinate. Treat them as decoration, not exits. - -### MAP_SILVER_CAVE_ROOM_2 - -- Script: `maps/SilverCaveRoom2.asm` (symbols `47:4238`, `47:423d`) -- Blocks: `maps/SilverCaveRoom2.blk` (15 x 18 blocks = 30 x 36 cells) -- Header: `data/maps/maps.asm:145` -> - `map SilverCaveRoom2, TILESET_CAVE, CAVE, LANDMARK_SILVER_CAVE, MUSIC_LIGHTHOUSE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:132` (`map_const SILVER_CAVE_ROOM_2, 15, 18`), group `DUNGEONS` = 3, map id 67 -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 17 | 31 | `SILVER_CAVE_ROOM_1` | 2 | -| 2 | 11 | 5 | `SILVER_CAVE_ROOM_3` | 1 | -| 3 | 13 | 21 | `SILVER_CAVE_ITEM_ROOMS` | 1 | -| 4 | 23 | 3 | `SILVER_CAVE_ITEM_ROOMS` | 2 | - -**Coord events** (`def_coord_events`) - -None. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 14 | 31 | `BGEVENT_ITEM` | `SilverCaveRoom2HiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_SILVER_CAVE_ROOM_2_HIDDEN_MAX_POTION` | - -**Object events**: none (`def_object_events` is the last line, no rows). - -**Scripts of interest** - -Empty `def_scene_scripts`, empty `def_callbacks`. Room 2 is pure terrain. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_SILVER_CAVE_ROOM_2_HIDDEN_MAX_POTION` | `constants/event_flags.asm:166` | `hiddenitem` at (14,31) | One-shot; the tile is right next to the Room 1 entrance warp at (17,31). | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_POTION` | hidden at (14,31) | bg_event | `EVENT_SILVER_CAVE_ROOM_2_HIDDEN_MAX_POTION` | - -**Trainers**: none. - -**Wild encounters** - -Grass: `data/wild/johto_grass.asm:1352`, `def_grass_wildmons SILVER_CAVE_ROOM_2`, -`6 percent` morn/day/nite. - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 45 QUAGSIRE | 45 QUAGSIRE | 45 QUAGSIRE | -| 2 | 48 GOLDUCK | 48 GOLDUCK | 48 GOLDUCK | -| 3 | 47 URSARING (Silver: DONPHAN) | 47 URSARING (Silver: DONPHAN) | 47 URSARING (Silver: DONPHAN) | -| 4 | 45 QUAGSIRE | 45 QUAGSIRE | **45 MISDREAVUS** | -| 5 | 48 GOLBAT | 48 GOLBAT | 48 GOLBAT | -| 6 | 20 LARVITAR | 20 LARVITAR | 20 LARVITAR | -| 7 | 15 LARVITAR | 15 LARVITAR | 15 LARVITAR | - -Water: `data/wild/johto_water.asm:100`, `def_water_wildmons SILVER_CAVE_ROOM_2`, -`2 percent`: 35 SEAKING / 40 SEAKING / 35 GOLDEEN. Fishing group `FISHGROUP_LAKE`. - -**Terrain notes** (expanded from `maps/SilverCaveRoom2.blk` + -`data/tilesets/cave_collision.asm`; both item-room warps are behind water) - -- You arrive at (17,31) and stand on the south floor shelf, y=30..31, x=14..24. -- **South lake / Max Revive branch.** Surf west from (14,30) onto the water at - x=4..13, y=30..31. The waterfall band is x=6..11, y=28..29; Waterfall up from - it reaches the water pocket at y=22..27. Come ashore at (11,25), walk north to - the floor pocket x=9..14, y=22..24, and step onto the warp at **(13,21)** -> - `SILVER_CAVE_ITEM_ROOMS` warp 1 -> `MAX_REVIVE`. -- **North lake / Full Restore branch.** From the mid-map floor at (11,11) step - down onto the water at (11,12); the lake runs x=10..13 y=12..15 and x=12..21 - y=14..15. The second waterfall band is x=18..21, y=10..13; Waterfall up lands - in the pool at x=18..21, y=8..9. Come ashore at (21,7), walk up through the - x=19..24 chamber to (23,4), and step onto the warp at **(23,3)** -> - `SILVER_CAVE_ITEM_ROOMS` warp 2 -> `FULL_RESTORE`. This is the walkthrough's - "use Surf on the lake, and then use Waterfall ... an entrance to grab a Full - Restore." -- The Room 3 entrance is the `CAVE` cell at (11,5), reached on foot from the - same north-central floor. -- The waterfall band at x=4..7, y=0..5 in the north-west corner has water below - it (y=6..7) that is walled off on every side. It looks decorative; nothing in - the asm routes through it. - -### MAP_SILVER_CAVE_ITEM_ROOMS - -- Script: `maps/SilverCaveItemRooms.asm` (symbols `47:42b7`, `47:42bd`) -- Blocks: `maps/SilverCaveItemRooms.blk` (10 x 9 blocks = 20 x 18 cells) -- Header: `data/maps/maps.asm:147` -> - `map SilverCaveItemRooms, TILESET_CAVE, CAVE, LANDMARK_SILVER_CAVE, MUSIC_LIGHTHOUSE, TRUE, PALETTE_NITE, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:134` (`map_const SILVER_CAVE_ITEM_ROOMS, 10, 9`), group `DUNGEONS` = 3, map id 69 -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 13 | 3 | `SILVER_CAVE_ROOM_2` | 3 | -| 2 | 7 | 15 | `SILVER_CAVE_ROOM_2` | 4 | - -**Coord events** / **BG events**: both empty. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILVERCAVEITEMROOMS_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 3 | `SPRITEMOVEDATA_STILL`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_ITEMBALL` | `SilverCaveItemRoomsMaxRevive` (`itemball MAX_REVIVE`) | `EVENT_SILVER_CAVE_ITEM_ROOMS_MAX_REVIVE` | -| `SILVERCAVEITEMROOMS_POKE_BALL2` | `SPRITE_POKE_BALL` | 15 | 11 | `SPRITEMOVEDATA_STILL`, radius 0/0, hours -1/-1, pal 0 | `OBJECTTYPE_ITEMBALL` | `SilverCaveItemRoomsFullRestore` (`itemball FULL_RESTORE`) | `EVENT_SILVER_CAVE_ITEM_ROOMS_FULL_RESTORE` | - -**Scripts of interest** - -Empty scene scripts and callbacks; the two labels are `itemball` payloads. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_SILVER_CAVE_ITEM_ROOMS_MAX_REVIVE` | `constants/event_flags.asm:1085` | object_event row | Ball at (6,3), north room. | -| `EVENT_SILVER_CAVE_ITEM_ROOMS_FULL_RESTORE` | `constants/event_flags.asm:1086` | object_event row | Ball at (15,11), south room. | - -**Items** - -| item | how obtained | source | one-time flag | -|---|---|---|---| -| `MAX_REVIVE` | item ball at (6,3) | `SilverCaveItemRoomsMaxRevive` | `EVENT_SILVER_CAVE_ITEM_ROOMS_MAX_REVIVE` | -| `FULL_RESTORE` | item ball at (15,11) | `SilverCaveItemRoomsFullRestore` | `EVENT_SILVER_CAVE_ITEM_ROOMS_FULL_RESTORE` | - -**Trainers**: none. - -**Wild encounters** - -`data/wild/johto_grass.asm:1462`, `def_grass_wildmons SILVER_CAVE_ITEM_ROOMS`, -`6 percent`. Same shape as Room 2 but the night swap lands in slot 1 instead of -slot 4: morn/day slot 1 is 45 QUAGSIRE, nite slot 1 is **45 MISDREAVUS**; slot 4 -stays 45 QUAGSIRE at all times. Slots 2/3/5/6/7 are 48 GOLDUCK / 47 URSARING -(Silver: DONPHAN) / 48 GOLBAT / 20 LARVITAR / 15 LARVITAR. No water table. - -**Terrain notes** - -The map is two disconnected chambers in one 20 x 18 grid, which is why both of -its warps go back to Room 2: - -- North chamber, floor y=2..3 spanning x=4..15, warp 1 at (13,3), Max Revive at (6,3). -- South chamber, floor y=10..15 spanning roughly x=4..17, warp 2 at (7,15), Full - Restore at (15,11). - -### MAP_SILVER_CAVE_ROOM_3 - -- Script: `maps/SilverCaveRoom3.asm` (symbols `47:425c SilverCaveRoom3_MapScripts`, `47:425e Red`, `47:429f SilverCaveRoom3_MapEvents`) -- Blocks: `maps/SilverCaveRoom3.blk` (10 x 18 blocks = 20 x 36 cells) -- Header: `data/maps/maps.asm:146` -> - `map SilverCaveRoom3, TILESET_CAVE, CAVE, LANDMARK_SILVER_CAVE, MUSIC_LIGHTHOUSE, TRUE, PALETTE_DAY, FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:133` (`map_const SILVER_CAVE_ROOM_3, 10, 18`), group `DUNGEONS` = 3, map id 68 -- Connections: none - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 33 | `SILVER_CAVE_ROOM_2` | 2 | - -**Coord events** (`def_coord_events`) - -None. Red is *not* a trip-wire; you have to walk into him. - -**BG events** (`def_bg_events`) - -None. - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `SILVERCAVEROOM3_RED` | `SPRITE_RED` | 9 | 10 | `SPRITEMOVEDATA_STANDING_UP`, radius 0/0, hours -1/-1, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` (sight range 0) | `Red` | `EVENT_RED_IN_MT_SILVER` | - -Note the type: Red is `OBJECTTYPE_SCRIPT`, **not** `OBJECTTYPE_TRAINER`. He has -no sight cone, no `loadtrainer` in his object row, and no "trainer spotted" -exclamation. You must stand next to him and press A. - -**Scripts of interest** - -`Red` (`maps/SilverCaveRoom3.asm:9`, symbol `47:425e`), in order: - -1. `special FadeOutMusic` -2. `faceplayer` / `opentext` / `writetext RedSeenText` ("…" / "…") / `waitbutton` / `closetext` -3. `winlosstext RedWinLossText, RedWinLossText` - the same "…" text for both outcomes -4. `loadtrainer RED, RED1` -5. `startbattle` -6. `dontrestartmapmusic` -7. `reloadmapafterbattle` -8. `special FadeOutMusic` -9. `opentext` / `writetext RedLeavesText` / `waitbutton` / `closetext` -10. `special FadeOutToBlack` / `special ReloadSpritesNoPalettes` -11. `disappear SILVERCAVEROOM3_RED` - sets `EVENT_RED_IN_MT_SILVER` -12. `pause 15` / `special FadeInFromBlack` / `pause 30` -13. `special HealParty` -14. `reanchormap` -15. `credits` -16. `end` - -There is no `checkevent`/`iftrue` guard anywhere in the script: the object flag -is the only gate, and the script is unconditional once you talk to him. There is -also no `applymovement` - "He flies away" in the walkthrough is prose; the asm -just fades to black and `disappear`s him. - -`credits` is opcode `$a0` (`engine/overworld/scripting.asm:227`). -`Script_credits` (`engine/overworld/scripting.asm:2215`) farcalls `RedCredits` -(`engine/events/halloffame.asm:34`), which fades to white, sets -`wSpawnAfterChampion = SPAWN_RED`, and jumps into `Credits`; on return the -script is force-ended and `MAPSTATUS_DONE` is loaded. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_RED_IN_MT_SILVER` | `constants/event_flags.asm:1284` | **set** by `InitializeEventsScript` (`engine/events/std_scripts.asm:526`) at new game; **cleared** by `maps/HallOfFame.asm:36`; **set** again by `disappear SILVERCAVEROOM3_RED` inside `Red` | Set = Red hidden (see the `CheckObjectFlag` polarity note above). So: absent on a fresh save, present after your first Hall of Fame induction, gone once beaten, and present again after every subsequent Hall of Fame. That is the asm behind "If you want to fight Red again, beat the Elite Four again and then he'll be back at Mt. Silver." | - -No `SCENE_*` values: `def_scene_scripts` is empty for this map (`wSilverCaveRoom3SceneID` exists in WRAM at `01:d6ea` but nothing writes a non-zero scene to it). - -**Items**: none. - -**Trainers** - -| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone | -|---|---|---|---|---|---| -| `RED1` | `RED` (`trainerclass RED ; 3f` = 63, `constants/trainer_constants.asm:637`) | 1 | `RedGroup` (`data/trainers/parties.asm:3143`, symbol `0e:75d5`) | `Red` | No phone number, no rematch table; he re-appears only via `EVENT_RED_IN_MT_SILVER` being cleared at the Hall of Fame | - -`RedGroup`, `db "RED@", TRAINERTYPE_MOVES` - four explicit moves per mon, in -send-out order: - -| # | level | species | moves | -|---|---|---|---| -| 1 | 81 | PIKACHU | CHARM, QUICK_ATTACK, THUNDERBOLT, THUNDER | -| 2 | 73 | ESPEON | MUD_SLAP, REFLECT, SWIFT, PSYCHIC_M | -| 3 | 75 | SNORLAX | AMNESIA, SNORE, REST, BODY_SLAM | -| 4 | 77 | VENUSAUR | SUNNY_DAY, GIGA_DRAIN, SYNTHESIS, SOLARBEAM | -| 5 | 77 | CHARIZARD | FLAMETHROWER, WING_ATTACK, SLASH, FIRE_SPIN | -| 6 | 77 | BLASTOISE | RAIN_DANCE, SURF, BLIZZARD, WHIRLPOOL | - -Class attributes (`data/trainers/attributes.asm:377-381`, the `; Red` block): - -- items: `FULL_RESTORE, FULL_RESTORE` - **two** slots, not three -- base reward: `25` -- AI: `AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY` -- item/switch: `CONTEXT_USE | SWITCH_SOMETIMES` - -Prize money: `ComputeTrainerReward` -(`engine/battle/read_trainer_party.asm:300`) computes -`wBattleReward = base_reward * wCurPartyLevel`, and `wCurPartyLevel` at that -point is the level of the **last** party row, i.e. Blastoise's 77. So -`25 * 77 = 1925`. `WinTrainerBattle` (`engine/battle/core.asm:2292`) then pays it -out with `ld c, 4` - four additions - giving **7700**, which matches the -walkthrough's "You get: 7700G" exactly. (Amulet Coin doubles it via -`.DoubleReward` before the split; Mom's savings take a share of the four -quarters.) - -**Wild encounters** - -`data/wild/johto_grass.asm:1407`, `def_grass_wildmons SILVER_CAVE_ROOM_3`, -`6 percent`, identical across morn/day/nite: - -| slot | species | -|---|---| -| 1 | 51 GOLBAT | -| 2 | 48 ONIX | -| 3 | 48 GOLBAT | -| 4 | 50 URSARING (Silver: DONPHAN) | -| 5 | 51 GOLDUCK | -| 6 | 20 LARVITAR | -| 7 | 15 LARVITAR | - -No water table for Room 3. - -**Terrain notes** - -A single long north-south corridor. From the entrance warp at (9,33) the path -runs straight up the x=9..10 column to the wide chamber at y=9..16, then to the -top room; Red stands at (9,10), squarely in the corridor mouth, so you cannot -pass him without triggering the fight. (11,33) also carries `WARP_CARPET_DOWN` -collision but has no `warp_event`. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| Reaching Route 28 at all (the north passage of the gate) | `maps/VictoryRoadGate.asm:19` `VictoryRoadGateBadgeCheckScript` / `VictoryRoadGateOfficerScript` (symbols `5f:4fdc` / `5f:4fe2`), fired by `coord_event 10, 11, SCENE_VICTORYROADGATE_BADGE_CHECK, VictoryRoadGateBadgeCheckScript` | `readvar VAR_BADGES` / `ifgreater NUM_JOHTO_BADGES - 1` - strictly more than 7 Johto badges | On pass: `setscene SCENE_VICTORYROADGATE_NOOP`, so the check never fires again. On fail: `applymovement PLAYER, VictoryRoadGateStepDownMovement` shoves you back one step. Note the west warps to Route 28 at (1,7)/(2,7) are *below* the trip-wire row; the badge scene guards the north corridor. | -| Mt. Silver "opened" flavour | `maps/OaksLab.asm:18-45` `Oak` -> `.CheckBadges` / `.OpenMtSilver` | `readvar VAR_BADGES` / `ifequal NUM_BADGES` (all 16) when you talk to Prof. Oak | `setevent EVENT_OPENED_MT_SILVER` (`constants/event_flags.asm:1265`). The only other reader is `maps/VictoryRoadGate.asm:118`, where it is the hide-flag of the left Black Belt at (7,5). **No script in any Mt. Silver map reads it**, and it does not appear in a `checkevent` on any warp. | -| Room 1 darkness | `engine/tilesets/timeofday_pals.asm:113` `ReplaceTimeOfDayPals` -> `.NeedsFlash`, driven by `PALETTE_DARK` in `data/maps/maps.asm:144` | `STATUSFLAGS_FLASH_F` in `wStatusFlags`, set by `BlindingFlash` (`engine/events/field_moves.asm:12`) | Use HM05 Flash. `FlashFunction` (`engine/events/overworld.asm:271`) needs `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET`. Rooms 2/3 and the Item Rooms are `PALETTE_NITE`/`PALETTE_DAY`, so Flash is only needed for Room 1. It is a visibility gate, not a movement gate. | -| Room 2 lakes | `SurfFunction.TrySurf` (`engine/events/overworld.asm:322-345`) | `ENGINE_FOGBADGE` + a party mon with SURF + facing a `WATER_TILE` | Fog Badge (Morty). Blocks both Item Rooms entrances and everything past y<30 on the west lake. | -| Room 2 waterfalls | `WaterfallFunction.TryWaterfall` (`engine/events/overworld.asm:611`) and `TryWaterfallOW` (`engine/events/overworld.asm:687`), gated by `CheckMapCanWaterfall` | `ENGINE_RISINGBADGE`, party mon with WATERFALL, player facing UP, and `wTileUp` a waterfall tile | Rising Badge (Clair). Without it neither item room is reachable: the north branch (Full Restore at (23,3)) and the south branch (Max Revive at (13,21)) both sit above a waterfall band. | -| Red himself | `maps/SilverCaveRoom3.asm:62` object row's event flag | `EVENT_RED_IN_MT_SILVER` must be **clear** | Cleared by `maps/HallOfFame.asm:36`, i.e. by completing the Elite Four / Lance and being inducted. Before that the object is masked by `CheckObjectFlag` and Room 3 is an empty corridor. | - -Not a gate, worth knowing: nothing in this section checks Strength, Whirlpool, -Cut or Rock Smash. The walkthrough's "bring all the HMs" is advice, not a -requirement - Flash, Surf and Waterfall are the only three the asm cares about, -and Flash only for comfort. - ---- - -## 4. Bot checklist - -Preconditions for the whole section: `EVENT_BEAT_ELITE_FOUR` set (Hall of Fame -done, which is what clears `EVENT_RED_IN_MT_SILVER`), 8 Johto badges for the -Victory Road Gate scene, `ENGINE_FOGBADGE` + `ENGINE_RISINGBADGE` for the item -rooms, `ENGINE_ZEPHYRBADGE` + Flash for Room 1 visibility. - -1. `MAP_VICTORY_ROAD_GATE` - walk onto (10,11) if the scene is still - `SCENE_VICTORYROADGATE_BADGE_CHECK`; talk through - `VictoryRoadGateOfficerScript`. Pre: `VAR_BADGES > 7`. Post: scene becomes - `SCENE_VICTORYROADGATE_NOOP`. Then take warp 7 or 8 at (1,7)/(2,7) to - `MAP_ROUTE_28`. -2. `MAP_ROUTE_28` - cross west onto the map connection. Post: now on - `MAP_SILVER_CAVE_OUTSIDE`. (Route 28 body belongs to the previous section.) -3. `MAP_SILVER_CAVE_OUTSIDE` - entering fires - `SilverCaveOutsideFlypointCallback`. Post: `ENGINE_FLYPOINT_SILVER_CAVE` set; - Fly to `LANDMARK_SILVER_CAVE` now works, and the respawn point is (23,20). -4. `MAP_SILVER_CAVE_OUTSIDE` - optional: face (9,25) and press A (or run the - ITEMFINDER) for the hidden `FULL_RESTORE`. Pre: - `EVENT_SILVER_CAVE_OUTSIDE_HIDDEN_FULL_RESTORE` clear. Post: it is set. -5. `MAP_SILVER_CAVE_OUTSIDE` - optional heal: warp 1 at (23,19) -> - `MAP_SILVER_CAVE_POKECENTER_1F`, talk to `SILVERCAVEPOKECENTER1F_NURSE` at - (3,1), leave by warp 1 at (3,7). -6. `MAP_SILVER_CAVE_OUTSIDE` - step on warp 2 at (18,11). Post: on - `MAP_SILVER_CAVE_ROOM_1` at (9,33). -7. `MAP_SILVER_CAVE_ROOM_1` - use Flash from the party menu (`FlashFunction`). - Pre: `ENGINE_ZEPHYRBADGE`, HM05 on a party mon, `wTimeOfDayPalset == - DARKNESS_PALSET`. Post: `STATUSFLAGS_FLASH_F` set. -8. `MAP_SILVER_CAVE_ROOM_1` - walk to `SILVERCAVEROOM1_POKE_BALL3` at (5,30) and - press A (approach along the ledge row from above, x=2..6 at y=30). Post: - `ESCAPE_ROPE` in the bag, `EVENT_SILVER_CAVE_ROOM_1_ESCAPE_ROPE` set. Then - press DOWN while standing on the ledge to hop to y=32. -9. `MAP_SILVER_CAVE_ROOM_1` - `SILVERCAVEROOM1_POKE_BALL2` at (15,29) -> - `X_ACCURACY`, flag `EVENT_SILVER_CAVE_ROOM_1_X_ACCURACY`. -10. `MAP_SILVER_CAVE_ROOM_1` - `SILVERCAVEROOM1_POKE_BALL1` at (4,9) -> - `MAX_ELIXER`, flag `EVENT_SILVER_CAVE_ROOM_1_MAX_ELIXER`. -11. `MAP_SILVER_CAVE_ROOM_1` - optional hidden items: (17,12) `ULTRA_BALL`, - (16,23) `DIRE_HIT`. -12. `MAP_SILVER_CAVE_ROOM_1` - step on warp 2 at (15,1). Post: on - `MAP_SILVER_CAVE_ROOM_2` at (17,31). -13. `MAP_SILVER_CAVE_ROOM_2` - optional hidden `MAX_POTION` at (14,31), two - cells west of where you land. -14. `MAP_SILVER_CAVE_ROOM_2` (Full Restore branch) - walk to (11,11), face down, - Surf onto (11,12). Follow the lake east to x=18..21 at y=14..15, face UP on - y=10 and use Waterfall. Come ashore at (21,7), walk to (23,4), step up onto - the warp at (23,3). Post: on `MAP_SILVER_CAVE_ITEM_ROOMS` at (7,15). -15. `MAP_SILVER_CAVE_ITEM_ROOMS` - `SILVERCAVEITEMROOMS_POKE_BALL2` at (15,11) - -> `FULL_RESTORE`, flag `EVENT_SILVER_CAVE_ITEM_ROOMS_FULL_RESTORE`. Leave - by warp 2 at (7,15). -16. `MAP_SILVER_CAVE_ROOM_2` (Max Revive branch, optional, not in the - walkthrough) - from the landing shelf walk to (14,30), Surf west, Waterfall - up the x=6..11 band at y=28..29, come ashore at (11,25), walk to (13,22) and - step up onto the warp at (13,21). Take - `SILVERCAVEITEMROOMS_POKE_BALL1` at (6,3) -> `MAX_REVIVE`. Leave by warp 1 - at (13,3). -17. `MAP_SILVER_CAVE_ROOM_2` - step on warp 2 at (11,5). Post: on - `MAP_SILVER_CAVE_ROOM_3` at (9,33). -18. `MAP_SILVER_CAVE_ROOM_3` - walk north up the x=9..10 corridor to (9,11), - face UP, press A on `SILVERCAVEROOM3_RED` at (9,10). Pre: - `EVENT_RED_IN_MT_SILVER` clear. This runs `Red`. -19. Battle `RED` / `RED1`. Six mons, levels 81/73/75/77/77/77, two Full Restores - on the AI. Post on victory: 7700 money (before Amulet Coin / Mom split), - `EVENT_RED_IN_MT_SILVER` set by `disappear`, party healed by - `special HealParty`, then `credits`. -20. After the credits the game re-enters at `SPAWN_MT_SILVER` = - `SILVER_CAVE_OUTSIDE` (23,20) with `hMapEntryMethod = MAPSETUP_WARP`. Save - here (the walkthrough's "After the credits roll, save"). - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| Map headers / warps / bg events / object events for all six maps | `src/import/RomExtractorGen2.lua` (warp + coord + bg + object decode around lines 785-861 and 2959-2990) | implemented - the maps are decoded generically from the ROM, so no Silver Cave specific data is hand-written anywhere | -| `itemball` object rows (Escape Rope, X Accuracy, Max Elixer, Max Revive, Full Restore) | `src/import/RomExtractorGen2.lua:2874`, `:2968` (`readItemBall`) | implemented | -| `hiddenitem` bg rows (Full Restore, Dire Hit, Ultra Ball, Max Potion) | `src/world/gen2/HiddenItems.lua` | implemented | -| `MAPCALLBACK_NEWMAP` flypoint callback | `src/world/gen2/Map.lua` + driver `tests/drivers/gold_map_callbacks.lua` | implemented | -| `ENGINE_FLYPOINT_SILVER_CAVE` -> Fly destination | `src/world/gen2/FieldMoves.lua:355` (`LANDMARK_SILVER_CAVE` / `SPAWN_MT_SILVER` / flag 75), `src/ui/gen2/Pokegear.lua:1478` | implemented | -| Flash (dark-cave palette gate) | `src/world/gen2/FieldMoves.lua:465-475` (`flashFromMenu`), badge table at `:105` | implemented | -| Surf | `src/world/gen2/FieldMoves.lua:477-500`, `src/world/gen2/Permissions.lua` | implemented | -| Waterfall (`CheckMapCanWaterfall`, `.CheckContinueWaterfall`) | `src/world/gen2/FieldMoves.lua:253-266`, `:528-540` | implemented | -| Gen 2 ledge hop (`.TryJump` / `HI_NYBBLE_LEDGES`) - needed for the Escape Rope drop in Room 1 | no gen2 implementation found; `src/world/gen2/Player.lua` is a plain grid stepper and `src/world/gen2/Permissions.lua` only models LAND/WATER/WALL. `src/world/OverworldController.lua:1330` `checkLedgeHop` is the **Gen 1** model (`data/tilesets/ledge_tiles.asm`, standing-tile + front-tile pair) and does not match Gen 2's standing-tile-nybble rule | missing | -| `loadtrainer` / `startbattle` / `winlosstext` | `src/script/gen2/Vm.lua:806`, `:817`; opcodes in `src/script/gen2/Opcodes.lua:99-105` | implemented | -| `disappear` | `src/script/gen2/Opcodes.lua:115`, handled in `src/script/gen2/Vm.lua` | implemented | -| `special FadeOutMusic` / `FadeOutToBlack` / `FadeInFromBlack` / `ReloadSpritesNoPalettes` / `HealParty` | `src/script/gen2/Specials.lua:450`, `:999`, `:1001`, `:1025`, `:1066` | implemented | -| `credits` opcode as a terminal script op | `src/script/gen2/Opcodes.lua:172`, `:194` | implemented | -| Red-credits path and post-credits respawn at Mt. Silver | `src/core/gen2/HallOfFame.lua:48-53`, `:239-259` (`markRedCredits`, `POST_CREDITS_SPAWN.SPAWN_RED = "SPAWN_MT_SILVER"`), test `tests/gen2_halloffame_test.lua:171` | implemented | -| Credits reel itself | `src/ui/Credits.lua`, driver `tests/drivers/gold_halloffame_shots.lua` | implemented (shared with the Hall of Fame credits) | -| Prize money (base reward x last level, x4, Mom split) | `src/battle/gen2/Prize.lua` | implemented - the `ld c, 4` factor is modelled explicitly, which is what makes Red's 7700 come out right | -| Trainer AI item use (Red's two Full Restores) | `src/battle/gen2/Ai.lua:1294`, `:1369-1375` | implemented | -| Wild tables incl. the morn/day/nite split | `src/import/RomExtractorGen2.lua:3720-3845` (`JohtoGrassWildMons` / `JohtoWaterWildMons` / `FishGroups`) | implemented | -| Trainer parties incl. `RedGroup` | `src/import/RomExtractorGen2.lua:3859-4001` (`TrainerGroups`) | implemented | -| A driver that actually reaches Red | none - the closest are `tests/drivers/gold_trainer_smoke.lua` and `tests/drivers/gold_halloffame_shots.lua` | missing | - ---- - -## 6. Unresolved / verify by hand - -- **"Items in Mount Silver: Escape Rope, Full Restore, Protein."** There is no - `PROTEIN` anywhere in `maps/SilverCaveOutside.asm`, `SilverCaveRoom1.asm`, - `SilverCaveRoom2.asm`, `SilverCaveRoom3.asm` or `SilverCaveItemRooms.asm`. The - full real list is Max Elixer, X Accuracy, Escape Rope, Max Revive, Full Restore - (balls) plus hidden Full Restore, Dire Hit, Ultra Ball, Max Potion. The - walkthrough's summary list is also missing the X Accuracy and Max Elixir it - later tells you to pick up. -- **"Pokémon in Mount Silver: ... #232 Donphan"** - Donphan is the *Silver* - column of every one of these `def_grass_wildmons` blocks. In Gold the same - slots are Ursaring. Same file, `IF DEF(_GOLD)` / `ELIF DEF(_SILVER)`. -- **"Red will have three Full Restores"** - `data/trainers/attributes.asm:378` - gives the RED class exactly two item slots, both `FULL_RESTORE`. Could not find - a third anywhere. -- **Red's party order.** The walkthrough lists Pikachu, Espeon, Blastoise, - Snorlax, Charizard, Venusaur. `RedGroup` orders them Pikachu, Espeon, Snorlax, - Venusaur, Charizard, Blastoise. Only the lead matches. The listed EXP yields - (1422 / 3081 / 3465 / 2475 / 3448 / 3432) are not stored anywhere as data - - they are computed from base experience and level at runtime - so they could not - be confirmed against a table. -- **"Espeon is a dark type that cannot be hit with ghost moves."** Espeon is - Psychic in `data/pokemon/base_stats/espeon.asm`; the ghost-immunity claim - describes Umbreon/Dark. Strategy prose, not asm, but a bot following it would - make bad move choices. -- **"He flies away."** The `Red` script has no `applymovement` and no fly - animation - `special FadeOutToBlack`, `disappear`, `special FadeInFromBlack`. - Cosmetic discrepancy only. -- **Which of the two Item Rooms the walkthrough means.** The text mentions only - the Full Restore, which I traced to `SilverCaveRoom2` warp 4 at (23,3) -> - `SilverCaveItemRooms` warp 2 at (7,15). That mapping is derived from expanding - `maps/SilverCaveRoom2.blk` and `maps/SilverCaveItemRooms.blk` through - `data/tilesets/cave_collision.asm`, not from an explicit statement in any asm - file. The Surf/Waterfall requirement on both branches comes from the same - derivation. Worth confirming in-game before a bot commits to the route. -- **`EVENT_OPENED_MT_SILVER` does nothing on these maps.** The walkthrough (and - common knowledge) says you need all 16 badges and a word with Prof. Oak to - enter Mt. Silver. `maps/OaksLab.asm` does set the flag on 16 badges, but the - only consumer in the whole tree is the hide-flag of a Black Belt in - `maps/VictoryRoadGate.asm:118`. I could not find a `checkevent` on that flag - guarding any warp, coord event or NPC on the Route 28 / Silver Cave path. If - the 16-badge requirement is real it is enforced somewhere I did not locate - - verify by hand before assuming a bot with 8 Johto badges can walk in. -- **Two `WARP_CARPET_DOWN` cells with no `warp_event`**: (11,27) and (13,27) in - Room 1, and (11,33) in Room 3. Collision says "warp", the event table says - nothing. Assumed decorative; not tested. diff --git a/docs/gold-walkthrough/asm-walk/section-32-raikou-entei-and-suicune.md b/docs/gold-walkthrough/asm-walk/section-32-raikou-entei-and-suicune.md deleted file mode 100644 index b25771dc..00000000 --- a/docs/gold-walkthrough/asm-walk/section-32-raikou-entei-and-suicune.md +++ /dev/null @@ -1,823 +0,0 @@ -# Section 32 - Raikou, Entei, and Suicune - -Source: `../section-32-raikou-entei-and-suicune.txt` -Maps covered: `MAP_ROUTE_29`, `MAP_ROUTE_30`, `MAP_ROUTE_31`, `MAP_ROUTE_32`, -`MAP_ROUTE_33`, `MAP_ROUTE_34`, `MAP_ROUTE_35`, `MAP_ROUTE_36`, `MAP_ROUTE_37`, -`MAP_ROUTE_38`, `MAP_ROUTE_39`, `MAP_ROUTE_42`, `MAP_ROUTE_43`, `MAP_ROUTE_44`, -`MAP_ROUTE_45`, `MAP_ROUTE_46` (the sixteen `RoamMaps` entries), plus -`MAP_VIOLET_CITY` as the Fly anchor the walkthrough names. -Badges / key milestones in this section: none. The milestone is three catches: -`RAIKOU`, `ENTEI`, `SUICUNE`. Every one of them clears its own roam slot -permanently. - -This section is a *mechanic*, not a walk. There is no scripted trigger anywhere -on these sixteen maps that has anything to do with the beasts; the entire -feature lives in `engine/overworld/wildmons.asm`, -`engine/battle/core.asm` and `data/wild/roammon_maps.asm`. Section 2 therefore -opens with a "roam engine" block before the per-map blocks, because that block -is the part a bot actually has to implement. - ---- - -## 1. Route order - -The walkthrough gives a policy, not an itinerary: cross route boundaries as fast -as possible, because every crossing is one `UpdateRoamMons` call. The named -concrete loop is the Violet City / Route 36 hub. - -| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | -|---|---|---|---|---|---| -| 0 | `MAP_BURNED_TOWER_B1F` | `maps/BurnedTowerB1F.asm` | (earlier section) | (earlier section) | `special InitRoamMons` at `ReleaseTheBeasts` is the only thing that ever creates the three roamers. Out of scope here; owned by the Burned Tower section. | -| 1 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | Fly (`MAPSETUP_TELEPORT`) | west connection | "fly to Violet City, then stay on a bike and keep switching between Routes 35, 36, and 37". Note the Fly itself runs `JumpRoamMons`, i.e. it *scatters* the beasts before the loop starts. | -| 2 | `MAP_ROUTE_36` | `maps/Route36.asm` | west connection from Violet City | south / north connections | Route 36 is the four-way roam junction (`ROUTE_35`, `ROUTE_31`, `ROUTE_32`, `ROUTE_37`) and the only map with a direct edge to Violet City. | -| 3 | `MAP_ROUTE_35` | `maps/Route35.asm` | south connection from Route 36 | north connection back | Half of the "left right left right" oscillation. Each crossing = one `UpdateRoamMons`. | -| 4 | `MAP_ROUTE_37` | `maps/Route37.asm` | north connection from Route 36 | south connection back | Other half of the oscillation, and Entei's starting route. | -| 5 | `MAP_ROUTE_42` | `maps/Route42.asm` | west connection from Ecruteak City | east connection to Mahogany Town | "go to Route 42 with the Max Repel". Raikou's starting route, the other four-way roam junction, and it carries a free `ULTRA_BALL` item ball. | -| - | `MAP_SILVER_CAVE_OUTSIDE` / `MAP_SILVER_CAVE_ROOM_*` | `maps/SilverCaveOutside.asm` etc. | - | - | "sharing EXP at Mount Silver" to raise the False Swipe Scyther. Mount Silver belongs to a later section; only the level ceiling (see section 3) matters here. | - -Spill-over note: the walkthrough's Master Ball aside points at the Elm / -Radio Tower reward, and its Scyther training points at Mount Silver. Both are -other sections' maps; nothing about them is transcribed here. - ---- - -## 2. Maps - -### The roam engine (not a map) - -This is the part of the section that is code rather than map data. Everything -below was read out of the files named. - -**State: `roam_struct`** (`macros/ram.asm`, 7 bytes each, three of them) - -| field | symbol (Raikou slot) | address | meaning | -|---|---|---|---| -| Species | `wRoamMon1Species` | `01:dd1a` | `0` once caught or defeated, which is what retires the slot forever | -| Level | `wRoamMon1Level` | `01:dd1b` | always 40, never changes (no EXP in the struct) | -| MapGroup | `wRoamMon1MapGroup` | `01:dd1c` | `GROUP_N_A` when retired | -| MapNumber | `wRoamMon1MapNumber` | `01:dd1d` | `MAP_N_A` when retired | -| HP | `wRoamMon1HP` | `01:dd1e` | **one byte**; `0` means "stats not rolled yet" | -| DVs | `wRoamMon1DVs` | `01:dd1f` | rolled once, on the first encounter, then kept | - -Entei is slot 2 (`wRoamMon2Species` `01:dd21`), Suicune slot 3 -(`wRoamMon3Species` `01:dd28`). Slot order is load bearing: -`CheckEncounterRoamMon` indexes the structs by a random 0..2 and -`GetRoamMonHP` walks them by species. - -Player-position backup: `wRoamMons_CurMapGroup` `01:dd30`, -`wRoamMons_LastMapGroup` `01:dd32` (`_BackUpMapIndices`, `0a:6942`). - -**Creation: `InitRoamMons`** (`engine/overworld/wildmons.asm:488`, `0a:67d7`) - -Called exactly once, from `maps/BurnedTowerB1F.asm:65`, at the tail of -`ReleaseTheBeasts` right after `setscene SCENE_BURNEDTOWERB1F_NOOP` and -`setevent EVENT_RELEASED_THE_BEASTS`. It writes: - -| slot | species | level | starting map | -|---|---|---|---| -| 1 | `RAIKOU` | 40 | `MAP_ROUTE_42` (`GROUP_ROUTE_42`) | -| 2 | `ENTEI` | 40 | `MAP_ROUTE_37` (`GROUP_ROUTE_37`) | -| 3 | `SUICUNE` | 40 | `MAP_ROUTE_38` (`GROUP_ROUTE_38`) | - -HP is written as `0` (`xor a ; generate new stats`). - -**The map graph: `RoamMaps`** (`data/wild/roammon_maps.asm`, `0a:695b`, -`NUM_ROAMMON_MAPS` = 16). Transcribed verbatim: - -| start map | reachable maps | -|---|---| -| `ROUTE_29` | `ROUTE_30`, `ROUTE_46` | -| `ROUTE_30` | `ROUTE_29`, `ROUTE_31` | -| `ROUTE_31` | `ROUTE_30`, `ROUTE_32`, `ROUTE_36` | -| `ROUTE_32` | `ROUTE_36`, `ROUTE_31`, `ROUTE_33` | -| `ROUTE_33` | `ROUTE_32`, `ROUTE_34` | -| `ROUTE_34` | `ROUTE_33`, `ROUTE_35` | -| `ROUTE_35` | `ROUTE_34`, `ROUTE_36` | -| `ROUTE_36` | `ROUTE_35`, `ROUTE_31`, `ROUTE_32`, `ROUTE_37` | -| `ROUTE_37` | `ROUTE_36`, `ROUTE_38`, `ROUTE_42` | -| `ROUTE_38` | `ROUTE_37`, `ROUTE_39`, `ROUTE_42` | -| `ROUTE_39` | `ROUTE_38` | -| `ROUTE_42` | `ROUTE_43`, `ROUTE_44`, `ROUTE_37`, `ROUTE_38` | -| `ROUTE_43` | `ROUTE_42`, `ROUTE_44` | -| `ROUTE_44` | `ROUTE_42`, `ROUTE_43`, `ROUTE_45` | -| `ROUTE_45` | `ROUTE_44`, `ROUTE_46` | -| `ROUTE_46` | `ROUTE_45`, `ROUTE_29` | - -The file's own comment: "Notably missing are Route 40 and Route 41, which are -water routes." That is the walkthrough's "the exceptions are Routes 40 and 41". - -**Movement: `UpdateRoamMons`** (`engine/overworld/wildmons.asm:578`, `0a:6859`) - -`.Update` per live beast, then `_BackUpMapIndices`. One random byte does double -duty: - -- `and %00011111`; if the result is zero (1 in 32) it calls `JumpRoamMon` - instead, i.e. a completely random `RoamMaps` entry. -- otherwise `and %11` of that same masked value is the connection index. An - index `>=` the entry's connection count re-rolls, and so does a candidate - equal to `wRoamMons_LastMapGroup`/`wRoamMons_LastMapNumber`, which is the map - the player was on *before* the one they are on now. That last check is what - stops a beast shadowing a player who paces one boundary. - -Because the jump test and the index share one byte, they are not independent -rolls. - -**When `UpdateRoamMons` runs** (`data/maps/setup_scripts.asm`) - -| map setup script | contains `UpdateRoamMons`? | reached by | -|---|---|---| -| `MapSetupScript_Connection` | yes (line 93) | walking across a map connection, `engine/overworld/events.asm:1013 reloadend MAPSETUP_CONNECTION` | -| `MapSetupScript_Train` | yes (line 121) | magnet train, and everything that falls into it | -| `MapSetupScript_Door` | yes, by falling through into `_Train` | normal door/stair warps, `engine/overworld/events.asm:993`; Dig / Escape Rope, `engine/events/overworld.asm:836` | -| `MapSetupScript_Fall` | yes, by falling through `_Door` into `_Train` | hole falls | -| `MapSetupScript_Warp` | **no** | `warp` script opcode (`engine/overworld/scripting.asm:1958`), whiteout (`engine/events/whiteout.asm:21`), new game | -| `MapSetupScript_ReloadMap` | no | `reloadmap` | -| `MapSetupScript_Teleport` | no, runs `JumpRoamMons` instead (line 29) | Fly (`engine/events/overworld.asm:604`), Teleport (`:921`) | - -So the walkthrough's "they change route when you change route or enter/exit a -city, cave, or building" is exactly right: connections and door warps both -update. A scripted `warp` does not. - -**Scattering: `JumpRoamMons`** (`:677`, `0a:68e0`) / `JumpRoamMon` (`:710`, -`0a:6919`). Picks a uniformly random `RoamMaps` entry, re-rolling only while it -lands on the player's own current map. Two callers: - -- `MapSetupScript_Teleport` (Fly / Teleport). This is the walkthrough's "Flying - will reset the roaming legendary's location". -- `engine/menus/intro_menu.asm:283`, `farcall JumpRoamMons`, on **Continue**. - Loading a save scatters all three. - -**Meeting one: `CheckEncounterRoamMon`** (`:531`, `0a:681a`), called from the -very top of `ChooseWildEncounter` (`0a:66ab`), which itself is called from -`TryWildEncounter` (`0a:6643`) *after* the map's encounter-rate roll has already -passed. Order of gates: - -1. `CheckOnWater` -> on water, no roamer. (Suicune cannot be met while surfing.) -2. One random byte, `cp 100` -> 100/256 pass. -3. `and %00000011`, `jr z` -> 3/4 of those pass. Running total 75/256, about - 29.3%, which the asm comments itself. -4. `dec a` -> slot 0, 1 or 2, evenly. -5. That one slot's stored map group/number must equal the player's. There is - **no re-roll onto another beast**, so two beasts on your route still get one - roll each. -6. On success: `wTempWildMonSpecies` = the beast, `wCurPartyLevel` = 40, - `wBattleType` = `BATTLETYPE_ROAMING`, and `ChooseWildEncounter` returns carry - straight to `.startwildbattle`, skipping the map's own slot table entirely. A - roamer *replaces* the normal encounter. - -**Sweet Scent is a separate, better door.** `engine/events/sweet_scent.asm:31` -`SweetScentEncounter` runs `CanEncounterWildMon`, then only checks that -`GetMapEncounterRate` is **nonzero** (`ld a, b / and a / jr z`), then calls -`ChooseWildEncounter` directly. It never performs the percentage roll and never -reaches `CheckRepelEffect`. So every Sweet Scent use in grass on a roam route is -a guaranteed encounter with a flat ~9.8% chance (1/3 of 75/256) of being the -specific beast standing there. The walkthrough does not mention this. - -**Battle behaviour** (`engine/battle/core.asm`) - -- DVs, `:5832`-`:5870`: if the struct's HP byte is nonzero the stored DVs are - reused, otherwise fresh DVs are rolled and written back. A beast you chase all - game is one individual. -- HP, `:6068`-`:6090`: same test. Zero takes `.InitRoamHP`, which writes the - mon's full HP into the struct on the *first* encounter. Non-zero loads the - stored value into `wEnemyMonHP + 1`. This is the walkthrough's "once you've - weakened a legendary once, it will remain at that HP". The comment notes only - the low byte is stored because Raikou and Entei are under 256 HP at level 40. -- `TryEnemyFlee` (`:711`, `0f:44fa`), called at the head of the enemy's half of - the turn in both orders (`Battle_EnemyFirst:823`, `Battle_PlayerFirst:885`). - It bails (beast stays) only if: trainer battle, `SUBSTATUS_CANT_RUN` on the - player (Mean Look / Spider Web), a live `wEnemyWrapCount`, or the enemy is - `FRZ` or asleep. Otherwise `AlwaysFleeMons` (`data/wild/flee_mons.asm`, - `0f:4568`) is exactly `RAIKOU, ENTEI, SUICUNE` and it flees with no roll at - all. This is "the roaming legendaries will all run after one attack or one - ball throw": a failed ball sets `wBattlePlayerAction = BATTLEPLAYERACTION_USEITEM`, - `DetermineMoveOrder` sends it to `.player_first`, and `Battle_PlayerFirst` - still reaches `call TryEnemyFlee`. - Note **paralysis does not stop the flee**; only sleep and freeze do. -- `BattleEnd_HandleRoamMons` (`:8237`, `0f:76ac`): on `WIN` (which covers a - catch) it zeroes HP, sets the map pair to `GROUP_N_A` / `MAP_N_A` and zeroes - the species, retiring the slot. On anything else it banks `wEnemyMonHP + 1` - into the struct and calls `UpdateRoamMons`. Its `.not_roaming` tail is the - other half: after **any** other wild battle, `BattleRandom and $f` gives a - 1-in-16 chance the beasts move anyway. - -**Tracking: `FindNest`** (`engine/overworld/wildmons.asm:28`, `0a:6560`). Its -`.RoamMon1/.RoamMon2/.RoamMon3` tails append the roamer's current map to the -nest list whenever `wNamedObjectIndex` matches the slot's species, and only for -the Johto pass (`ld a, e / and a / jr nz, .kanto` returns before them). The one -caller is `engine/pokegear/pokegear.asm:2430`, inside `Pokedex_GetArea` -(`24:5c7f`), i.e. the **Pokedex AREA screen**, which is drawn with the Pokegear -town-map engine and headed `<MON>'S NEST`. See section 6 for the wording -mismatch with the walkthrough. - -**Catch rate** (`engine/items/item_effects.asm`, `PokeBallEffect` `03:6926`) - -- All three beasts have `db 3 ; catch rate` - (`data/pokemon/base_stats/raikou.asm`, `entei.asm`, `suicune.asm`). -- `MASTER_BALL` short-circuits to `.catch_without_fail` before any multiplier. -- `UltraBallMultiplier` (`03:6c92`) is `sla b`, a flat x2 -> effective 6. -- `FastBallMultiplier` (`03:6dbc`) carries a documented bug: it is *intended* to - x4 anything in the three `FleeMons` tables, but the `jr nz, .next` where the - author meant `jr nz, .loop` limits it to the **first three entries of - `SometimesFleeMons`** (Magnemite, Grimer, Tangela). Fast Balls are worthless - on the beasts in Gold/Silver. -- Status: the `.statuscheck` block is also buggy. `+10` for `FRZ`/`SLP` works; - the `BRN`/`PSN`/`PAR` `+5` branch is unreachable because the `ld a, - [wEnemyMonStatus]` reload is commented out. **Paralysis gives no catch bonus - at all**, which contradicts the walkthrough's "asleep or paralyzed". - ---- - -### Roam map set (the other twelve) - -These twelve maps carry no roamer-specific script or object; they matter only as -graph nodes and as places to stand in grass. Header rows are from -`data/maps/maps.asm`, dimensions from `constants/map_constants.asm`. - -| Map constant | asm file | group | W x H (blocks) | tileset / environment / music | header line | -|---|---|---|---|---|---| -| `MAP_ROUTE_29` | `maps/Route29.asm` | `NEW_BARK` | 30 x 9 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_29` | `data/maps/maps.asm:474` | -| `MAP_ROUTE_30` | `maps/Route30.asm` | `CHERRYGROVE` | 10 x 27 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_30` | `:508` | -| `MAP_ROUTE_31` | `maps/Route31.asm` | `CHERRYGROVE` | 20 x 9 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_30` | `:509` | -| `MAP_ROUTE_32` | `maps/Route32.asm` | `VIOLET` | 10 x 45 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_30` | `:247` | -| `MAP_ROUTE_33` | `maps/Route33.asm` | `AZALEA` | 10 x 9 | `TILESET_JOHTO_MODERN` / `ROUTE` / `MUSIC_ROUTE_30` | `:231` | -| `MAP_ROUTE_34` | `maps/Route34.asm` | `GOLDENROD` | 10 x 27 | `TILESET_JOHTO_MODERN` / `ROUTE` / `MUSIC_ROUTE_36` | `:268` | -| `MAP_ROUTE_38` | `maps/Route38.asm` | `OLIVINE` | 20 x 9 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_37` | `:61` | -| `MAP_ROUTE_39` | `maps/Route39.asm` | `OLIVINE` | 10 x 18 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_37` | `:62` | -| `MAP_ROUTE_43` | `maps/Route43.asm` | `LAKE_OF_RAGE` | 10 x 27 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_LAKE_OF_RAGE` | `:241` | -| `MAP_ROUTE_44` | `maps/Route44.asm` | `MAHOGANY` | 30 x 9 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_LAKE_OF_RAGE` | `:73` | -| `MAP_ROUTE_45` | `maps/Route45.asm` | `BLACKTHORN` | 10 x 45 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_36` | `:185` | -| `MAP_ROUTE_46` | `maps/Route46.asm` | `BLACKTHORN` | 10 x 18 | `TILESET_JOHTO` / `ROUTE` / `MUSIC_ROUTE_36` | `:186` | - -Excluded on purpose, and confirmed absent from `RoamMaps`: -`MAP_ROUTE_40` (`maps/Route40.asm`, `CIANWOOD`, 10 x 18, -`data/maps/maps.asm:442`) and `MAP_ROUTE_41` (`maps/Route41.asm`, `CIANWOOD`, -25 x 27, `:443`). - ---- - -### MAP_VIOLET_CITY - -- Script: `maps/VioletCity.asm` -- Header: `data/maps/maps.asm:251` -> `TILESET_JOHTO`, `TOWN`, - `LANDMARK_VIOLET_CITY`, `MUSIC_VIOLET_CITY`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:230`, `map_const VIOLET_CITY, 20, 18` -- Connections (`data/maps/attributes.asm:127`): south `Route32` (`ROUTE_32`, 0), - **west `Route36` (`ROUTE_36`, 0)**, east `Route31` (`ROUTE_31`, 9) - -Included only because it is the walkthrough's Fly anchor and the one city with a -direct edge onto a roam route. It is not itself a roam map, so arriving here -never gives a beast encounter; it only ticks `UpdateRoamMons` on the way in and -out. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 17 | `VIOLET_MART` | 2 | -| 2 | 18 | 17 | `VIOLET_GYM` | 1 | -| 3 | 30 | 17 | `EARLS_POKEMON_ACADEMY` | 1 | -| 4 | 3 | 15 | `VIOLET_NICKNAME_SPEECH_HOUSE` | 1 | -| 5 | 31 | 25 | `VIOLET_POKECENTER_1F` | 1 | -| 6 | 21 | 29 | `VIOLET_KYLES_HOUSE` | 1 | -| 7 | 23 | 5 | `SPROUT_TOWER_1F` | 1 | -| 8 | 39 | 24 | `ROUTE_31_VIOLET_GATE` | 1 | -| 9 | 39 | 25 | `ROUTE_31_VIOLET_GATE` | 2 | - -Every one of these nine is a door warp, i.e. `MAPSETUP_DOOR`, i.e. one -`UpdateRoamMons` in and one on the way out. Stepping in and out of the Pokecenter -is a legal, very short roam tick. - -**Coord events** (`def_coord_events`): none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 24 | 20 | `BGEVENT_READ` | `VioletCitySign` | -| 15 | 17 | `BGEVENT_READ` | `VioletGymSign` | -| 24 | 8 | `BGEVENT_READ` | `SproutTowerSign` | -| 27 | 17 | `BGEVENT_READ` | `EarlsPokemonAcademySign` | -| 32 | 25 | `BGEVENT_READ` | `VioletCityPokecenterSign` | -| 10 | 17 | `BGEVENT_READ` | `VioletCityMartSign` | -| 37 | 14 | `BGEVENT_ITEM` | `VioletCityHiddenHyperPotion` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| (1) | `SPRITE_FISHER` | 13 | 16 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `VioletCityEarlScript` | `EVENT_VIOLET_CITY_EARL` | -| (2) | `SPRITE_LASS` | 28 | 28 | `WANDER` | `OBJECTTYPE_SCRIPT` | `VioletCityLassScript` | -1 | -| (3) | `SPRITE_SUPER_NERD` | 24 | 14 | `WANDER` | `OBJECTTYPE_SCRIPT` | `VioletCitySuperNerdScript` | -1 | -| (4) | `SPRITE_GRAMPS` | 17 | 20 | `WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `VioletCityGrampsScript` | -1 | -| (5) | `SPRITE_YOUNGSTER` | 5 | 18 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `VioletCityYoungsterScript` | -1 | -| (6) | `SPRITE_FRUIT_TREE` | 14 | 29 | `STILL` | `OBJECTTYPE_SCRIPT` | `VioletCityFruitTree` | -1 | -| (7) | `SPRITE_POKE_BALL` | 4 | 1 | `STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityPPUp` | `EVENT_VIOLET_CITY_PP_UP` | -| (8) | `SPRITE_POKE_BALL` | 35 | 5 | `STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityRareCandy` | `EVENT_VIOLET_CITY_RARE_CANDY` | - -**Wild encounters**: none relevant (town, no grass on the walked path). - ---- - -### MAP_ROUTE_36 - -- Script: `maps/Route36.asm` -- Header: `data/maps/maps.asm:249` -> `TILESET_JOHTO`, `ROUTE`, - `LANDMARK_ROUTE_36`, `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:228`, `map_const ROUTE_36, 30, 9` -- Connections (`data/maps/attributes.asm:206`): north `Route37` (`ROUTE_37`, 10), - south `Route35` (`ROUTE_35`, 0), east `VioletCity` (`VIOLET_CITY`, 0) -- Roam edges (`RoamMaps`): `ROUTE_35`, `ROUTE_31`, `ROUTE_32`, `ROUTE_37`. One - of the two four-way junctions, so a beast standing here has the widest spread. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 18 | 8 | `ROUTE_36_NATIONAL_PARK_GATE` | 3 | -| 2 | 18 | 9 | `ROUTE_36_NATIONAL_PARK_GATE` | 4 | -| 3 | 47 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 1 | -| 4 | 48 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 2 | - -**Coord events** (`def_coord_events`): none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 29 | 1 | `BGEVENT_READ` | `Route36TrainerTips2` | -| 45 | 11 | `BGEVENT_READ` | `RuinsOfAlphNorthSign` | -| 55 | 7 | `BGEVENT_READ` | `Route36Sign` | -| 21 | 7 | `BGEVENT_READ` | `Route36TrainerTips1` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE36_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 20 | 12 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerPsychicMark` | -1 | -| `ROUTE36_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 31 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerSchoolboyAlan1` | -1 | -| `ROUTE36_WEIRD_TREE` | `SPRITE_WEIRD_TREE` | 35 | 9 | `SUDOWOODO` | `OBJECTTYPE_SCRIPT` | `SudowoodoScript` | `EVENT_ROUTE_36_SUDOWOODO` | -| `ROUTE36_LASS1` | `SPRITE_LASS` | 51 | 8 | `WALK_LEFT_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route36LassScript` | -1 | -| `ROUTE36_FISHER` | `SPRITE_FISHER` | 44 | 9 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route36RockSmashGuyScript` | -1 | -| `ROUTE36_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 21 | 4 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route36FruitTree` (`FRUITTREE_ROUTE_36`) | -1 | -| `ROUTE36_ARTHUR` | `SPRITE_YOUNGSTER` | 46 | 6 | `WANDER` | `OBJECTTYPE_SCRIPT` | `ArthurScript` | `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` | - -**Scripts of interest** - -- `Route36ArthurCallback` (`MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY`, - `ifequal THURSDAY` -> `appear ROUTE36_ARTHUR`, else `disappear`. Irrelevant to - the beasts but it is the map's only callback, so a bot that emulates map - callbacks must run it. -- `SudowoodoScript`: the one thing on this map that can physically block the - Route 36 loop. `checkitem SQUIRTBOTTLE`; without it the tree just shakes and - the tile stays solid. With it: `loadwildmon SUDOWOODO, 20`, `startbattle`, - `setevent EVENT_FOUGHT_SUDOWOODO`, then `disappear ROUTE36_WEIRD_TREE` on - either branch (`DidntCatchSudowoodo` also disappears it). Once - `EVENT_ROUTE_36_SUDOWOODO` is cleared the tile is free for good. - -**Flags and events** - -| constant | defined in | read/written by | meaning for a bot | -|---|---|---|---| -| `EVENT_ROUTE_36_SUDOWOODO` | `constants/event_flags.asm:1178` | `object_event` visibility; cleared by `disappear` in `SudowoodoScript` | while set, x=35,y=9 is impassable | -| `EVENT_FOUGHT_SUDOWOODO` | `constants/event_flags.asm` | set in `WateredWeirdTreeScript` | gates the Rock Smash gift and the lass's line | -| `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` | `constants/event_flags.asm` | `Route36ArthurCallback` | day-of-week NPC | - -**Wild encounters** (`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_36`, -rates `10 percent` morn/day/nite, Gold column) - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 12 `NIDORAN_M` | 12 `NIDORAN_M` | 12 `NIDORAN_M` | -| 2 | 12 `NIDORAN_F` | 12 `NIDORAN_F` | 12 `NIDORAN_F` | -| 3 | 13 `PIDGEY` | 14 `PIDGEY` | 13 `HOOTHOOT` | -| 4 | 13 `GROWLITHE` | 13 `GROWLITHE` | 13 `GROWLITHE` | -| 5 | 13 `STANTLER` | 13 `STANTLER` | 13 `STANTLER` | -| 6 | 15 `PIDGEY` | 15 `GROWLITHE` | 15 `HOOTHOOT` | -| 7 | 15 `PIDGEY` | 15 `GROWLITHE` | 15 `HOOTHOOT` | - -(Silver swaps `GROWLITHE` for `VULPIX` and reverses the Nidoran pair.) Max wild -level 15, which matters for the Max Repel plan in section 3. - ---- - -### MAP_ROUTE_35 - -- Script: `maps/Route35.asm` -- Header: `data/maps/maps.asm:248` -> `TILESET_JOHTO`, `ROUTE`, - `LANDMARK_ROUTE_35`, `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_POND` -- Dimensions: `constants/map_constants.asm:227`, `map_const ROUTE_35, 10, 18` -- Connections (`data/maps/attributes.asm:202`): north `Route36` (`ROUTE_36`, 0), - south `GoldenrodCity` (`GOLDENROD_CITY`, -5) -- Roam edges: `ROUTE_34`, `ROUTE_35` -> `ROUTE_34`, `ROUTE_36` - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 9 | 33 | `ROUTE_35_GOLDENROD_GATE` | 1 | -| 2 | 10 | 33 | `ROUTE_35_GOLDENROD_GATE` | 2 | -| 3 | 3 | 5 | `ROUTE_35_NATIONAL_PARK_GATE` | 3 | - -**Coord events** (`def_coord_events`): none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 1 | 7 | `BGEVENT_READ` | `Route35Sign` | -| 11 | 31 | `BGEVENT_READ` | `Route35Sign` | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| (1) | `SPRITE_YOUNGSTER` | 3 | 19 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (4) | `TrainerCamperIvan` | -1 | -| (2) | `SPRITE_YOUNGSTER` | 8 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (3) | `TrainerCamperElliot` | -1 | -| (3) | `SPRITE_LASS` | 7 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (3) | `TrainerPicnickerBrooke` | -1 | -| (4) | `SPRITE_LASS` | 11 | 24 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (3) | `TrainerPicnickerKim` | -1 | -| (5) | `SPRITE_YOUNGSTER` | 14 | 28 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (0) | `TrainerBirdKeeperBryan` | -1 | -| (6) | `SPRITE_FISHER` | 2 | 10 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (2) | `TrainerFirebreatherWalt` | -1 | -| (7) | `SPRITE_BUG_CATCHER` | 16 | 7 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (3) | `TrainerBugCatcherArnie` | -1 | -| (8) | `SPRITE_SUPER_NERD` | 5 | 10 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (2) | `TrainerJugglerIrwin` | -1 | -| (9) | `SPRITE_OFFICER` | 5 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TrainerOfficerDirk` | -1 | -| (10) | `SPRITE_FRUIT_TREE` | 2 | 25 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route35FruitTree` (`FRUITTREE_ROUTE_35`) | -1 | -| (11) | `SPRITE_POKE_BALL` | 13 | 16 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route35TMRollout` (`TM_ROLLOUT`) | `EVENT_ROUTE_35_TM_ROLLOUT` | - -**Scripts of interest** - -- `TrainerOfficerDirk` (`maps/Route35.asm:223`): an `OBJECTTYPE_SCRIPT`, not a - sight trainer. `checktime NITE` -> only battles at night, guarded by - `EVENT_BEAT_OFFICER_DIRK`, `loadtrainer OFFICER, DIRK`. A bot oscillating - Route 35 / Route 36 at night will be pulled into this battle once. Note that - a normal wild-or-trainer battle is *not* wasted time: `BattleEnd_HandleRoamMons`'s - `.not_roaming` tail moves the beasts 1 time in 16. - -**Wild encounters** (`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_35`, -rates `10 percent` all three, Gold column) - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 12 `NIDORAN_M` | 12 `NIDORAN_M` | 12 `NIDORAN_M` | -| 2 | 12 `NIDORAN_F` | 12 `NIDORAN_F` | 12 `NIDORAN_F` | -| 3 | 14 `DROWZEE` | 14 `DROWZEE` | 14 `DROWZEE` | -| 4 | 10 `ABRA` | 10 `ABRA` | 10 `ABRA` | -| 5 | 14 `PIDGEY` | 14 `PIDGEY` | 14 `HOOTHOOT` | -| 6 | 10 `DITTO` | 10 `DITTO` | 10 `DITTO` | -| 7 | 12 `YANMA` | 12 `YANMA` | 12 `YANMA` | - -Max wild level 14. - ---- - -### MAP_ROUTE_37 - -- Script: `maps/Route37.asm` -- Header: `data/maps/maps.asm:250` -> `TILESET_JOHTO`, `ROUTE`, - `LANDMARK_ROUTE_37`, `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_SHORE` -- Dimensions: `constants/map_constants.asm:229`, `map_const ROUTE_37, 10, 9` -- Connections (`data/maps/attributes.asm:211`): north `EcruteakCity` - (`ECRUTEAK_CITY`, -5), south `Route36` (`ROUTE_36`, -10) -- Roam edges: `ROUTE_36`, `ROUTE_38`, `ROUTE_42`. Entei's starting map. - -**Warps** (`def_warp_events`): **none**. Route 37 is reached only by connection, -which makes it the cheapest possible `UpdateRoamMons` tick in the loop: one step -across the boundary and one step back. - -**Coord events** (`def_coord_events`): none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 5 | 3 | `BGEVENT_READ` | `Route37Sign` | -| 4 | 2 | `BGEVENT_ITEM` | `Route37HiddenEther` (`hiddenitem ETHER, EVENT_ROUTE_37_HIDDEN_ETHER`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| `ROUTE37_WEIRD_TREE1` | `SPRITE_WEIRD_TREE` | 6 | 12 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (1) | `TrainerTwinsAnnandanne1` | -1 | -| `ROUTE37_WEIRD_TREE2` | `SPRITE_WEIRD_TREE` | 7 | 12 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (1) | `TrainerTwinsAnnandanne2` | -1 | -| `ROUTE37_YOUNGSTER` | `SPRITE_YOUNGSTER` | 9 | 6 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (3) | `TrainerPsychicGreg` | -1 | -| `ROUTE37_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 13 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree1` (`FRUITTREE_ROUTE_37_1`) | -1 | -| `ROUTE37_SUNNY` | `SPRITE_BUG_CATCHER` | 16 | 8 | `WANDER` | `OBJECTTYPE_SCRIPT` | `SunnyScript` | `EVENT_ROUTE_37_SUNNY_OF_SUNDAY` | -| `ROUTE37_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 16 | 5 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree2` (`FRUITTREE_ROUTE_37_2`) | -1 | -| `ROUTE37_FRUIT_TREE3` | `SPRITE_FRUIT_TREE` | 15 | 7 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route37FruitTree3` (`FRUITTREE_ROUTE_37_3`) | -1 | - -**Scripts of interest** - -- `Route37SunnyCallback` (`MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY`, - `ifequal SUNDAY` -> `appear ROUTE37_SUNNY`, else `disappear`. -- `SunnyScript`: `verbosegiveitem MAGNET` guarded by - `EVENT_GOT_MAGNET_FROM_SUNNY` and `EVENT_MET_SUNNY_OF_SUNDAY`. - -**Wild encounters** (`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_37`, -rates `10 percent` all three, Gold column) - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 13 `PIDGEY` | 13 `PIDGEY` | 13 `SPINARAK` | -| 2 | 15 `STANTLER` | 15 `STANTLER` | 15 `STANTLER` | -| 3 | 15 `PIDGEY` | 15 `PIDGEY` | 15 `HOOTHOOT` | -| 4 | 14 `GROWLITHE` | 14 `GROWLITHE` | 14 `GROWLITHE` | -| 5 | 15 `PIDGEY` | 15 `PIDGEOTTO` | 15 `SPINARAK` | -| 6 | 15 `PIDGEY` | 16 `GROWLITHE` | 15 `SPINARAK` | -| 7 | 15 `PIDGEY` | 16 `GROWLITHE` | 15 `SPINARAK` | - -Max wild level 16. - ---- - -### MAP_ROUTE_42 - -- Script: `maps/Route42.asm` -- Header: `data/maps/maps.asm:72` -> `TILESET_JOHTO`, `ROUTE`, - `LANDMARK_ROUTE_42`, `MUSIC_LAKE_OF_RAGE`, phone `FALSE`, `PALETTE_AUTO`, - `FISHGROUP_LAKE` -- Dimensions: `constants/map_constants.asm:60`, `map_const ROUTE_42, 30, 9` -- Connections (`data/maps/attributes.asm:231`): west `EcruteakCity` - (`ECRUTEAK_CITY`, -9), east `MahoganyTown` (`MAHOGANY_TOWN`, 0) -- Roam edges: `ROUTE_43`, `ROUTE_44`, `ROUTE_37`, `ROUTE_38`. The other four-way - junction, and Raikou's starting map. This is the walkthrough's Max Repel spot. - -**Warps** (`def_warp_events`) - -| idx | x | y | destination map | dest warp | -|---|---|---|---|---| -| 1 | 0 | 8 | `ROUTE_42_ECRUTEAK_GATE` | 3 | -| 2 | 0 | 9 | `ROUTE_42_ECRUTEAK_GATE` | 4 | -| 3 | 10 | 5 | `MOUNT_MORTAR_1F_OUTSIDE` | 1 | -| 4 | 28 | 9 | `MOUNT_MORTAR_1F_OUTSIDE` | 2 | -| 5 | 46 | 7 | `MOUNT_MORTAR_1F_OUTSIDE` | 3 | - -**Coord events** (`def_coord_events`): none. - -**BG events** (`def_bg_events`) - -| x | y | type | script/item | -|---|---|---|---| -| 4 | 10 | `BGEVENT_READ` | `Route42Sign1` | -| 7 | 5 | `BGEVENT_READ` | `MtMortarSign1` | -| 45 | 9 | `BGEVENT_READ` | `MtMortarSign2` | -| 54 | 8 | `BGEVENT_READ` | `Route42Sign2` | -| 16 | 11 | `BGEVENT_ITEM` | `Route42HiddenMaxPotion` (`hiddenitem MAX_POTION, EVENT_ROUTE_42_HIDDEN_MAX_POTION`) | - -**Object events** (`def_object_events`) - -| const | sprite | x | y | movement | type | script label | event flag | -|---|---|---|---|---|---|---|---| -| (1) | `SPRITE_FISHER` | 40 | 10 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (1) | `TrainerFisherChris` | -1 | -| (2) | `SPRITE_POKEFAN_M` | 51 | 9 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (3) | `TrainerHikerBenjamin` | -1 | -| (3) | `SPRITE_SUPER_NERD` | 47 | 8 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` (3) | `TrainerPokemaniacShane` | -1 | -| (4) | `SPRITE_FRUIT_TREE` | 27 | 16 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree1` (`FRUITTREE_ROUTE_42_1`) | -1 | -| (5) | `SPRITE_FRUIT_TREE` | 28 | 16 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree2` (`FRUITTREE_ROUTE_42_2`) | -1 | -| (6) | `SPRITE_FRUIT_TREE` | 29 | 16 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route42FruitTree3` (`FRUITTREE_ROUTE_42_3`) | -1 | -| (7) | `SPRITE_POKE_BALL` | 6 | 4 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route42UltraBall` (`itemball ULTRA_BALL`) | `EVENT_ROUTE_42_ULTRA_BALL` | -| (8) | `SPRITE_POKE_BALL` | 33 | 8 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route42SuperPotion` (`itemball SUPER_POTION`) | `EVENT_ROUTE_42_SUPER_POTION` | - -**Items** - -| item | how obtained | source (label / bg_event / hidden) | one-time flag | -|---|---|---|---| -| `ULTRA_BALL` | item ball at x=6, y=4 | `Route42UltraBall` | `EVENT_ROUTE_42_ULTRA_BALL` | -| `SUPER_POTION` | item ball at x=33, y=8 | `Route42SuperPotion` | `EVENT_ROUTE_42_SUPER_POTION` | -| `MAX_POTION` | hidden, x=16, y=11 | `Route42HiddenMaxPotion` | `EVENT_ROUTE_42_HIDDEN_MAX_POTION` | - -**Wild encounters** (`data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_42`, -rates `10 percent` all three, Gold column) - -| slot | morn | day | nite | -|---|---|---|---| -| 1 | 15 `MANKEY` | 15 `MANKEY` | 15 `MANKEY` | -| 2 | 13 `MAREEP` | 13 `MAREEP` | 13 `MAREEP` | -| 3 | 14 `SPEAROW` | 14 `SPEAROW` | 14 `ZUBAT` | -| 4 | 16 `SPEAROW` | 16 `SPEAROW` | 16 `ZUBAT` | -| 5 | 15 `FLAAFFY` | 15 `FLAAFFY` | 15 `FLAAFFY` | -| 6 | 17 `FLAAFFY` | 17 `FLAAFFY` | 17 `FLAAFFY` | -| 7 | 17 `FLAAFFY` | 17 `FLAAFFY` | 17 `FLAAFFY` | - -Max wild level 17. Every grass slot on this map is below 39, which is precisely -why the walkthrough's Max Repel plan works here (section 3). - ---- - -### Trainers on the four detailed maps - -Constants from `constants/trainer_constants.asm`, parties from -`data/trainers/parties.asm` (group and index in the file's own comment). - -| const | class | id | party (`data/trainers/parties.asm`) | script label | map | -|---|---|---|---|---|---| -| `MARK` | `PSYCHIC_T` | `PSYCHIC_T (7)` | 13 `ABRA` (Teleport, Flash), 13 `ABRA` (Teleport, Flash), 15 `KADABRA` (Teleport, Kinesis, Confusion) | `TrainerPsychicMark` | Route 36 | -| `ALAN1` | `SCHOOLBOY` | `SCHOOLBOY (3)` | 16 `TANGELA` | `TrainerSchoolboyAlan1` | Route 36 | -| `IVAN` | `CAMPER` | `CAMPER (3)` | 10 `DIGLETT`, 10 `ZUBAT`, 14 `DIGLETT` | `TrainerCamperIvan` | Route 35 | -| `ELLIOT` | `CAMPER` | `CAMPER (4)` | 13 `SANDSHREW`, 15 `MARILL` | `TrainerCamperElliot` | Route 35 | -| `BROOKE` | `PICNICKER` | `PICNICKER (3)` | 16 `PIKACHU` (ThunderShock, Growl, Quick Attack, Double Team) | `TrainerPicnickerBrooke` | Route 35 | -| `KIM` | `PICNICKER` | `PICNICKER (4)` | 15 `VULPIX` | `TrainerPicnickerKim` | Route 35 | -| `BRYAN` | `BIRD_KEEPER` | `BIRD_KEEPER (3)` | 12 `PIDGEY`, 14 `PIDGEOTTO` | `TrainerBirdKeeperBryan` | Route 35 | -| `WALT` | `FIREBREATHER` | `FIREBREATHER (6)` | 11 `MAGMAR`, 13 `MAGMAR` | `TrainerFirebreatherWalt` | Route 35 | -| `ARNIE1` | `BUG_CATCHER` | `BUG_CATCHER (8)` | 15 `VENONAT` | `TrainerBugCatcherArnie` | Route 35 | -| `IRWIN1` | `JUGGLER` | `JUGGLER (1)` | 2 / 6 / 10 / 14 `VOLTORB` | `TrainerJugglerIrwin` | Route 35 | -| `DIRK` | `OFFICER` | (see `OfficerGroup`) | night only | `TrainerOfficerDirk` | Route 35 | -| `ANNANDANNE1` | `TWINS` | `TWINS (2)` | 16 `CLEFAIRY` (Growl, Encore, DoubleSlap, Metronome), 16 `JIGGLYPUFF` (Sing, Defense Curl, Pound, Disable) | `TrainerTwinsAnnandanne1` | Route 37 | -| `ANNANDANNE2` | `TWINS` | `TWINS (3)` | same two, order swapped | `TrainerTwinsAnnandanne2` | Route 37 | -| `GREG` | `PSYCHIC_T` | `PSYCHIC_T (5)` | 17 `DROWZEE` (Hypnosis, Disable, Dream Eater) | `TrainerPsychicGreg` | Route 37 | -| `CHRIS1` | `FISHER` | `FISHER (7)` | 18 `QWILFISH` | `TrainerFisherChris` | Route 42 | -| `BENJAMIN` | `HIKER` | `HIKER (6)` | 14 `DIGLETT`, 14 `GEODUDE`, 16 `DUGTRIO` | `TrainerHikerBenjamin` | Route 42 | -| `SHANE` | `POKEMANIAC` | `POKEMANIAC (4)` | 16 `NIDORINA`, 16 `NIDORINO` | `TrainerPokemaniacShane` | Route 42 | - -All of these are one-and-done (`EVENT_BEAT_*` in the `trainer` macro row). By -this point in the game they are already beaten and are not obstacles. - ---- - -### The beasts as battle opponents - -`data/pokemon/base_stats/{raikou,entei,suicune}.asm`, level from -`InitRoamMons`, moves from `data/pokemon/evos_attacks.asm` (last four learned at -or before 40). - -| species | types | base HP/Atk/Def/Spe/SpA/SpD | catch rate | level 40 moveset | -|---|---|---|---|---| -| `RAIKOU` | Electric | 90 / 85 / 75 / 115 / 115 / 100 | 3 | `LEER`, `THUNDERSHOCK`, `ROAR`, `QUICK_ATTACK` | -| `ENTEI` | Fire | 115 / 115 / 85 / 100 / 90 / 75 | 3 | `LEER`, `EMBER`, `ROAR`, `FIRE_SPIN` | -| `SUICUNE` | Water | 100 / 75 / 115 / 85 / 90 / 115 | 3 | `LEER`, `WATER_GUN`, `ROAR`, `GUST` | - -All three know **Roar**. `BattleCommand_ForceSwitch`'s wild-target branch -(`engine/battle/effect_commands.asm:4913`, `.force_player_switch` at `:5010` --> `.wild_succeed_playeristarget` at `:5041`) compares -`wCurPartyLevel` against `wBattleMonLevel` and, when the wild level is greater -or equal, succeeds unconditionally: `wForcedSwitch` = TRUE and `wBattleResult` = -DRAW, ending the battle on the spot. Against a lead at level 40 or below that is -a guaranteed battle-ender if the beast picks Roar. The DRAW result then routes -through `BattleEnd_HandleRoamMons`'s non-WIN arm, so the beast keeps its damage -and moves. - ---- - -## 3. Blockers and gates - -| Gate | Enforced by (file:label) | Requirement | Unlock condition | -|---|---|---|---| -| The beasts do not exist at all | `maps/BurnedTowerB1F.asm:65` `special InitRoamMons`, guarded by `coord_event 9, 5, SCENE_BURNEDTOWERB1F_RELEASE_THE_BEASTS, ReleaseTheBeasts` | walk the Burned Tower B1F trigger | `EVENT_RELEASED_THE_BEASTS` set, `SCENE_BURNEDTOWERB1F_NOOP` | -| No roamer on water | `engine/overworld/wildmons.asm:534` `CheckEncounterRoamMon` `call CheckOnWater / jr z, .DontEncounterRoamMon` | be on a land tile | none, this is absolute | -| No roamer off the sixteen routes | `data/wild/roammon_maps.asm` `RoamMaps` + the map compare in `CheckEncounterRoamMon:546` | stand on the beast's stored map | none | -| No roamer where there is no wild table | `engine/overworld/wildmons.asm:270` `LoadWildMonDataPointer` returning nc -> `.nowildbattle` before `CheckEncounterRoamMon` runs | map must have grass/water data | none | -| No roamer in the Bug Contest | `engine/overworld/events.asm:1194` `ChooseWildEncounter_BugContest` never calls `CheckEncounterRoamMon` | leave the contest | none | -| Route 36 tile x=35, y=9 blocked | `maps/Route36.asm` `SudowoodoScript`, `checkitem SQUIRTBOTTLE` | `SQUIRTBOTTLE` in bag, then win/lose/flee the level 20 Sudowoodo | `EVENT_ROUTE_36_SUDOWOODO` cleared by `disappear` | -| Beast escapes after one player action | `engine/battle/core.asm:711` `TryEnemyFlee` + `data/wild/flee_mons.asm` `AlwaysFleeMons` | trap it: `SUBSTATUS_CANT_RUN` (Mean Look / Spider Web), a live `wEnemyWrapCount`, or inflict `SLP` / `FRZ` | any one of those four makes `TryEnemyFlee` return no-carry | -| Repel filters the *wild*, not the beast | `engine/overworld/wildmons.asm:366` `CheckRepelEffect`: `ld a, [wCurPartyLevel] / cp [hl] / jr nc, .encounter` where `hl` is the first non-fainted party mon's level | roamer level is 40, so the lead must be **level 40 or lower** or the roamer is repelled too | Route 42 grass tops out at level 17, so a level 39 lead plus `MAX_REPEL` (250 steps, `engine/items/item_effects.asm:2058`) blocks every normal encounter and passes the beast. A level 41+ lead blocks the beast as well. | -| Fast Ball does nothing here | `engine/items/item_effects.asm:986` `FastBallMultiplier`, documented bug (`jr nz, .next` instead of `jr nz, .loop`) | use `ULTRA_BALL` (x2, `03:6c92`) or `MASTER_BALL` (short-circuit) | none, the bug is in the shipped ROM | -| Paralysis gives no catch bonus | `engine/items/item_effects.asm:344` `.statuscheck`, the `ld a, [wEnemyMonStatus]` reload is commented out | `SLP` or `FRZ` only (+10 to the rate) | none | -| Ultra Ball / Max Repel supply | `data/items/marts.asm:216 MartBlackthorn`, `:384 MartIndigoPlateau` | money | both marts stock `ULTRA_BALL` and `MAX_REPEL` | - -Nothing in this section is badge- or HM-gated on its own. The HMs needed to -reach individual roam routes (Surf for Route 41's neighbours, Whirlpool, etc.) -belong to their own sections; all sixteen roam maps are walkable by the time the -beasts are loose. - ---- - -## 4. Bot checklist - -Preconditions for the whole section: `EVENT_RELEASED_THE_BEASTS` set, -`save.roamers` (port) / the three `roam_struct`s (cart) populated, `BICYCLE` in -the bag, `SQUIRTBOTTLE` already used on Route 36. - -1. **Locate.** Map `MAP_*` any, action: open Pokedex -> select `RAIKOU` / - `ENTEI` / `SUICUNE` -> AREA. Precondition: species is in the *seen* dex - (set by the first encounter, not by the Burned Tower cutscene). - Postcondition: the blinking nest icon is the beast's current map - (`FindNest.RoamMon1/2/3`). If the species is retired the icon is absent. -2. **Anchor.** Fly to `MAP_VIOLET_CITY`. Warning: the Fly itself runs - `JumpRoamMons`, so any location read from step 1 is stale afterwards. Do step - 1 *after* the Fly, not before. -3. **Oscillate (cheap ticks).** From Violet City walk west onto `MAP_ROUTE_36` - (connection, `data/maps/attributes.asm:209`). Then repeat: step north onto - `MAP_ROUTE_37` and back, step south onto `MAP_ROUTE_35` and back. Each single - boundary crossing is one `MapSetupScript_Connection` and therefore one - `UpdateRoamMons`. Route 37 has zero warps, so the boundary step is the only - thing that happens there. -4. **Alternative tick.** Any door warp works too: on `MAP_VIOLET_CITY` step onto - warp 5 (x=31, y=25, `VIOLET_POKECENTER_1F`) and back out. That is - `MAPSETUP_DOOR` -> `MapSetupScript_Train` -> `UpdateRoamMons`, and it also - heals. -5. **Fish for the encounter.** Stand in grass on the map the beast is on and - walk. Each step that passes `CanEncounterWildMon` and the map's 10% rate roll - then has a 75/256 roamer roll, of which 1/3 selects your beast. - Postcondition: `wBattleType = BATTLETYPE_ROAMING`. -6. **Better: Sweet Scent.** Same tile, use `SWEET_SCENT`. It skips the 10% roll - and the repel check entirely (`engine/events/sweet_scent.asm:31`), so every - use is an encounter and ~9.8% of uses are the specific beast. This is the - highest-throughput option and the walkthrough does not mention it. -7. **Repel filter (optional).** Lead a party mon of level 39 or 40 (a raised - Scyther works; it learns `FALSE_SWIPE` at level 18 per - `data/pokemon/evos_attacks.asm:1674`, and at 39 its auto-generated four are - False Swipe / Agility / Wing Attack / Slash). Use `MAX_REPEL` (250 steps). - Precondition: lead level <= 40. Postcondition: only the level 40 beast can - still trigger. -8. **Save.** Before every encounter. Battle outcomes are irreversible: a `WIN` - retires the slot for the rest of the file. -9. **In battle, turn 1 only.** Options, in order of value: - - `MASTER_BALL` -> guaranteed, but there is only one and there are three - beasts. - - Trap first (`MEAN_LOOK` / `SPIDER_WEB`, or a wrap move), which makes - `TryEnemyFlee` return no-carry and buys unlimited turns. This is the only - way to legitimately chip HP and land status. - - Otherwise: one `FALSE_SWIPE` (leaves 1 HP, banked into the struct for next - time) **or** one `ULTRA_BALL`. Either way the beast is gone at the end of - the turn. - - Do not count on `Roar`: if the beast picks it against a lead of level 40 or - lower the battle ends immediately as a DRAW. -10. **After the battle.** `BattleEnd_HandleRoamMons` has already banked HP and - moved the beast. Return to step 1. -11. **Repeat per beast.** Three independent slots. Retiring one does not affect - the other two; their `RoamMaps` walk is unchanged. - ---- - -## 5. Port coverage - -| Beat | Port file | Status | -|---|---|---| -| `roam_struct` on the save (species/level/map/hp/dvs, three slots in Raikou/Entei/Suicune order) | `src/core/gen2/Roamers.lua:41-47`, `:184-217` | implemented | -| `InitRoamMons` reachable through the real special dispatch | `src/script/gen2/Specials.lua:1843-1848`, `src/core/gen2/Roamers.lua:196` | implemented | -| `RoamMaps` graph, all 16 rows, Routes 40/41 excluded | `src/core/gen2/Roamers.lua:58-75` | implemented, but hardcoded: `Roamers.mapTable` looks for `encounters.roamMaps` and the extractor does not emit it yet (`:77-85`) | -| `.Update` shared-byte jump/index roll, last-map re-roll | `src/core/gen2/Roamers.lua:142-158` | implemented | -| `JumpRoamMon` random entry, re-roll off the player's map | `src/core/gen2/Roamers.lua:117-126` | implemented | -| `_BackUpMapIndices` | `src/core/gen2/Roamers.lua:223-234` | implemented | -| `UpdateRoamMons` on `CONNECTION` / `DOOR` / `FALL` / `TRAIN` | `src/world/gen2/World.lua:211-215` (`MAPSETUP_ROAM_UPDATE`), `:2626-2633` | implemented, and the set matches `data/maps/setup_scripts.asm` exactly | -| `JumpRoamMons` on `TELEPORT` (Fly / Teleport) | `src/world/gen2/World.lua:215`, `:2617-2624` | implemented | -| `JumpRoamMons` on **Continue** (`engine/menus/intro_menu.asm:283`) | - | **missing**: `Roamers.jumpAll` has exactly one caller, `roamMonsBeforeLoad`, which is keyed on `MAPSETUP_TELEPORT`. Loading a save does not scatter the beasts. | -| `CheckEncounterRoamMon` (water gate, 100/256, `and %11`, slot map compare, no re-roll) | `src/core/gen2/Roamers.lua:288-301`; called from `src/world/gen2/World.lua:3043` and `:3790` | implemented | -| Roamer check ordered **after** the map's encounter-rate roll | `src/world/gen2/World.lua:3043` vs `:3053-3060` | **partial**: the port runs `Roamers.checkEncounter` *before* `Encounter.triggers`, so a beast can appear on a step whose 10% roll would have failed. The asm order is `TryWildEncounter.EncounterRate` -> `ChooseWildEncounter` -> `CheckEncounterRoamMon`. Beast encounter rate is inflated by roughly 1/rate. | -| Sweet Scent path also consults the roamers, and skips the rate roll | `src/world/gen2/World.lua:3785-3796` | implemented, and the "nonzero rate only" check at `:3786` matches `engine/events/sweet_scent.asm:38` | -| `CheckRepelEffect` (lead level vs `wCurPartyLevel`) | - | **missing**: `save.repelSteps` counts down in `src/world/gen2/StepEvents.lua:98-101` and prints the wore-off text, but nothing in `World:tryWildEncounter` consults it. The walkthrough's Max Repel plan is a no-op in the port today. | -| DV roll on first encounter, DVs kept afterwards | `src/core/gen2/Roamers.lua:311-328` | implemented | -| HP banked in a single byte, reloaded on the next encounter | `src/core/gen2/Roamers.lua:182`, `:311-328`, `:341-354` | implemented | -| `BattleEnd_HandleRoamMons` WIN/caught retires the slot | `src/core/gen2/Roamers.lua:345-350`, wired at `src/world/gen2/World.lua:4475-4478` | implemented | -| `.not_roaming` 1-in-16 drift after any other wild battle | `src/core/gen2/Roamers.lua:359-363` | implemented | -| `AlwaysFleeMons` / `OftenFleeMons` / `SometimesFleeMons` and the shared roll byte | `src/core/gen2/Roamers.lua:370-380`, `src/battle/gen2/Battle.lua:1639-1651` | implemented | -| Sleep / freeze suppress the flee | `src/battle/gen2/Battle.lua:1641-1642` | implemented | -| `SUBSTATUS_CANT_RUN` (Mean Look) and `wEnemyWrapCount` suppress the flee | - | **missing**, and the port says so itself at `src/battle/gen2/Battle.lua:1632-1635`. There is currently no way to trap a beast, which removes the only legitimate multi-turn strategy. | -| Failed ball still costs the turn, so the beast flees | `src/ui/gen2/BattleState.lua:900-903` -> `Battle:takeTurn{kind="item"}` -> `Battle:tryEnemyFlee` (`:1877`) | implemented | -| `MASTER_BALL` never fails; `ULTRA_BALL` x2 | `src/battle/gen2/Catching.lua:29-30`, `:62-63` | implemented | -| `FastBallMultiplier` bug (x4 only for Magnemite/Grimer/Tangela) | `src/battle/gen2/Catching.lua:39` (`FAST_BALL = 1`) | **partial**: the port is correct for the beasts (flat x1) but drops the x4 the three buggy species do get | -| Pokedex AREA screen shows the roamer's current map (`FindNest.RoamMon1/2/3`) | `src/ui/TownMap.lua:167-198` | **missing**: nest mode scans only the map wild-slot tables for the species; there is no roamer branch, so a located beast never appears on the AREA map | -| `BattleCommand_ForceSwitch` (Roar from a wild beast ends the battle as a DRAW) | - | **missing**: `EFFECT_FORCE_SWITCH` exists only as an AI scoring hook (`src/battle/gen2/Ai.lua:559`); there is no effect handler, so a roamer's Roar does not end the battle | -| Headless coverage | `tests/gen2_world_test.lua:2203-2271`, `tests/gen2_roamers_test.lua`, `tests/drivers/gold_roamers.lua` | implemented: the driver runs the real `InitRoamMons` special, asserts the three starting routes, and exercises `MAPSETUP_DOOR` ticks | - ---- - -## 6. Unresolved / verify by hand - -1. **"They will show up on the map in Pokegear."** The only caller of `FindNest` - is `engine/pokegear/pokegear.asm:2430`, inside `Pokedex_GetArea` (`24:5c7f`), - whose header string is `'S NEST` (`.PlaceString_MonsNest`). That is the - **Pokedex AREA** screen, which is *drawn* with the Pokegear town map code but - is not reached from the Pokegear menu. Nothing in `_TownMap` - (`engine/pokegear/pokegear.asm:1709`) plots roamers. Treat the walkthrough's - wording as loose. -2. **"After the initial encounter ... they will show up."** `FindNest` itself has - no "encountered" gate; it only compares `wNamedObjectIndex` with the slot's - species. The real gate is that you must be able to select the species in the - Pokedex at all, i.e. it must be SEEN. I could not find anything in - `maps/BurnedTowerB1F.asm` that marks the three as seen during - `ReleaseTheBeasts` (it only `appear`s sprites and plays cries), so in practice - the first battle is what unlocks the AREA view. Not proven by a single - `SetSeenMon` call; worth a hardware check. -3. **"asleep or paralyzed."** Sleep and freeze are worth `+10` to the catch rate - (`engine/items/item_effects.asm:344`). Paralysis is worth **nothing** because - of the commented-out `ld a, [wEnemyMonStatus]` reload in the same block, which - the file itself flags as a bug. The advice is half wrong on the cart. -4. **"they occasionally will jump a few routes."** The asm's only "jump" is the - 1-in-32 `and %00011111 / jr z, JumpRoamMon` branch inside `.Update`, which is - not "a few routes" but a uniformly random one of the sixteen. The observed - behaviour matches loosely; the mechanism does not. -5. **"as quickly as every 10 seconds."** No timer anywhere in the roam code. Roam - movement is purely event-driven (map setup scripts and battle ends). The ten - seconds is a human estimate of how long two boundary crossings take. -6. **Roar vs the level-39 Scyther plan.** `BattleCommand_ForceSwitch`'s wild - branch reads `wCurPartyLevel` for the *wild* mon's level and compares it with - `wBattleMonLevel`; at 40 vs 39 it takes `.wild_succeed_playeristarget` - unconditionally. I did not trace whether `wCurPartyLevel` is still the wild - level at that point after a mid-battle player switch (several routines write - that variable), so the "guaranteed Roar" claim is asserted from the code as - written and should be confirmed on hardware before a bot relies on it. -7. **Bike and encounter rate.** The walkthrough leans on biking. `CanEncounterWildMon` - (`engine/overworld/events.asm:1164`) and `GetMapEncounterRate` have no bicycle - term, so the bike changes travel speed only, not encounter odds. Stated here - because the opposite is widely believed. -8. **Suicune's starting route vs the walkthrough's framing.** `InitRoamMons` - places Suicune on `ROUTE_38`, not at the Tin Tower or Route 42. The section - text never states a starting route, so there is no contradiction, but a bot - that assumes "Suicune is the one you chase last" has no asm basis for it. -9. **Route dimensions vs event coordinates.** `map_const` widths/heights are in - blocks; `warp_event` / `object_event` / `bg_event` coordinates are in the - 2x-finer walk grid (e.g. `ROUTE_35` is 10 x 18 blocks and carries a warp at - y=33). Every coordinate in this document is the raw asm value, unconverted. diff --git a/docs/gold-walkthrough/section-00-chikorita-cyndaquil-or-totodile.txt b/docs/gold-walkthrough/section-00-chikorita-cyndaquil-or-totodile.txt deleted file mode 100644 index 9d4b6322..00000000 --- a/docs/gold-walkthrough/section-00-chikorita-cyndaquil-or-totodile.txt +++ /dev/null @@ -1,119 +0,0 @@ -============================================================================== -============================================================================== -============================================================================== - -(c06) ------------------------------------------------- ----- 6 > Chikorita, Cyndaquil, or Totodile? ---- ------------------------------------------------- - -Video Playthrough Link (Introduction through Violet City Gym): -https://www.youtube.com/watch?v=dqJtkx8DcE0 - -|-------------| -|New Bark Town| -|-------------| - -Pokémon found in New Bark Town: - -#152 Chikorita -#155 Cyndaquil -#158 Totodile - -Items found in New Bark Town: - -Everstone -Pokegear - -When you first start, you will be in New Bark Town. Choose new game and start. -Afterwards, Professor Oak will ask you the time. First answer what hour it is -and what minute it is, so you are setting the game's time. - -There is a default time, which is 10:00 AM, but you may set the time for -whatever you'd like it to be. Preferably, it should be the time that it is where -you live, because then it'd make more sense. They have it well-programmed on -Gold/Silver that even if you don't have the GBA on, the time will keep running. - -Then, Professor Oak will welcome you to the world of Pokémon like the older -versions of Pokémon and introduces you to Professor Elm. Basically Pokémon -cannot be lived without in the worlds of Johto and Kanto. Then he asks you what -your name is, so input that. Choose something as your name and your Pokémon -Gold/Silver adventure finally unfolds! - -You are in your room at the beginning. Exit the room, and you will meet your -mom. She tells you that your Pokegear is back from the repair shop. You get the -Pokegear. Pokegear is used to call people on a phone and also see the day and -time of the day. Then, when she asks you to input the day of the week, put that -in. You can even put in Daylight Savings Time. How cool! Remember, that's the -cycle from April to October. The other half of the year is not. The Phone is -part of the Pokegear, so simply go there and phone whoever. Kinda cool, -actually. She'll want you to call from time to time. - -Leave your house and head left to Professor Elm's lab. Notice the guy standing -left of it in that little corner. That will be your rival. If you talk to him, -he'll push you out of the way. So rude. Anyway, go into Elm's lab. He'll want -you to do him a favor by raising up a Pokémon. Win-win situation, no? He rants -on about Mr. Pokémon's real discovery which wouldn't make any sense yet. You get -to pick your starter Pokémon. Which Pokémon should you take? - -|------------| -|THE STARTERS| -|------------| - -As I mentioned before, your starter Pokémon choice really matters. You can't get -the other two starters short of restarting your game save, getting a second game -(like the other version), or trading someone else for one. Which Pokémon should -YOU choose? Let's analyze all three starters. Thard_Verad notes that the -evolution levels vary in this game: - -"Chikorita -16-> Bayleef -32-> Meganium -Cyndaquil -14-> Quilava -36-> Typhlosion -Totodile -18-> Croconaw -30-> Feraligatr - -As you can see, Cyndaquil is first to evolve into its intermediate form, but -slowest to reach its final form. Totodile is the other way -around, and Chikorita is in the middle." - -Here are additional thoughts below on each type. - -CHIKORITA: Grass Type. - -Chikorita is the hardest starter to start with and will struggle against the -first two gyms. Chikorita will also struggle a bit against the fourthgym. And -Team Rocket likes using poison types, which have a type advantage against grass -types. Expert trainers should use Chikorita if they want a challenge. - -Moves to Keep: - -Tackle (35 PWR) => Body Slam (85 PWR) -Growl => Poison Powder => HM04 Strength (80 PWR) -Razor Leaf (55 PWR) => Solar Beam (120 PWR) -Reflect => Synthesis - -CYNDAQUIL: Fire Type. - -Cyndaquil evolves to its second form faster than the other two starters and has -a powerful Flamethrower move in its final form. Cyndaquil is the best starter -for the first two gyms, but slows down a bit after that because Cyndaquil only -really has fire and normal type moves. - -Moves to Keep: - -Tackle (35 PWR) => Quick Attack (40 PWR) -Leer => Swift (60 PWR) => HM04 Strength (80 PWR) -SmokeScreen => Flamethrower (95 PWR) -Ember (40 PWR) => Flame Wheel (60 PWR) - -TOTODILE: Water Type. - -Totodile is a great choice for speedrunning. When evolved, it can make use of -two powerful HMs: Surf AND Strength. Totodile also eventually gets Bite, a dark -type move, for balance. - -Moves to Keep: - -Scratch (40 PWR) => Slash (70 PWR) => Hydro Pump (120 PWR) -Leer => Bite (60 PWR) -Rage (20 PWR) => HM04 Strength (80 PWR) -Water Gun (40 PWR) => HM03 Surf (95 PWR) - diff --git a/docs/gold-walkthrough/section-01-cherrygrove-city-and-routes-29-31.txt b/docs/gold-walkthrough/section-01-cherrygrove-city-and-routes-29-31.txt deleted file mode 100644 index ac5d4fe8..00000000 --- a/docs/gold-walkthrough/section-01-cherrygrove-city-and-routes-29-31.txt +++ /dev/null @@ -1,369 +0,0 @@ -============================================================================== -============================================================================== -============================================================================== - -(c07) ------------------------------------------------ ----- 7 > Cherrygrove City and Routes 29-31 ---- ------------------------------------------------ - -Video Playthrough Link (Introduction through Violet City Gym): -https://www.youtube.com/watch?v=dqJtkx8DcE0 - -After you get your starting Pokémon, He says that Mr. Pokémon lives somewhere -past Cherrygrove, the next city. Then you get Elm's phone number. He'll call you -a few times over the course of the game. And just like in the Red, Blue, and -Yellow games, the professor's assistant gives you something. In this case, it's -a free potion. Potions can be used to heal your Pokémon up to 20 HP. Now leave -the lab! - -|-------------| -|New Bark Town| -|-------------| - -After choosing your Pokémon carefully, it seems your stupid rival's still -outside the lab. Head west onto Route 29, where your first battles with wild -Pokémon will begin. - -|--------| -|Route 29| -|--------| - -Pokémon found on Route 29: - -#016 Pidgey -#019 Rattata -#161 Sentret -#163 Hoothoot -#187 Hoppip - -Items found on Route 29: - -Berry -Pink Bow -Potion - -|-----------|-----------|--------|------------------| -| Chikorita | Cyndaquil |Totodile| THE | -|-----------|-----------|--------| STARTING | -| Tackle | Tackle | Scratch| Pokémon | -| Growl | Leer | Leer | TYPES | -|-----------|-----------|--------|------------------| -|Move Type PP Power Description | -|---------------------------------------------------| -|Tackle Normal 35 35 A full-body charge attack. | -|Scratch Normal 35 40 Scratches with sharp claw. | -|Growl Normal 40 -- Reduces the foe's attack. | -|Leer Normal 30 -- Reduces the foe's defense. | -|---------------------------------------------------| - -Here you will fight all different varieties of Pokémon, depending what time of -day it is, or what day it is. Note that the battle system looks all cool now -because it looks far more obvious how much HP you have, and how much EXP you -need your Pokémon to get before they advance to the next level. The EXP meter is -below the HP meter. Also note the new gender differences in Pokémon. - -In any case, you'll want your starting Pokémon to be at Level 8 by the time you -reach Cherrygrove City, so keep training. If you're new to the Pokémon world, -you might also want to talk to the trainers on the path, as they give good -advice. When you're on Route 29, to the north is Route 46. You can go there and -visit if you'd like, but it'll be a pain to defeat any Pokémon there because you -only have normal moves. The berry is in the tree in the northwest, the potion is -in the northeast. - -Thard_Verad adds, "Berry and Apricorn trees regenerate every day so don't be -afraid to pick them. And go back tomorrow and pick them again!" - -Remember to have your starting Pokémon at Level 8 before you pass through -Cherrygrove City. You really need the experience. Keep heading west until you -arrive in... - -|----------------| -|Cherrygrove City| -|----------------| - -Items found in Cherrygrove City: - -Map Card - -See that man at the entrance to the city? Talk to him and he will show you -around the city and some key Pokémon concepts, as well as places, like the -Pokémon Center, the PoKeMart, the routes, the sea, and his house. Most -importantly, you get the Map Card when he gives you it at the end. Then go into -the Pokémon Center, and heal your starting Pokémon by talking to the nurse. -Don't bother trying to get Pokeballs because they aren't being sold just yet. - -Head west then north up Route 30 when you're at Level 8. If you're not, go back -to Route 29, fight till your starter is about to faint, heal again at the -Pokémon Center in Cherrygrove City , and then head up Route 30, since you can't -buy anything from the PoKeMart yet. - -|--------| -|Route 30| -|--------| - -Pokémon found on Route 30: - -#010 Caterpie -#011 Metapod -#016 Pidgey -#019 Rattata -#165 Ledyba (Silver only, morning only) - -Items found on Route 30: - -Antidote -Berry (2X) -Mystery Egg -PoKeDeX -PSNCure Berry - -Welcome to Route 30! Reading the sign, you'll notice this is the midpoint route -between Cherrygrove City and Violet City. On this route, your starter Pokémon -should reach Level 10 by the time you get back to Cherrygrove City. Go a bit up -from the sign and you'll find a house with a tree next to it. Grab the berry -next to it and then enter the house for another berry. - -Don't actually use them, though. Potions are better most of the time. Just above -the house is an Antidote. Take that and head up the right fork. On the left -fork, you'd see a guy that will tell you that he trains his Pokémon, as well as -two boys battling on the path. You can't pass that area just yet. Going up the -right path, you'll eventually spot a house. To the left is a tree containing a -PSNCure Berry. Take that, then enter the house. - -You'll find Mr. Pokémon and Professor Oak inside. Mr. Pokémon gives you the -Mystery Egg, which Professor Elm wants to see. Apparently, the Daycare Service -gave Mr. Pokémon that Mystery Egg. This is the reason you must go back to -Professor Elm later, because he knows the evolutionary stuff even better than -Professor Oak! Wow, where'd he come from? Professor Oak wants you to help him -track down more Pokémon, and gives you the Pokedex. How dependable you are! It's -basically an encyclopedia, dictionary, and recorder all in one, if that makes -any sense. Professor Oak has to go for a radio show in Goldenrod, but Mr. -Pokémon will heal your Pokémon before you leave. - -Right when you leave the door, Professor Elm will give you a call on your cell -phone. He tells you that something terrible has happened in the lab. Now that -you're Professor Elm's 911 phone contact, you'll want to quickly head back to -New Bark Town. Consider training up your Pokémon to Level 10 before you even -exit Cherrygrove City onto Route 29. Keep training and heal at Cherrygrove's -Pokémon Center if you need to first. - -|----------------| -|Cherrygrove City| -|----------------| - -Heal at the Pokémon Center. Do not forget this. After your Level 10 Pokémon is -prepared for combat, exit Cherrygrove City to the east. Something stops you. You -notice that it's your rival. He is currently named ???. I've never met anyone -with that name before. I wonder who named him that? He calls you a wimp, but I -think he's the wimp around here. Your Level 10 Starter should completely destroy -his Level 5 one. XD - -|------------------------| -|Rival | -|Level 5 Starter (84 EXP)| -|You get: 300G | -|------------------------| - -Well, that was an easy battle. You probably lost less than 10HP. Now that you've -beaten him, he gets past you and now you can exit to Route 29 again. No way -he'll be the world's best Pokémon trainer. - -Moving forward, you will want to be cognizant of starter type weaknesses. Think -of it like paper-rock-scissors. Water beats fire. Fire beats grass. And grass -beats water. Your rival will always have the starter that can use super -effective moves against your starter. Keep that in mind as you play through the -game. - -|--------| -|Route 29| -|--------| - -Pokémon found on Route 29: - -#016 Pidgey -#019 Rattata -#161 Sentret -#163 Hoothoot -#187 Hoppip - -Keep heading east to New Bark Town. Keep training, too. Your starter should be -Level 11 by the time you reach New Bark, go back through Route 29, and back to -Cherrygrove. - -|-------------| -|New Bark Town| -|-------------| - -Go back to Professor Elm's laboratory and talk with the police officer in his -lab. The police officer says he has talked to Professor Elm, and when you note -that you've seen this person, you get to name your rival, the one who you just -battled and had the Pokémon stolen from Professor Elm. Name him whatever name -you feel like naming him. That's supposed to help the investigation, apparently. -He goes by the name Silver in the Pokémon community, though. - -Talk to Professor Elm and give him the discovery from Mr. Pokémon, the Mystery -Egg. Professor Elm is very surprised at you receiving a PoKeDeX. Only the -trainers with the best potential get one, after all. You're amazing already! -Professor Elm informs you that he thinks you have what it takes to try to get -the eight badges for the Pokémon Johto League. - -When you try to leave the lab, Professor Elm's aide will come up to you again -and give you five Pokeballs to begin your journey. Excellent. Now go right to -your home and talk to your mom, who will save your money for you. Now your -journey truly begins. Head west back onto Route 29. - -|--------| -|Route 29| -|--------| - -Pokémon found on Route 29: - -#016 Pidgey -#019 Rattata -#161 Sentret -#163 Hoothoot -#187 Hoppip - -Start heading west back to Cherrygrove City again. Before you can even go -anywhere, the first trainer will come up to you and teach you how to catch -Pokémon, so if you need the tutorial, by all means let him teach you. It's a -demonstration of him catching a Level 5 Rattata with a Pokeball. Nothing -special, really. Catch both a Hoothoot and Rattata on this route or Route 46. -Rattata's Super Fang can be a useful move early on. And Hoothoot can use the -ater HM Fly. - -In the Items pocket, note that there are now 4 sections. The 2nd one contains -the Pokeballs. Before you even fight a Pidgey, reach Level 11. Then, you are -able to do more damage and keep the Level 3 Pidgey low on HP. Capture it. Don't -go to Cherrygrove City yet. First, make a visit north onto Route 46. - -|--------| -|Route 46| -|--------| - -I know it doesn't look like much, but here there's a better chance of finding a -Rattata. Don't use your Level 11 Starter to catch it, you'll just kill it in a -hit. Instead, use Pidgey to weaken it, then capture it. An advanced strategy -would be to keep leveling up your starter Pokémon and have an imbalanced Pokémon -lineup. However, if you want to use a more balanced lineup, this could be your -2nd Pokémon that you use in your game playthrough, as its super fang is super -useful. Weaken it to the point where Pidgey is also weakened, then capture it. - -Head back onto Route 29, then west to Cherrygrove City. - -|----------------| -|Cherrygrove City| -|----------------| - -Heal at the Pokémon Center, then head north onto Route 30! If you want a more -balanced Pokémon lineup where you level up more than one Pokémon, move that -Pokémon to the #1 spot in your lineup. That way, Rattata will appear in battle -first. You'll want to switch to your starter on turn 1, and then your starter -will defeat the other Pokémon. Then both your Pokémon get equal experience and -the Pokémon you are leveling up will grow quickly. Do this even in trainer -battles. - -|--------| -|Route 30| -|--------| - -Pokémon found on Route 30: -#010 Caterpie -#011 Metapod -#013 Weedle -#014 Kakuna -#016 Pidgey -#042 Zubat -#06? Poliwag -#163 Hoot Hoot -#165 Ledyba (Silver only, morning only) -#167 Spinarak (Gold only, night only) - -OK, this time, you'll want to steer away from the right path but rather to the -left path. On the left path, you'll meet some resistance. The two boys that were -fighting earlier will both challenge you to battles, since they've already -battled each other. - -Youngster Joey -Level 4 Rattata (48 EXP) -You get: 64G - -Note that now some of your money goes to your mom for safekeeping now. Move up -to the next trainer for the next battle! - -Youngster Mikey -Level 2 Rattata (24 EXP) -Level 4 Rattata (48 EXP) -You get: 64G - -Go pass the lady to the third trainer to fight! - -Bug Catcher Don -Level 3 Caterpie (33 EXP) -Level 3 Caterpie (33 EXP) -You get: 48G - -Advance north onto Route 31. - -|--------| -|Route 31| -|--------| - -Pokémon found on Route 31: - -#010 Caterpie -#011 Metapod -#013 Weedle -#014 Kakuna -#016 Pidgey -#042 Zubat -#06? Poliwag -#069 Bellsprout -#163 Hoot Hoot -#165 Ledyba (Silver only, morning only) -#167 Spinarak (Gold only, night only) - -Items found on Route 31: - -Antidote -Bitter Berry -Pokeball -Potion - -You'll find the entrance to Dark Cave at the beginning of Route 31, as well as a -Potion. The Dark Cave won't really do you any good till you have the HM Flash, -so wait it out and grab that Potion to the left of it. When you see two -trainers, head south to where the item is and fight! - -Bug Catcher Wade -Level 2 Caterpie (20 EXP) -Level 2 Caterpie (20 EXP) -Level 3 Weedle (33 EXP) -Level 2 Caterpie (20 EXP) -You get: 32G - -I'm not really clear on how these Bug Catchers are catching the bugs. Are they -using their nets, or Pokeballs? Grab the Pokeball below the trainer, head back -up to grab the Bitter Berry from the tree, then head west, through the house-ish -entry, and into Violet City. - -|-----------| -|Violet City| -|-----------| - -Pokémon found in Violet City: - -#95 Onix - -Items found in Violet City: - -PRZCure Berry -TM 31 - -Heal your Pokémon. There is a GYM there in Violet City that uses Flying type -Pokémon. Don't go there just yet, head over far north to a tower called the -Sprout Tower. The experience you get up there will be very useful for the gym. - -(c08) ------------------------------------------------ diff --git a/docs/gold-walkthrough/section-02-sprout-tower-and-violet-city-gym.txt b/docs/gold-walkthrough/section-02-sprout-tower-and-violet-city-gym.txt deleted file mode 100644 index 1e573d23..00000000 --- a/docs/gold-walkthrough/section-02-sprout-tower-and-violet-city-gym.txt +++ /dev/null @@ -1,150 +0,0 @@ ----- 8 > Sprout Tower and Violet City Gym ---- ------------------------------------------------ - -Video Playthrough Link (Introduction through Violet City Gym): -https://www.youtube.com/watch?v=dqJtkx8DcE0 - -|------------| -|Sprout Tower| -|------------| - -Pokémon found in Sprout Tower: - -#019 Rattata -#069 Bellsprout -#092 Gastly - -Items found in Sprout Tower: - -Escape Rope -HM05 -Parlyz Heal -Potion -X Accuracy - -This tower is called Sprout Tower, and it's obviously named that because the -sages inside mostly use Bellsprouts. I wonder what rituals the sages go through -with those Bellsprouts. It should give you plenty of experience. Head up the -stairs to Floor 2. Continue up the path and you'll see Sage Nico, and he'll -challenge you to a battle. - -Sage Nico -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -You get: 96G - -Continue along the path and down the stairs. Grab the item below you. It is -Parlyz Heal. Head back up and left along the path to fight Sage Chow. - -Sage Chow -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -You get: 96G - -Go up the stairs and along the path till you see YET another sage, after you -grab that X Defense above you. - -Sage Edmond -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -Level 3 Bellsprout (54 EXP) -You get: 96G - -Climb up the stairs, grab the item near you (a Potion), then fight every sage up -in turn. - -Sage Jin -Level 6 Bellsprout (108 EXP) -You get: 192G - -Next up is another sage. Use your starter for backing. - -Sage Neal -Level 6 Bellsprout (108 EXP) -You get: 192G - -Move up to the third sage on the floor and fight him. - -Sage Troy -Level 7 Bellsprout (126 EXP) -Level 7 Hoothoot (106 EXP) -You get: 224G - -When you move up, you'll see some sort of shaking up above you. Your rival is -there, after beating the main sage of the tower. He has been given the HM Flash -but the sage criticizes him for treating his Pokémon too harshly and he gets -enraged. He escapes with the Escape Rope. Go up to the item to the right of the -monk. In reality, it is a... - -Level 3 Rattata (24 EXP) - -Now move up to take on the leader of the sages itself. The final battle of the -Sprout Tower challenge commences. - -Sage Li -Level 7 Bellsprout (126 EXP) -Level 10 Hoothoot (122 EXP) -Level 7 Bellsprout (126 EXP) -You get: 320G - -For beating the main guy of the tower, you get your reward, the Flash HM (HM05). -Flash is a flashlight, it's not something else. Get your mind out of the gutter. -You'll need the 1st badge to use it, however. Go back to the Rattata you faced -before, and now it's an Escape Rope. You may use that to get out of the tower or -by climbing down it. Get out of the tower and go back to the Pokémon Center of -Violet City. - -|-----------| -|Violet City| -|-----------| - -Well, you should be around an hour into the game now. Now, you'll want to head -northwest in the city to the Violet City Gym. After all that training, things -are gonna look awfully ridiculously easy in here, so you'll know you're doing -the right thing. And best of all, you didn't even need to run on a treadmill at -the school gym. Head up to fight your trainer battles first. - -Bird Keeper Abe -Level 9 Spearow (111 EXP) -You get: 216G - -Head up the path to the second trainer. - -Bird Keeper Rod -Level 7 Pidgey (82 EXP) -Level 7 Pidgey (82 EXP) -You get: 168G - -Go back to the Pokémon Center to heal, then you're ready to battle, so head up -the zig-zag pathway to Falkner. Falkner is ready to show the magnificent power -of bird Pokémon. I'm not convinced. If you chose Cyndaquil or Totodile, this -should be pretty easy. If you have Chikorita, I would suggest evolving it first -or using a non-grass Pokémon (like a Rattata) take this gym. - ---- -GYM LEADER FALKNER -Level 7 Pidgey (82 EXP) -Level 9 Pidgeotto (217 EXP) -You get: Zephyr Badge, $900, & TM31 (Mud Slap) ---- - -Strategy VERSUS Pidgey: Use your highest-powered move from your starter Pokémon, -or consider using Rattata's quick attacks if you have Chikorita and haven't -evolved it yet. - -Strategy VERSUS Pidgeotto: Use your highest-powered move from your starter -Pokémon, or consider using Rattata's quick attacks if you have Chikorita and -haven't evolved it yet. Pidgeotto will have a Gust attack that does a bit more -damage. - -The Zephyr Badge gives your Pokémon a slight attack boost. That's cool and you -have one badge now. - -============================================================================== -============================================================================== -============================================================================== - -(c09) ------------------------------------------- diff --git a/docs/gold-walkthrough/section-03-ruins-of-alph-and-union-cave.txt b/docs/gold-walkthrough/section-03-ruins-of-alph-and-union-cave.txt deleted file mode 100644 index 26208627..00000000 --- a/docs/gold-walkthrough/section-03-ruins-of-alph-and-union-cave.txt +++ /dev/null @@ -1,312 +0,0 @@ ----- 9 > Ruins of Alph and Union Cave ---- ------------------------------------------- - -Video Playthrough Link (Violet City, Ruins Alph, Route 32, Union Cave, to Azalea -Town): https://www.youtube.com/watch?v=7v-n0Z0CQsg - -Video Playthrough Link (Slowpoke Well, Azalea Town Gym, Ilex Forest, Goldenrod -City Gym): https://www.youtube.com/watch?v=LSEriFoAxC0 - -|-----------| -|Violet City| -|-----------| - -Right after you leave the gym, you should receive a phone call from Professor -Elm. He tells you to pick up something important at the Pokémon Center from his -aide. Apparently you are now doing volunteer work for the Professor for no extra -pay. When you get to the Pokémon Center, you will see Professor Elm's aide, the -guy by the counter. He will give you a Pokémon egg. The egg will hatch in about -1,000 steps. Leave the Violet City Gym and go to the Pokémon Center. It is -Togepi in egg form. There is one way to not keep Togepi on your team while it's -in egg form. Just put it in one of the Pokémon boxes (while it's an egg form) -and keep it there. - -Heal, buy at least 10 Pokeballs, then head west from the Violet City Gym. -Continue on the path north and then west until you enter Route 36. - -|--------| -|Route 36| -|--------| - -Talking to the two people on the route, you'll find out that a supposed tree is -blocking the way. Ignore that for now and head south past the house to the Ruins -of Alph. - -|-------------| -|Ruins of Alph| -|-------------| - -Pokémon found in or outside the Ruins of Alph: - -#177 Natu -#194 Wooper (Surfing) -#201 Unown -#235 Smeargle - -Item found in the Ruins of Alph: - -Unown Pokedex - -After you go through the route-changing station, enter the first cave on the -right that you see. Talk to the two people inside, go up to the left panel, and -then solve the Kabuto puzzle. The corner pieces should be obvious, so start from -there and work to the middle. It will look circular, but the eyes point -out...remember that. You are then dropped to the next floor, and you will find -yourself in another cave you could enter on the left side. - -This will allow you to catch a new Pokémon, #201 Unown, in its 28 forms. The -forms include all 26 letters of the English alphabet, plus two punctuation marks -(? and !). The lower passageway eventually leads up to a ladder that you can -climb up. But you might have fun catching some of the Unown alphabet while -you're here. - -Thanks to Thard_Verad for correcting the typing for Unown, who also comments: -"It's also a terrible addition to one's party. Bad stats and can only learn -Hidden Power." - -If you catch at least three different Unown letters in the Ruins of Alph when -you exit the cave at the bottom with the ladder, a professor assistant will -greet you and give you an Unown Pokedex add-on. - -For when you later have access to radio stations, Thard_Verad comments, "There's -a mysterious radio signal in the Ruins of Alph. It's on frequency 213.5 if you -want to listen." - -There's another puzzle you can do here once you have HM03 Surf. When you do, -surf across the narrow lake south of where you were (where you can catch a -Wooper, by the way) to the other side. Enter that part of the Ruins of Alph and -you can solve an Aerodactyl puzzle. It'll be easier to the corner and side -pieces first because it will come with that white background. - -After exiting the cave, go south, and then east. You will come up to another -route changing house. Welcome to Route 32. - -|--------| -|Route 32| -|--------| - -Pokémon found in Route 32: - -#019 Rattata -#023 Ekans -#041 Zubat -#069 Bellsprout -#179 Mareep -#187 Hoppip -#194 Wooper - -Items found in Route 32: - -Great Ball -Miracle Seed -Poison Barb -Potion -Old Rod -Repel -TM05 - -After exiting the house, go a little east and then up into Violet City to grab -that PRZCure Berry, then head back south onto Route 32. Talk to the first guy -you see and you will get Miracle Seed. Grass moves will then be increased in -power by 20%. Keep heading south and getting easy experience points fighting -silly Pokémon like Ekans. - -Depending on what starter you have, you may want to catch both a Bellsprout and -a Wooper (starting with Cyndaquil), just Wooper (starter being Chikorita), or -just Bellsprout (starter being Totodile), for the HMs Cut and Surf later on. Use -quick attack from a non-starter Pokémon to a yellow or HP range, then catch -those Pokémon. Wooper will be a bit harder to find than Bellsprout. Then keep -going down till you see the first trainer. - -Youngster Albert -Level 6 Rattata (72 EXP) -Level 8 Zubat (91 EXP) -You get: 128G - -Now there's a fork. Take the left path and get the Repel, then head back to the -right to talk to the girl who will fight you while being on the phone. So -talkie. - -Picnicker Liz -Level 9 Nidoran (F) (110 EXP) -You get: 180G - -Talk to her again and you'll even get her phone number and vice versa. Anyway, -head down and take the grassy eastern route along the lake. Go down and talk to -the first fisherman you reach for a nice battle. Easy battle versus Magikarps -who can barely splash, except for the Level 15 one. - -Fisher Justin -Level 5 Magikarp (21 EXP) -Level 5 Magikarp (21 EXP) -Level 15 Magikarp (63 EXP) -Level 5 Magikarp (21 EXP) -You get: 200G - -Talk to the fisherman to the left of Justin and you'll have an actual fight. - -Fisher Henry -Level 8 Poliwag (128 EXP) -Level 8 Poliwag (132 EXP) -You get: 320G - -Keep going south till that next fisherman spots you. - -Fisher Ralph -Level 10 Goldeen (237 EXP) -You get: 400G - -Exhausted with all the battles now? Take your trip back north to that PRZCure -Berry you saw earlier, after your Mom gives you that call. She apparently buys -something with your money. You can check that at the Pokémon Center. - -Thard_Verad adds, "Mom will buy something once your savings reach certain -thresholds. The items are pretty forgettable, but the dolls (AFAIK) are unique -to her shopping trips." - -Anyway, heal, withdraw the item (a Super Potion), then come back down the same -route, but take the LEFT path instead and immediately you see a trainer that -wants to fight you. Oh no! - -Camper Roland -Level 9 Nidoran (M) (115 EXP) -You get: 180G - -Go south then back up to grab the Great Ball, then head south once more to fight -the trainer below you. - -Youngster Gordon -Level 10 Wooper (111 EXP) -You get: 160G - -Go back up the right/fishing route to talk to Fisher Ralph to exchange phone -numbers. Keep going down the route and someone wants a million dollars for a -Slowpoke tail. Go up to the girl and she'll give you a Poison Barb. Now heal at -that Pokémon Center in the middle of nowhere. Some rando fisher will give you an -Old Rod. Keep heading south and there's yet another trainer. Finally a trainer -that knows you have one badge! - -Bird Keeper Peter -Level 6 Pidgey (70 EXP) -Level 6 Pidgey (70 EXP) -Level 8 Spearow (99 EXP) -To get: 192G - -Well, that was easy. Now that you're at the Union Cave entrance, you should -enter Union Cave. - -|----------| -|Union Cave| -|----------| - -Pokémon in Union Cave: - -#019 Rattata -#041 Zubat -#074 Geodude - -Items in Union Cave: - -Potion -X Attack -TM39 Swift - -Now that you're finished with Route 32, it's time to bring the fighting into -Union Cave. There isn't that much to do in Union Cave, however. - -Your lineup (if you are balancing out a starter and Rattata) should now be: - -Level 3 Pidgey -Level 4 Wooper -Level 5 Mystery Egg -Level 7 Bellsprout -Level 15 Rattata -Level 15 Starter - -Keep heading along the path westward and you'll see a lot of silly, silly -Geodudes. Take the Great Ball, and go south to fight your first trainer battle -in Union Cave. - -Hiker Daniel -Level 11 Onix (252 EXP) -You get: 352G - -Head east to the next trainer, who will ask if you are truly ready for Azalea's -gym. Show that you are! - -Hiker Russell -Level 4 Geodude (82 EXP) -Level 6 Geodude (109 EXP) -Level 8 Geodude (147 EXP) -You get: 256G - -Firebreather Bill -Level 6 Koffing (145 EXP) -Level 6 Koffing (145 EXP) -You get: 288G - -Grab the X Attack below Bill, then head back up to Bill and go left. Don't miss -the item to the far left. It's a Potion. Go south and then up the ledge. - -PoKeManiac Larry -Level 10 Slowpoke (211 EXP) -You get: 600G - -Now go up and down the ladder. Head left to get TM39 Swift, then back east past -the ledge you went down, to get the item X Defend. Head back thus the way you -came, head down the ledge, and grab the Awakening Item to the southeast. Then go -east to fight the last trainer. - -Firebreather Ray -Level 9 Vulpix (121 EXP) -You get: 432G - -Afterwards, you can go right and down and out the door to Route 33. - -Note that when you have obtained HMs Strength and Surf you should return back to -Union Cave and explore the lower levels. If you do you will find a Lapras, who -only appears on Friday, plus the entrance to more of the Ruins of Alph. - -|--------| -|Route 33| -|--------| - -Pokémon found in Route 33: - -#019 Rattata -#021 Spearow -#023 Ekans -#041 Zubat -#187 Hoppip - -Items found in Route 33: - -PSNCure Berry - -After exiting the Union Cave, go a little down to grab the PSNCure Berry from -the tree, then head west. You'll meet just one trainer in the way: Hiker -Anthony. He says he's got plenty of energy left to battle you. - -Hiker Anthony -Level 11 Geodude (202 EXP) -Level 11 Machop (207 EXP) -You get: 352G - -Now that he is defeated, head west into Azalea Town. - -If you chose to switch leveling up your starter and Rattata, your lineup should -be around: - -Level 17 Starter -Level 16 Rattata -Level 8 Bellsprout -Level 5 Mystery Egg -Level 4 Wooper -Level 3 Pidgey - -============================================================================== -============================================================================== -============================================================================== - -(c10) ----------------------------------------------- diff --git a/docs/gold-walkthrough/section-04-slowpoke-well-azalea-town-gym.txt b/docs/gold-walkthrough/section-04-slowpoke-well-azalea-town-gym.txt deleted file mode 100644 index 54dd1d60..00000000 --- a/docs/gold-walkthrough/section-04-slowpoke-well-azalea-town-gym.txt +++ /dev/null @@ -1,155 +0,0 @@ ----- 10 > Slowpoke Well & Azalea Town Gym ---- ----------------------------------------------- - -Video Playthrough Link (Slowpoke Well, Azalea Town Gym, Ilex Forest, Goldenrod -City Gym): https://www.youtube.com/watch?v=LSEriFoAxC0 - -|-----------| -|Azalea Town| -|-----------| - -Items found in Azalea Town: - -Lure Ball -TM49 -Wht Apricorn - -When you enter the town, you should notice that one Team Rocket member is -guarding a well-like structure. Ignore this for now, but remember where it is. -At about this time, you might get a call from Liz the Picnicker and she tells -you that Moo Moo Milk can be bought at Moo Moo Farm for good health and beauty. -Anyway, keep rolling up to the northernmost house in town by heading west then -north in the city. He is Kurt and he informs you about the disbanding and return -of Team Rocket. Kurt goes to "give them a lesson in pain". Anyway, go back to -the well-like structure after you heal at the Pokémon Center and you'll see that -the Team Rocket guard has gone, or more like, demolished by Kurt. Now enter the -Slowpoke Well! - -|-------------| -|Slowpoke Well| -|-------------| - -You'll notice Kurt took a tumble down the well, so it's up to you now. Head up -to start the war on Team Rocket and its members, especially the guy Kurt had a -shouting match with. - -Rocket Grunt (M) -Level 9 Rattata (109 EXP) -Level 9 Rattata (109 EXP) -You get: 360G - -Talk to him again and you'll learn that they're chopping off Slowpoke tails for -cash. Move on up the stairs for Battle #2. - -Rocket Grunt (F) -Level 9 Zubat (103 EXP) -Level 11 Ekans (145 EXP) -You get: 440G - -Move left to grab the super potion to the left of her, then head down the stairs -into another battle. - -Rocket Grunt (M) -Level 7 Rattata (85 EXP) -Level 9 Zubat (103 EXP) -Level 9 Zubat (103 EXP) -You get: 360G - -Talk to the Slowpoke above you. It had its tail cut off. Poor thing, and there's -mail on it? Anyway, move left to face the fourth Team Rocket member. - -Rocket Grunt (M) -Level 14 Koffing (342 EXP) -You get: 560G - -He, the leader of this band of Team Rocket members, tells you that they were -working underground for the past three years. Looks true, doesn't it? Team -Rocket leaves and Kurt talks to you. He returns you to his house in Azalea Town. - -|-----------| -|Azalea Town| -|-----------| - -Items found in Azalea Town: - -Lure Ball -TM49 -Wht Apricorn - -Talk to Kurt again and he'll give you a Lure Ball for free. Whee! Go right above -his house and grab the Wht Apricorn, then heal at the Pokémon Center. - -Thard_Verad comments, "Kurt only accepts ONE Apricorn at a time. Unfortunately -you can't give him multiples of the same kind." - -After you heal, head to the Azalea Town Gym, and the home of the Bug types. By -now, you're 2 to 2 1/2 hours into the game. Anyway, now's the time to enter the -gym and face Bugsy and her apprentices. Rattata will be a MAJOR help here. Go up -and Amy (the left girl) and May (the right girl) will challenge you. Remember, -Quilava is extra-effective here if you have him. Otherwise, Rattata will still -stun through this gym. - -Twins Amy & May -Level 10 Spinarak (115 EXP) -Level 10 Ledyba (115 EXP) -You get: 200G - -Head up the left side to meet your next opponent. Someone knows you saved the -Slowpokes. - -Bug Catcher Josh -Level 13 Paras (195 EXP) -You get: 208G - -Continue around the circular center to the top center person. Good luck! - -Bug Catcher Benny -Level 7 Weedle (78 EXP) -Level 9 Kakuna (136 EXP) -Level 12 Beedrill (408 EXP) -You get: 192G - -Continue around the circle, clockwise, to the final one of the five trainers -lying around the gym leader's circle. Time to beat Al! - -Bug Catcher Al -Level 12 Caterpie (135 EXP) -Level 12 Weedle (133 EXP) -You get: 200G - -Depending on whether Rattata and have your starter have the HP, you can either -go back and heal or just go straight to fighting Gym Leader Bugsy. In any case, -put Rattata on the front line, then your starter as backup. Yay for Bugsy. She -may have the Bug Pokémon, but you have the counters! - ---- -GYM LEADER BUGSY -Level 14 Metapod (215 EXP) -Level 14 Kakuna (213 EXP) -Level 16 Scyther (640 EXP) -You get: Hive Badge, 1,600G, & TM49 (Fury Cutter) ---- - -Strategy VERSUS Metapod: Use the highest-powered attack on your highest level -Pokémon. Metapod's easy. If you have Cyndaquil, use Ember, as fire type moves -are super effective against bug types. - -Strategy VERSUS Kakuna: Use the highest-powered attack on your highest level -Pokémon. But watch out for Kakuna's poison sting. You might want to use an -Antidote. If you have Cyndaquil, use Ember, as fire type moves are super -effective against bug types. - -Strategy VERSUS Scyther: Use the highest-powered attack on your highest level -Pokémon. If you have Cyndaquil, use Ember, as fire type moves are super -effective against bug types. Scyther may take multiple hits to take down. - -So now, you have the second badge which lets all Pokémon up to Level 30 obey you -regardless of whatever, and you can use the HM01 Cut. You also get TM49, Fury -Cutter. This lets Pokémon have stronger attacks over time. Whee. - -============================================================================== -============================================================================== -============================================================================== - -(c11) -------------------------------------------------- diff --git a/docs/gold-walkthrough/section-05-ilex-forest-and-goldenrod-city-gym.txt b/docs/gold-walkthrough/section-05-ilex-forest-and-goldenrod-city-gym.txt deleted file mode 100644 index e52dae31..00000000 --- a/docs/gold-walkthrough/section-05-ilex-forest-and-goldenrod-city-gym.txt +++ /dev/null @@ -1,395 +0,0 @@ ----- 11 > Ilex Forest and Goldenrod City Gym ---- -------------------------------------------------- - -Video Playthrough Link (Slowpoke Well, Azalea Town Gym, Ilex Forest, Goldenrod -City Gym): https://www.youtube.com/watch?v=LSEriFoAxC0 - -|-----------| -|Azalea Town| -|-----------| - -Now leave the gym and heal your Pokémon at the Pokémon Center. Go west and try -to exit the town and you'll run into your rival. Yay for you! He doesn't believe -you beat Team Rocket, but let the battle begin. - -|--------------------------| -|Rival | -|Level 12 Gastly (240 EXP) | -|Level 16 Starter (489 EXP)| -|Level 14 Zubat (162 EXP) | -|You get: 960G | -|--------------------------| - -Pokémon VERSUS Gastly: Use the highest-powered attack on your highest level -Pokémon that is NOT a normal type move, as ghost types do not get damaged by -normal moves. You can't use normal moves with Gastly, but you can use grass, -fire, or water. - -Pokémon VERSUS Starter: Use the highest-powered attack that aren't the type of -your starter. - -Pokémon VERSUS Zubat: Use the highest-powered attack on your highest level -Pokémon. - -Well, at least he admits, "I only lost because my Pokémon were weak." He talks -tougher than his Pokémon really are. Uncle Ralph calls again about taking care -of Pokémon and taking out a Zubat. Anyway, heal again, then continue going west -into Ilex Forest! - -Defeat your rival and head the way you were going, west. You'll enter Ilex -Forest. - -|-----------| -|Ilex Forest| -|-----------| - -Pokémon found in Ilex Forest: - -#010 Caterpie -#011 Metapod -#013 Weedle -#041 Zubat -#043 Oddish -#046 Paras - -Items found in Ilex Forest: - -HM01 (Cut) -Revive -TM02 (Headbutt) -TM12 (Sweet Scent) - -Keep heading north up the path, then follow the path eastward. Talk to the first -guy you see. He is standing next to a bush that you can cut. He tells you that -his boss's Farfetch'd got away. He's sooo irresponsible. Keep following the path -and you'll find that scary Farfetch'd. XD - -Anyway, if you talk to the Farfetch'd, it'll run away. Far, far away. Keep on -following it and talking to it. Eventually, it'll turn back the opposite -direction so make sure you're "pushing" it back the way you came. Grab the -Revive you see while you're at it. It will then lead you to the man and his -boss. - -Anyway, talk to the boss and he will give you HM01 Cut! You'll want to equip Cut -on Bellsprout right away, and that is in the fourth section of your backpack. -Now use HM01 Cut on that bush to keep moving up the path. A new feature is that -you can just go up to the bush, press A, and it will get cut down if you have a -Pokémon with Cut in your lineup. You can do the same later with other HMs like -Strength (with boulders), Surf (with water), and Whirlpool (with, well, -whirlpools). - -Now you enter the Ilex Forest Shrine, and the Ilex Forest Maze. Keep heading -along the path and grab the X Attack you see, and then the Antidote you spot -later on the path going east. To the northeast is an Ether, but go right first. -Talk to the trainer and you'll get TM02 Headbutt, which allows you to headbutt -other people. Just kidding. It's for your Pokémon and I am a serious person. -Headbutt lets you shake the little trees that are everywhere in the game, -sometimes revealing sleeping Pokémon. Then, head back left from there and talk -to that other trainer for a battle! - -Bug Catcher Wayne -Level 8 Ledyba (122 EXP) -Level 10 Paras (150 EXP) -You get: 160G - -Keep going along the path and into the building to the north. Talk to the lady -behind the counter and you'll get TM12 Sweet Scent. Walk through and you'll get -on Route 34! - -|--------| -|Route 34| -|--------| - -Pokémon found in Route 34: - -#019 Rattata -#063 Abra -#096 Drowzee -#132 Ditto - -Items found in Route 34: - -Soft Sand - -Welcome to Route 34. Here, you'll fight even more trainers. Head right up and -you'll enter a match right away. - -Youngster Samuel -Level 7 Rattata (85 EXP) -Level 10 Sandshrew (198 EXP) -Level 8 Spearow (99 EXP) -Level 8 Spearow (99 EXP) -You get: 128G - -Keep heading up the path to face the guy who sent his Pokémon to the Daycare -Center! - -PoKeFan Brandon -Level 13 Snubbull (175 EXP) -You get: 1040G - -Continue along the path to face that Youngster, who says he's the best in his -class at Pokémon. - -Youngster Ian -Level 10 Mankey (157 EXP) -Level 12 Diglett (207 EXP) -You get: 192G - -Youngster Ian will want to trade phone numbers with you. Now head down through -some of the wild grass to battle Picnicker Gina. - -Picnicker Gina -Level 9 Hoppip (142 EXP) -Level 9 Hoppip (142 EXP) -Level 12 Bulbasaur (163 EXP) -You get: 240G - -Head up from Youngster Ian and you'll find this building to the right. Enter it -and talk. Thard_Verad notes correctly that only in the Pokémon Crystal Version -does the Daycare have a free egg and that "there's a PC in the corner of the -Daycare" if you need it. - -Inside the Daycare/Breeding Center with the old man and woman, you can talk to -the man and he will take one of your Pokémon to level up. Talking to the woman -will do the same thing. It will cost 100G to take them out, and 100G per level -up, but their stats won't be as good as if you trained them yourself, obviously. -To breed Pokémon you must give the old man a Male Pokémon and give the old woman -a Female Pokémon (or vice-versa) that have the same little icon or sprite. Or -they must be compatible at breeding. - -Then exit the building and go walk around for a little while. Come back when you -see the man standing outside with your two Pokémon. Talk to him and answer yes, -he will give you an Egg. If you put in Pokémon of different #'s, for example -Pidgey and Hoothoot, the resulting egg will be the same species as the female -Pokémon and with an Attack that the Male had. Just think of the strange -combinations. - -Head back out as you can't really do much here yet. If it's night time, you can -fight the officers. Thanks to Thard_Verad for pointing out that the Police -Officers will only battle you at night. - -Officer Keith -Level 17 Growlithe (331 EXP) -You get: 680G - -Move up to take on the final trainer on the route. - -Camper Todd -Level 14 Psyduck (240 EXP) -You get: 280G - -Great! Now, talk to the same guy again and you'll exchange phone numbers. He's -great. He'll call you later about his awesome looking Psyduck and that he beat -some tough Rattata. - -Then, let's go into Goldenrod City, the LARGEST city in the game, or something -like that. - -If you focused on just leveling up your starter, your lineup might look like -this: - -Level 25 Starter Evolution -Level 6 Bellsprout -Level 6 Wooper -Level 5 Rattata -Level 5 Unown Egg - -If you balanced leveling up your Starter and Raticate: - -Level 22 Starter Evolution -Level 20 Raticate -Level 8 Bellsprout -Level 5 Mystery Egg -Level 5 Pidgey -Level 4 Wooper - -|--------------| -|Goldenrod City| -|--------------| - -Pokémon found in Goldenrod City: - -#66 Machop (Through Trading) -#133 Eevee (Get from Bill) - -Pokémon/Items in Goldenrod's Game Corner: (Coins) - -Abra 200 TM 25 5500 -Ekans/Sandshrew 700 TM 14 5500 -Dratini 2100 TM 38 5500 - -Items found in Goldenrod City: - -Bicycle -Coin Case -HP Up -Radio Card -TM 21 -TM 27 -TM 45 -Squirtwater - -Goldenrod City has got to be the biggest city in the game. There's lots to do in -the city. Heal at the Pokémon Center, then cross the road to the right to the -Goldenrod City Department Store. There's a beautiful assortment of items on -every floor: - -Level 2: Potions, Heals, Great Balls, Escape Ropes, Repels -Level 3: X Items (temporary stat boosts) -Level 4: Stat increase items -Level 5: TM02, TM33, TM41, TM48 (includes Drownzee-Machop trade and Sunday -giveaway of TMs) -Level 6: Vending Machine heal items - -Thard_Verad adds, "Super Potions are a scam. As soon as you reach Goldenrod, you -can go to the top of the Dept. Store and replace all your Potions with Fresh -Water (same price, 2.5x stronger) and Super Potions with Lemonade (half price, -60% stronger) If only you could buy in bulk...." - -Additionally, Thard_Verad comments, "After you get TM08 Rock Smash, it will be -added to the Dept. Store's lineup." - -Next, you'll want to head to Bill's house. Go through the path just above the -Pokémon Center and it should be the first house you see. Talk to the girl that's -closer to the door and you'll get Bill's phone number. Calling it will tell you -how many Pokémon you have. - -After that, we'll go down under the city for some street fighting. Go to the -house below Bill's, and it turns out to be the entrance to the underground. Go -downstairs. Go up and you'll face a Game Corner cheater. - -Super Nerd Eric -Level 11 Grimer (211 EXP) -Level 11 Grimer (211 EXP) -You get: 352G - -Head up to face PoKeManiac Isaac. - -PoKeManiac Isaac -Level 12 Lickitung (325 EXP) -You get: 720 - -Grab the Coin Case above him. You'll need it for the Game Corner because -apparently, nothing else can carry their special coins. Pass by the three -strange shops, including a Pokémon Salon, and stop in front of the guy that's -there. - -Super Nerd Teru -Level 7 Magnemite (133 EXP) -Level 11 Voltorb (241 EXP) -Level 7 Magnemite (133 EXP) -Level 9 Magnemite (171 EXP) -You get: 288G - -Head up to face another trainer. - -PoKeManiac Donald -Level 10 Slowpoke (211 EXP) -Level 10 Slowpoke (211 EXP) -You get: 600G - -Head to the right and you'll see the door is locked, so come back and go up the -stairs above Donald. Come back out and go to the Pokémon Center to heal. - -Now head out of the gym and we'll hit the east side of town (below the train -tracks) for a Bike. Keep heading down that path till you hit a dead end and -there's a house. It's the Bike Shop. Talk to the manager inside and he tells you -that you can borrow a bike if you advertise for him. You can access the bike -from your backpack. This makes no sense - is it a foldable bike? Eh, whatever. -It's in the 3rd section of your backpack, so start using it to cruise through -areas. Once you get out, go to your bicycle and press Sel, which means the item -will be equipped for use on the Select Button. Now you can simply just press -Select and you'll be on your bike. - -To the west is a train station. The train conductor stands there and says no -train is coming, but they're still paying him to be there. Now, you'll want to -visit the Radio Tower in the western side of town. It is a black building just -south of the train tracks. Talk to the middle man for possible ID matches, then -talk to the woman on the right for the Radio Quiz. - -Question 1: Can the Town Map be displayed on a Pokegear? (YES) - -Question 2: Can Nidorina be female only? (YES) - -Question 3: Does Kurt, the Pokeball crafts-man, use Aprikorn? (NO) - -Question 4: Maikarp won't learn any TM move? (YES) - -Question 5: Prof. Oak's Pokémon Talk is a very popular program? Is Marie the -co-host of the show? (NO, it's Mary) - -Now you'll get the Radio Card for your Pokegear, and can listen to the radio -anytime. There are three radio channels, including Pokémon Music 7.5 with DJ Ben -and the 8.5 Lucky Channel. Thard_Verad notes correctly that the Buena meeting -and Blue Card are only available in the Pokémon Crystal Version. Head back out -now. - -Now, let's head for the gym for that 3rd badge, shall we? We'll go back to the -PoKeMart for supplies later. The gym is in the northeast part of town. This -gym's trainer all use Normal Pokémon. - -Enter the gym and head up to face Beauty Victoria. - -Beauty Victoria -Level 9 Sentret (109 EXP) -Level 13 Sentret (157 EXP) -Level 17 Sentret (207 EXP) -You get: 1496G - -Easy battle. Go around her and to the right side of the gym to face the other -Beauty now. - -Beauty Samantha -Level 16 Meowth (235 EXP) -Level 16 Meowth (235 EXP) -You get: 1408G - -Start heading left along the path towards the center, for another battle. - -Lass Carrie -Level 18 Snubbull (243 EXP) -You get: 432G - -Go up to fight the last trainer before the gym leader, Lass Bridget. She loves -to use Jigglypuff and has 3 of them, so expect to get hit by sleep spells. - -Lass Bridget -Level 15 Jigglypuff (243 EXP) -Level 15 Jigglypuff (243 EXP) -Level 15 Jigglypuff (243 EXP) -You get: 360G - -Now, me being me, I'd just go straight into the battle against the gym leader -without items or healing, but I'd recommend going back to the Pokémon Center to -heal before the battle. - ---- -GYM LEADER WHITNEY -Level 18 Clefairy (261 EXP) -Level 20 Miltank (856 EXP) -You get: Plain Badge, 2,000G, & TM45 (Attract) ---- - -Strategy VERSUS Clefairy: Use your highest powered attack from your starter. - -Strategy VERSUS Miltank: The most troublesome thing about facing a Miltank is -that it heals with milk and it has an attack that makes your Pokémon get woozy. -Your Pokémon are then unable to attack the Miltank for a few turns and Miltank -just slams you with Stomps and Rollouts. Use your highest powered attacks from -your highest level Pokémon. Flying type moves are also super effective against -Miltank, and rock Pokémon tend to absorb moves from Miltank better than the -rest. - -Whitney won't give you the Plain Badge at first because she will be upset that -you've been "so serious". Anyway, she refuses to give you your badge. The -trainer below you will talk to you. Then talk to Whitney again. She will now -give you the Plain Badge. It will let your Pokémon use Strength outside of -battle and it boosts your Pokémon's Speed. She'll also give you TM45 Attract. - -============================================================================== -============================================================================== -============================================================================== - -(c12) -------------------------------------------------- diff --git a/docs/gold-walkthrough/section-06-bug-catching-contest-and-sudowoodo.txt b/docs/gold-walkthrough/section-06-bug-catching-contest-and-sudowoodo.txt deleted file mode 100644 index 067b4ae0..00000000 --- a/docs/gold-walkthrough/section-06-bug-catching-contest-and-sudowoodo.txt +++ /dev/null @@ -1,292 +0,0 @@ ----- 12 > Bug Catching Contest and Sudowoodo ---- -------------------------------------------------- - -Video Playthrough Link (Goldenrod City, Routes 35-37, to Ecruteak City): -https://www.youtube.com/watch?v=x0E8PnSbA5o - -Head to the house just to the right of the gym. Talk to the girl you talked to -before and it turns out her sister is right in the same house. Talk to her -sister and you'll get the Squirtbottle. You'll need that for a later part of the -story. No, you won't use it on a Squirtle. What would that even do, anyway? - -Then, we're ready to head into Route 35 after Liz's offer for a rematch on Route -32. Ride your bike north through the building. It's cool how you can just ride a -bike through, but isn't that a safety hazard? - -In the route changing house towards Route 35, if you have a Pokémon lineup spot -open, a man can give you a Spearow named Kenya. You're supposed to get the mail -that Spearow is carrying to Route 31. Or you can just keep the Spearow with the -mail and not do anything with the mail. It's your life. You do you. - -Thard_Verad adds, "You can keep Kenya the Spearow and still complete the side -quest. Go catch a spare Spearow and move the Mail from Kenya to your own bird." - -|------------------------| -|Route 35 & National Park| -|------------------------| - -Pokémon found in Route 35: - -#016 Pidgey -#029 Nidoran (F) -#032 Nidoran (M) -#063 Abra -#096 Drowzee -#132 Ditto -#163 Hoothoot -#193 Yanma - -Items found in Route 35: - -Mystery Berry -TM04 Rollout - -The short Route 35 is home to a few trainers to fight along with. This is a -great place to train up your Pokémon. Thard_Verad adds, "If you farm up Mystery -Berries, they can supplement any Ethers you bring to the Elite Four." - -Picnicker Kim -Level 15 Vulpix (202 EXP) -You get: 300G - -Next up is Camper Elliot. He thinks he's amazing because he has a girlfriend. - -Camper Elliot -Level 13 Sandshrew (258 EXP) -Level 15 Marill (186 EXP) -You get: 300G - -Then you talk to Picnicker Brooke and she's less enthused that her boyfriend's -Pokémon are so bad. "My boyfriend's weak, so I can't rely on him." XD - -Picnicker Brooke -Level 16 Pikachu (280 EXP) -You get: 320G - -Move up to the next trainer! - -Camper Ivan -Level 10 Diglett (172 EXP) -Level 10 Zubat (115 EXP) -Level 14 Diglett (243 EXP) -You get: 280G - -Keep moving up the short Route 35. Irwin will have ridiculously easy Voltorbs to -beat. - -Juggler Irwin -Level 2 Voltorb (43 EXP) -Level 6 Voltorb (132 EXP) -Level 10 Voltorb (220 EXP) -Level 14 Voltorb (309 EXP) -You get: 560G - -Now go up to the trainer at the left. - -Firebreather Walt -Level 11 Magmar (393 EXP) -Level 13 Magmar (465 EXP) -You get: 624G - -There's also an officer just outside the checking station. Show him that you -mean business. - -Officer Dirk -Level 14 Growlithe (273 EXP) -Level 14 Growlithe (273 EXP) -You get: 560G - -Head up through the checking station on Route 35 and you'll be in National Park. - -|-------------| -|National Park| -|-------------| - -Pokémon found in National Park: - -#010 Caterpie -#011 Metapod -#012 Butterfree -#013 Weedle -#014 Kakuna -#015 Beedrill -#016 Pidgey -#046 Paras -#048 Venonat -#123 Scyther -#127 Pinsir -#163 Hoot Hoot -#191 Sunkern - -Items found in National Park: - -Berry (Consolation Prize) -Everstone (2nd Prize) -Gold Berry (3rd Prize) -Parlyz Heal -Quick Claw -Sun Stone (1st Prize) -TM28 - -There are some rare Pokémon here that makes the detour worthwhile. Scyther and -Pinsir are rare, and Nincada and Scyther can learn False Swipe. You'll also have -your version's bug type (Caterpie or Weedle). However these Pokémon are only -found during the Bug Catching Contest, and the contest only happens on Tuesdays, -Thursdays, and Saturdays. - -Pokémon at the Bug Catching Contest: - -Caterpie/Weedle -Nincada -Scyther -Pinsir -Volbeat -Wurmple - -You can decide which Pokémon that you use in the contest, but you must place it -in the first spot in your Pokémon lineup. The Pokémon here are generally around -Levels 28-31. You will get 20 Sport Balls and 20 minutes. In order to win the -contest, you will have to catch a rare Pokémon. Scyther and Pinsir are worth the -most points. If you catch a Scyther at full HP, it may be worth around 342 -points. If you win the contest, you can win a Shiny Stone! - -Thard_Verad adds: "During the Bug Catching Contest, your score also takes the -bug's HP into consideration. The best plan of attack is -to not attack. Bring a Pokemon with Sleep Powder and try to catch Scyther/Pinsir -while it's asleep but at full health." - -You can stay for the Bug-Catching Contest if you'd like and it is the right day. -Otherwise or afterwards, talk to the lady on the bench for Quick Claw, then head -up past the first part of the National Park and fight Pokefan Beverly. - -PoKeFan Beverly -Level 14 Snubbull (189 EXP) -You get: 1120G - -Head northeast to fight Schoolboy Jack! - -Schoolboy Jack -Level 12 Oddish (199 EXP) -Level 15 Voltorb (330 EXP) -You get: 480G - -Here in the park you'll also notice that there is high grass. This gives you -a bigger chance of encountering wild Pokémon. Head back to the left of -Beverly, then go northwest to fight the Lass. - -Lass Krise -Level 12 Oddish (199 EXP) -Level 15 Cubone (279 EXP) -You get: 360G - -If you go up and to the right, you can fight the last Pokefan in the National -Park. - -PokeFan William -Level 14 Raichu (366 EXP) -You get: 1120G - -How did these Pokefans get so rich? Next, head east and a little south to reach -Route 36. Have a Repel ready, then head onto Route 36. - -|-------------------| -|Route 36 & Route 37| -|-------------------| - -Pokémon found on Routes 36 & 37: - -#016 Pidgey -#017 Pidgeotto -#029 Nidoran (F) -#032 Nidoran (M) -#037 Vulpix (Silver only) -#058 Growlithe (Gold only) -#163 Hoothoot -#165 Ledyba (Silver only, morning only) -#167 Spinarak (Gold only, night only) -#234 Stantler - -Items found on Routes 36 & 37: - -Blk Apricorn -Blu Apricorn -Hard Stone -Ice Berry -Red Apricorn -TM08 Rock Smash - -Routes 36 & 37 are home to even more trainers. Grab the Ice Berry from the tree -you see right away, then head down to face Psychic Mark. - -Psychic Mark -Level 13 Abra (202 EXP) -Level 13 Abra (202 EXP) -Level 15 Kadabra (465 EXP) -You get: 480G - -Head down from Mark through the small path into Route 35 once again. - -|--------| -|Route 35| -|--------| - -Cut the tree blocking your way with A, then fight the trainer you see right -away. - -Bug Catcher Arnie -Level 15 Venonat (240 EXP) -You get: 240G - -Keep going down the grassy route till you reach another trainer. Fight him. - -Bird Keeper Bryan -Level 12 Pidgey (141 EXP) -Level 14 Pidgeotto (339 EXP) -You get: 336G - -Drive up to the left side and grab the item you saw earlier, TM04 Rollout. Then, -head back to Arnie and talk to her for her phone number. Afterwards, you can cut -the bush and head back onto Route 36. - -|--------| -|Route 36| -|--------| - -Welcome back, I guess. You'll want to head east now and take on the next -trainers. - -Schoolboy Alan -Level 16 Tangela (566 EXP) -You get: 512G - -Talk to Floria, the girl nearby, now. While she goes away to get the -Squirtbottle, you'll want to talk to Alan for his phone number before you run -after her back to Goldenrod City. Arnie also gives you a call at about that time -with Pidgey. Not sure why it's so important that you learn about Pidgey. Keep -heading northeast and you'll get to the tree Pokémon, Sudowoodo. - -Now that you're at the strange looking tree, SAVE! You'll want to catch it even -though you never once dreamed of catching something that looks like a lamp post. -Now that you have the Squirtbottle, approach the tree and use the Squirtbottle -on it. The tree will then attack and you'll learn it's a Level 20 Sudowoodo. - -It's the only one in the game, so catch it or else. Or else what, you may ask? -Well, I'm not going to inflict physical torture on you. You'll just have to live -with the grim reality that you did not catch Sudowoodo. Use same-type moves on -your starter Pokémon to send it down into the red HP range, then use a Great -Ball. You do not want to knock it out. You're here to capture it. - -Sudowoodo is a good addition to your team. Once you have captured Sudowoodo, -talk to the guy to the east. He will give you TM08, Rock Smash -for getting Sudowoodo out of the way. Now head east from Sudowoodo back into -Violet City. - -Also note that on Thursday, Arthur of Thursday will appear. He will give you the -Hard Stone item just for talking to him. - -============================================================================== -============================================================================== -============================================================================== - -(c13) -------------------------------------------------- diff --git a/docs/gold-walkthrough/section-07-burned-tower-and-ecruteak-city-gym.txt b/docs/gold-walkthrough/section-07-burned-tower-and-ecruteak-city-gym.txt deleted file mode 100644 index 36c5fe1b..00000000 --- a/docs/gold-walkthrough/section-07-burned-tower-and-ecruteak-city-gym.txt +++ /dev/null @@ -1,361 +0,0 @@ ----- 13 > Burned Tower and Ecruteak City Gym ---- -------------------------------------------------- - -Video Playthrough Link (Goldenrod City, Routes 35-37, to Ecruteak City): -https://www.youtube.com/watch?v=x0E8PnSbA5o - -Video Playthrough Link (Burned Tower, Ecruteak City Gym, & Olivine City -Lighthouse): https://www.youtube.com/watch?v=W0IMjwjGPO8 - -|-----------| -|Violet City| -|-----------| - -Now that Sudowoodo has damaged your Pokémon, heal at the Pokémon Center. You can -also now deposit Togepi and withdraw Sudowoodo to have it on your team. - -If you focused on just leveling up your starter, your lineup might look like -this: - -Level 31 Feraligatr -Level 20 Sudowoodo -Level 14 Drowzee -Level 12 Nidoran -Level 10 Spearow -Level 6 Bellsprout - -If you balanced leveling up your Starter and Raticate: - -Level 25 Raticate -Level 25 Starter Evolution -Level 20 Sudowoodo -Level 20 Eevee -Level 8 Bellsprout -Odd Egg - -Now, head back west onto Route 36, then go northwest onto Route 37. - -|--------| -|Route 37| -|--------| - -Pokémon found in Route 36 & 37: - -#016 Pidgey -#017 Pidgeotto -#029 Nidoran (F) -#032 Nidoran (M) -#037 Vulpix (Silver only) -#058 Growlithe (Gold only) -#163 Hoothoot -#165 Ledyba (Silver only, morning only) -#167 Spinarak (Gold only, night only) -#234 Stantler - -Items found in Route 36 & 37: - -Blk Apricorn -Blu Apricorn -Hard Stone -Ice Berry -Red Apricorn -TM08 Rock Smash - -First, you're battling some twins. If you start using your Sudowoodo here, Rock -Throw is the best move, but Low Kick can be a good alternative fighting move. - -Twins Ann & Anne -Level 16 Jigglypuff (259 EXP) -Level 16 Clefairy (232 EXP) -You get: 320G - -The bike shop calls again. Apparently, you can keep the bicycle because you -raised sales. Look at you, you're such an amazing salesman. We salute your -marketing efforts. Now, head up to fight the Psychic. - -Psychic Greg -Level 17 Drowzee (370 EXP) -You get: 544G - -Head back south of the Twins, then go east to the three apricorn trees. You may -want to clear your items at the Ecruteak City Pokémon Center before you grab -those items, so let's head to sunny Ecruteak City. - -If it's Sunday, you can also find Sunny of Sunday here and she'll give you a -Magnet item. - -|-------------| -|Ecruteak City| -|-------------| - -Enter the Pokémon Center. You will be unable to move for a second, making you -wonder if you have managed to freeze time. Alas, you do not have that ability -yet. Sad times. It's actually because Bill is getting his Pokémon healed. He's -finished adjustments on his Time Capsule, allowing you to trade Pokémon between -the Red/Blue/Yellow and Gold/Silver/Crystal games. But you'll need to have -caught something like 151 Pokémon first in Gold/Silver/Crystal before you can -actually make the trade. - -Heal at the Pokémon Center, and empty your small backpack of all your berries, -apricorns, and unused TMs into the PC. Make sure you CLEAR one Pokémon spot in -your team so that you can accept a free new Pokémon. Now, bike your way back -down to Route 37. Grab the apricorns you didn't grab earlier (Red, Black, and -Blue Apricorns). Then, keep biking down Route 36, through the National Park, and -through Route 35 back to Goldenrod City. With your newfound biking skills, I -think you should be ready for the Tour de France in no time. - -|--------------| -|Goldenrod City| -|--------------| - -Head to the southwest portion of the city and enter Bill's house. Now that -you've babysat a Togepi, he will give you an Eevee to take care of. I personally -think Eevee should be named Eevily. What a fantastic name, if I do say so -myself. Eevee can evolve into five other Pokémon: Jolteon (with a thunder -stone), Flareon (with a fire stone), Vaporeon (with a water stone), and Espeon -and Umbreon (based on high friendship and time of day - Espeon in the day, -Umbreon at night). Now, head back north onto Route 35. - -If you focused on just leveling up your starter, your lineup might look like -this: - -Level 31 Feraligatr -Level 20 Eevee -Level 20 Sudowoodo -Level 14 Drowzee -Level 10 Spearow -Level 6 Bellsprout - -If you balanced leveling up your Starter and Raticate: - -Level 25 Raticate -Level 25 Starter Evolution -Level 20 Sudowoodo -Level 20 Eevee -Level 8 Bellsprout -Odd Egg - -If you are building a balanced team, you could have Sudowoodo and Eevee exchange -experience points for a while from now on, until the fourth gym. In any case, -ride all the way back up to Ecruteak City, and don't get lost. Use the Pokegear -if you need to. There isn't a GPS in this game. - -|-------------| -|Ecruteak City| -|-------------| - -Items found in Ecruteak City: - -HM03 Surf -Itemfinder -TM30 - -You've reached Ecruteak City with an Eevee this time! Congratulations! Actually, -this isn't a prize booth, as much as you want it to be one. With your new Eevee -with you, enter the Dance Theater just above the Pokémon Center. Talk to the -creepy old man near the door and he will tell you that if you defeat all five -Kimono Girls, you will get a gift. I mean, he's creepy, but the challenge is -still worth your while. - -Head up the left side of the stage and battle the girl in green. It's time to -take on every Eevee evolution, one by one. The first is Flareon, a fire type. - -Kimono Girl Naoko -Level 17 Flareon (720 EXP) -You get: 1224G - -The girl in blue has Espeon, the psychic evolution to Eevee. - -Kimono Girl Sayo -Level 17 Espeon (717 EXP) -You get: 1224G - -Next is the girl in red, who has Umbreon, the dark evolution to Eevee. - -Kimono Girl Zuki -Level 17 Umbreon (717 EXP) -You get: 1224G - -The one that's next to the right in blue has Vaporeon, the water evolution to -Eevee. - -Kimono Girl Kuni -Level 17 Vaporeon (714 EXP) -You get: 1224G - -The final Kimono Girl has Jolteon, the electric evolution to Eevee. - -Kimono Girl Miki -Level 17 Jolteon (717 EXP) -You get: 1224G - -Now, talk to the same old man again. You'll get HM03 Surf for your efforts. You -can have almost any water type Pokémon learn it, plus a few others. But not -Magikarp. That fish just flops all over the place. Teach HM03 Surf to any -Pokémon that can learn it, because it's one of the best water moves for battle -and you can use it in the overworld. - -Leave the house and then head southwest to the gym. Once you see it, go to the -building to the right of it and talk to the man inside. He will give you -Itemfinder if you answer Yes to his question. It's amazing how many items you -can get in this game just by hearing people blather on about themselves and just -say Yes. - -Heal at the Pokémon Center, then head to the northwest part of Ecruteak City and -into the Burned Tower. I suggest bringing at least 3 Escape Ropes, 3 Repels, and -10 Great Balls with you. - -|------------| -|Burned Tower| -|------------| - -Pokémon at the Burning Tower: - -#019 Rattata -#041 Zubat -#109 Koffing - -Equipping the TM Rock Smash will give you the HP Up at the northeastern part of -the floor, but is it really worth it to use up that TM this early? Think about -your future. Head left and all the way around the spiral and you will now fight -your rival in a battle. He's gonna get pwned again by your superior lineup. He -has a Magnemite now, but you have two extra Level 21+ Pokémon. - -|-------------------------------------------| -|Rival | -|Level 20 Haunter (540 EXP) | -|Level 18 Magnemite (342 EXP) | -|Level 20 Zubat (231 EXP) | -|Level 22 Bayleef/Quilava/Croconaw (673 EXP)| -|You get: 1,320G | -|-------------------------------------------| - -Use anything other than normal and fighting moves against Haunter. Magnemite's a -steel type, so fire moves are good, while normal moves are bad. Fighting moves -like low kick help. Rock throws are great against Zubat. Against his starter -evolution, you can use the highest-powered move from your highest level Pokémon -that isn't the type of your Pokémon. Piece of cake. I love cake. - -Your rival says, after losing to you, “There’s no challenge in it.” After your -victory, you can grab a HP Up by dropping down the northwest hole. - -|-------------| -|Ecruteak City| -|-------------| - -Heal your Pokémon just before entering the Ecruteak City Gym. You'll probably be -around 4 1/4 to 5 hours into the game. Pat yourself on the back for your -commitment to fighting with fictitious pixelated creatures. Good job! - -If you're focusing on only leveling up your starter, your lineup should look -something like this: - -Level 33 Starter Evolution -Level 20 Sudowoodo -Level 20 Eevee - -If you're building up a more balanced lineup, it may look something like this: - -Level 21 Sudowoodo -Level 21 Eevee -Level 25 Starter Evolution -Level 26 Raticate - -Let's enter the gym now. Go straight up to fight Trainer 1. Every trainer will -use Ghost type Pokémon, meaning that you can't use any normal or fighting moves -from your Raticate or Eevee. So, you'll have to lean into using your starter -evolution and potentially Sudowoodo (rock throws). Your Pokémon will get licked -by ghosts. I hope your Pokémon likes the experience because it will get licked a -bunch of times. - -Sage Ping -Level 16 Gastly (325 EXP) -Level 16 Gastly (325 EXP) -Level 16 Gastly (325 EXP) -Level 16 Gastly (325 EXP) -Level 16 Gastly (325 EXP) -You get: 512G - -Now head exactly TWO steps right, then FOUR steps up to fight Trainer 2. Doing -just about anything else will make you fall off the platform. Keep those rock -throws going against Grace. - -Medium Grace -Level 20 Haunter (540 EXP) -Level 20 Haunter (540 EXP) -You get: 800G - -Talk to Grace again and basically, the path is the zig-zag. You must face every -trainer before you face the gym leader. Go three left, 2 up to Trainer 3. - -Sage Jeffrey -Level 22 Haunter (594 EXP) -You get: 704G - -Time to take on the final trainer before Morty. Go right 3, then up 2. As a test -of what's to come, this is almost the same lineup as the gym leader, except -Morty has higher levelled and stronger Pokémon. - -Medium Martha -Level 18 Gastly (360 EXP) -Level 20 Haunter (540 EXP) -Level 20 Gastly (406 EXP) -You get: 800G - -You can move up two squares to get past the platform to get to the gym leader. -But consider healing at the Pokémon Center before fighting the gym leader. I -guess you don't have to, but you're reading this guide and if you're not -following suggestions, why are you here? Oh, sorry, I digress. To get back, fall -off the path anywhere and it'll drop you back to the gym entrance. - -Heal at the Pokémon Center and then return to the gym, then take the zig-zag -path back to Morty. Save before you fight Morty in case you lose and want to -re-try the battle quickly. Talk to Morty, and he'll reference some kind of -vision of legendaries. I don't know what he was talking about, as I wasn't -really listening. He's kind of boring. - -Battle time - let's go! You'll want to beware of Pokémon that can use Hypnosis -to put your Pokémon to sleep, and then eat its dreams. If you picked Chikorita, -this could be a more challenging fight where you might need some assistance from -your Sudowoodo or other Pokémon. - ---- -GYM LEADER MORTY -Level 21 Gastly (427 EXP) -Level 23 Haunter (567 EXP) -Level 25 Gengar (1017 EXP) -Level 23 Haunter (621 EXP) -You get: Fog Badge, 2,300G, & TM30 (Shadow Ball) ---- - -Strategy VERSUS Gastly: You can always use your same-type attack from your -starter evolution. Alternatively, a single rock throw from Sudowoodo should and -would down Gastly. - -Strategy VERSUS Haunters: Use the same-type attack from your starter evolution -or use two rock throws from Sudowoodo. - -Strategy VERSUS Gengar: Gengar is really nasty because it has Hypnosis and then -Dream Eater to cap on it, but do as many rock throws from Sudowoodo as you can -before you pull out your starter evolution. Use the same-type attack from your -starter evolution to finish off Gengar. - -Phew, you won. That's pretty good, considering how many times you got cursed. -Half of the League has fallen to you. All Level 50 and below Pokémon obey you, -and now you can use HM03 Surf out of battle. TM30 Shadow Ball is a really good -ghost move you can equip on a ghost Pokémon, but keep in mind that it's a -one-use item. - -Now that you can equip and use HM03 Surf, you should equip it on every water -Pokémon that can use it. It's a 95 power move, making it one of the best attacks -you can use during the game playthrough. Surf is also a great move to use in the -overworld to get across bodies of water. And once you have HM03 Surf, you can -also go back to Union Cave on a Friday to catch a Level 20 Lapras (see Section -36 of this guide). - -============================================================================== -============================================================================== -============================================================================== - -(c14) -------------------------------------------------------- diff --git a/docs/gold-walkthrough/section-08-olivine-lighthouse-and-cianwood-city-gym.txt b/docs/gold-walkthrough/section-08-olivine-lighthouse-and-cianwood-city-gym.txt deleted file mode 100644 index bd8322e4..00000000 --- a/docs/gold-walkthrough/section-08-olivine-lighthouse-and-cianwood-city-gym.txt +++ /dev/null @@ -1,584 +0,0 @@ ----- 14 > Olivine Lighthouse and Cianwood City Gym ---- -------------------------------------------------------- - -Video Playthrough Link (Burned Tower, Ecruteak City Gym, & Olivine City -Lighthouse): https://www.youtube.com/watch?v=W0IMjwjGPO8 - -Video Playthrough Link (Routes 40-43, Cianwood City Gym, Olivine City Gym, Lake -of Rage): https://www.youtube.com/watch?v=HQoKpSr3VaE - -Heal, then head west onto Route 38. Arnie calls again. I'm pretty sure these -trainers keep calling randomly because they're extroverts and they're lonely. - -Thard_Verad adds, "So far as I can tell, it's roughly every 20-30 minutes, but -the trainer who calls and what they call about is random. Sometimes it's just to -chat, others want a rematch, a few might tell you about an outbreak of a -particular Pokemon. (Personally I'm waiting for news of a Remoraid outbreak)." - -Thard_Verad also notes that trainers will only call about gifts in the Pokémon -Crystal Version. - -Well, go through the building into Route 38. - -|--------| -|Route 38| -|--------| - -Pokémon found in Route 38: - -#019 Rattata -#020 Raticate -#052 Meowth (Silver only) -#081 Magnemite -#082 Farfetch'd -#128 Tauros -#241 Miltank - -Items found in Route 38: - -Berry - -You're heading west. There are trainers, like on almost every route. I know, -that part is repetitive. Beep beep boop. - -Bird Keeper Toby -Level 15 Doduo (307 EXP) -Level 16 Doduo (328 EXP) -Level 17 Doduo (349 EXP) -You get: 408G - -Go left and you'll get a Berry, then return to just before the checkpoint house. -Instead of going west, go northwest and you'll face a loner. - -Sailor Harry -Level 19 Wooper (211 EXP) -You get: 760G - -Head left through the grass to face another trainer. She says, "If you're really -good, I want you to teach me things." That, I can do! - -Lass Dana -Level 18 Flaaffy (450 EXP) -Level 18 Psyduck (307 EXP) -You get: 432G - -Talk to Dana again and you will be able to switch phone numbers with her. Well, -she seems popular and fairly technologically savvy. I mean, who even has a cell -phone in this region? She'll call you from time to time to tell you that her -Flaaffy is in good shape. - -Now head left to face the 4th trainer on this route. - -Beauty Valerie -Level 17 Hoppip (268 EXP) -Level 17 Skiploom (495 EXP) -You get: 1496G - -Go northwest and you'll run into: - -Schoolboy Chad -Level 19 Mr. Mime (553 EXP) -You get: 608G - -Mom calls saying she bought you an adorable doll. But is it adorable? That's for -you to decide. I just hope it's not a creepy doll that she thinks is adorable. -Now move southward now to face Beauty Olivia. Now head west onto Route 39. - -|--------| -|Route 39| -|--------| - -Pokémon found in Route 39: - -#019 Rattata -#020 Raticate -#052 Meowth (Silver only) -#081 Magnemite -#082 Farfetch'd -#128 Tauros -#241 Miltank - -Items found in Route 39: - -Mint Berry -TM13 - -Welcome to Route 39! It's the home of Moo Moo Farm. Here, moles pop out of the -road randomly and you'll fall off your bike. Oh wait, that's Mario Kart 64. -Sorry, my bad. It can be hard to keep track sometimes. Anyway, there are four -trainers ready to lose you to here on Route 39. - -Psychic Norman -Level 17 Slowpoke (360 EXP) -Level 20 Slowpoke (423 EXP) -You get: 640G - -Check out the Moo Moo Farm to the west of him. You can get Moo Moo Milk there -for 500G. It's a little expensive, but cheaper than a -Super Potion and more effective. But before you can buy milk, you will first -have to feed berries to their Miltank so that it gets better. Once you do that, -you'll get TM13! For now, grab the Mint Berry, remember this place exists, and -come back later with those berries. Head southward. - -PoKeFan Derek -Level 17 Pikachu (298 EXP) -You get: 1360G - -Continue down... - -PoKeFan Ruth -Level 17 Pikachu (298 EXP) -You get: 1360G - -Continue southward to yet another trainer... - -Sailor Eugene -Level 17 Poliwhirl (477 EXP) -Level 17 Raticate (421 EXP) -Level 19 Krabby (468 EXP) -You get: 760G - -Head southward into... - -|------------| -|Olivine City| -|------------| - -Pokémon found in Olivine City: - -#100 Voltorb - -Items found in Olivine City: - -Good Rod -HM04 Strength -TM23 - -As soon as you enter Olivine City and, when you are about to pass by the gym -your rival will appear. He will not challenge you to a match (because he knows -he's going to lose ^_^). Instead, he will tell you that Jasmine the Gym Leader -is currently at the lighthouse tending to a sick Pokémon. He thinks the gym -leader is a weakling because he has always favored strong Pokémon. - -As you zig zag your way down Olivine City, go into the house to the southeast of -the gym. You can apparently do that everywhere because no one locks their doors -in this foreign land. Talk to the fisherman and he will give you a Good Rod, -which can catch more than just Magikarps that can only splash. - -Then heal at the Pokémon Center and talk to the sailor at the table for HM04 -Strength! All that biking you did isn't going to help you move boulders. That's -mostly cardio and strong legs, man. But now you can move boulders! - -Now that you have and can use HM04 Strength, it is beneficial to equip HM04 -Strength on any Pokémon at this stage that you can, because it's an 80-power -move that's worth using. Strength is also a great move to use in the overworld -to get past boulders in dungeons. You'll need to go to a Move Deleter to remove -it later if you want to improve your post-game moves. However, HM04 Strength is -all around stellar, and it's going to be better than most moves that Pokémon -naturally learn or can learn from TMs during your game playthrough. - -After you get Strength (HM04) for free, go up the Olivine City Lighthouse in the -southeast part of town (not the dock). You'll spot it when you see it. It's -basically the only skyscraper in the city because no one can afford to build -bigger buildings in Johto. Let's go in and fight! - -|-----------------------| -|Olivine City Lighthouse| -|-----------------------| - -Items found in Olivine City Lighthouse: - -Ether -Great Ball -Rare Candy -Super Potion -TM34 Swagger - -OK, you're going to have to climb up a few floors to even meet Jasmine. You'll -need to switch back and forth between going clockwise and counterclockwise on -each floor. Wait, do people still know what analog clocks work anymore? Think -about going around in a circle in one direction and then back in the other -direction. - -Follow the path up to the second floor. You might get a call from Liz about guys -like Falkner. I don't know, people call about the weirdest things. I guess the -real reason here is not to give strangers your phone number. Keep going till you -see another trainer. - -Gentleman Alfred -Level 20 Noctowl (693 EXP) -You get: 1440G - -Keep advancing and move along the path to face Trainer 2. - -Sailor Huey -Level 18 Poliwag (297 EXP) -Level 18 Poliwhirl (504 EXP) -You get: 720G - -Go up to the next floor. You can avoid fighting the next trainer, but you should -probably pick up the EXP. - -Bird Keeper Theo -Level 17 Pidgey (199 EXP) -Level 15 Pidgey (175 EXP) -Level 19 Pidgey (223 EXP) -Level 15 Pidgey (175 EXP) -Level 15 Pidgey (175 EXP) -You get: 360G - -You'll probably want to heal Eevee up a bit now. Keep following the path to the -4th trainer battle! - -Gentleman Preston -Level 18 Growlithe (351 EXP) -Level 18 Growlithe (351 EXP) -You get: 1296G - -Head up the stairs to the 4th floor and head directly left for trainer battle 5. -She seems to be twitchy about things with Jasmine, almost like she wants to find -Jasmine and kidnap her. - -Lass Connie -Level 21 Marill (261 EXP) -You get: 504G - -If you talk to Connie again, it sounds like she actually cares about Connie. -Phew! Head south now, around on the path to the next trainer. - -Sailor Kent -8Level 18 Krabby (442 EXP) -Level 20 Krabby (492 EXP) -You get: 800G - -Todd will now call and say that Goldenrod finally has cheap sales. Woot! Head up -the stairs to fifth floor, or whatever it is. I'm not writing this guide because -I'm good at counting. I didn't promise that anywhere. After you get up, head -downward to grab TM34 Swagger, then go up to fight another trainer. - -Bird Keeper Denis -Level 18 Spearow (223 EXP) -Level 18 Spearow (223 EXP) -Level 20 Fearow (693 EXP) -You get: 432G - -Keep going and grab the Rare Candy, then go back down to Floor 4. There's a hole -next to Lass Connie. This would be a lawsuit anywhere else, but it's Pokémon and -your main character can jump down floors unscathed because he's the hero of the -story. Then, you'll find another trainer. - -Sailor Terrell -Level 20 Poliwhirl (561 EXP) -You get: 800G - -Grab the Ether right next to him, head up the stairs, and head up even more -stairs. Fight the trainer below you. - -Sailor Ernest -Level 18 Machop (339 EXP) -Level 18 Machop (339 EXP) -Level 18 Poliwhirl (504 EXP) -You get: 720G - -Go down and get the Great Ball, then climb the stairs again. We are finally on -the top level, Level 6. Talk to the person on that level, and it is Gym Leader -Jasmine. She asks you to get medicine for Ampharos for her. Afterwards, grab the -Super Potion left of Jasmine, then go to the right and jump down the holes on -the right side of every floor to drop back down to the bottom of the -lighthouses. - -|------------| -|Olivine City| -|------------| - -Heal at the Pokémon Center. Make sure you have a water Pokémon that can learn -HM03 Surf. If you don't have one, you'll have to catch one with a Good Rod on -the following route, Route 40. - -Teach HM03 Surf to any Pokémon that can learn it, which will primarily be water -type Pokémon, because it's one of the best water moves for battle and you can -use it in the overworld. You'll need to go to a Move Deleter to remove it later -if you want to improve your post-game moves. However, HM03 Surf is all around -stellar, and it's going to be better than most moves that Pokémon naturally -learn or can learn from TMs during your game playthrough. Note that Magikarp -can't learn Surf. That fish just flops all over the place. But Krabby can. How -does a trainer ride a tiny Krabby? No one really knows. - -Now exit, and enter the PoKeMart to restock on items, like Great Balls. Now head -west from the Pokémon Center onto Route 40! - -|--------| -|Route 40| -|--------| - -Pokémon found in Route 40: - -#072 Tentacool -#073 Tentacruel -#098 Krabby -#213 Shuckle -#226 Mantine (Gold only) - -Head to about where you see the swimmer in the water, then activate Surf by -clicking A at the water. It's time for the sea battles to begin! Talk to the -swimmer and let's begin a fight! - -Swimmer Simon -Level 20 Tentacool (450 EXP) -Level 20 Tentacool (450 EXP) -You get: 160G - -Swim southwest till you see a swimmer that's a girl. Face her next. - -Swimmer Elaine -Level 21 Staryu (477 EXP) -You get: 420G - -Head down till you see another girl a little southeast. Let's take another water -battle! - -Swimmer Paula -Level 19 Staryu (430 EXP) -Level 19 Shellder (394 EXP) -You get: 380G - -Head southeast again to encounter another guy there, the final swimmer on the -route. - -Swimmer Randall -Level 18 Shellder (373 EXP) -Level 20 Wartotle (612 EXP) -Level 18 Shellder (373 EXP) -You get: 144G - -Now, swim south onto Route 41. - -|--------| -|Route 41| -|--------| - -Pokémon found in Route 41: - -#072 Tentacool -#073 Tentacruel -#098 Krabby -#213 Shuckle -#226 Mantine (Gold only) - -Route 41 should give your Pokémon (like Eevee) lots of training for experience. - -If you go down the right side: - -You'll see a boy swimming around, or more like, spinning around. I feel like -spinning in the water like that should be in the Summer Olympics. He seems to be -the first trainer you've met to even have six Pokémon. - -Swimmer George -Level 16 Tentacool (360 EXP) -Level 17 Tentacool (382 EXP) -Level 16 Tentacool (360 EXP) -Level 19 Staryu (430 EXP) -Level 17 Tentacool (382 EXP) -Level 19 Remoraid (316 EXP) -You get: 152G - -Liz then calls you about her Nidoran pwning a Rattata. Yay! Continue going south -from George and you'll spot another swimmer. Either that, or she spots you. -Those are really the only two options. There isn't a third. - -Swimmer Kara -Level 20 Staryu (453 EXP) -Level 20 Starmie (886 EXP) -You get: 400G - -Keep heading south along the smaller and smaller path. - -If you go down the left side: - -You'll be going between some reefs and whirlpools. Keep going down to fight more -swimmers. - - -Swimmer Charlie -Level 21 Shellder (436 EXP) -Level 21 Tentacool (427 EXP) -Level 19 Tentacruel (834 EXP) -You get: 152G - -If you go west from here: - -Swimmer Wendy -Level 21 Horsea (373 EXP) -Level 21 Horsea (373 EXP) -You get: 420G - -If you go south/southwest instead of west: - -Swimmer Susie -Level 20 Psyduck (342 EXP) -Level 22 Goldeen (522 EXP) -You get: 440G - -Swimmer Berke -Level 23 Qwilfish (492 EXP) -You get: 184G - -Swimmer Denise -Level 22 Seel (471 EXP) -You get: 440G - -Head to the east of her. - -Swimmer Kirk -Level 20 Gyarados (916 EXP) -Level 20 Gyarados (916 EXP) -You get: 160G - -The two sides will eventually merge. You will EVENTUALLY spot a swimmer. Fight -him, the guy who talks of the Whirl Islands. The Krabby could do some damage -against lower-level Pokémon. -Swimmer Mathew -Level 23 Krabby (565 EXP) -You get: 184G - -You will reach land after that battle. Welcome to Cianwood City. - -|-------------| -|Cianwood City| -|-------------| - -Pokémon found in Cianwood City: - -#213 Shuckle (Get for free) - -Items found in Cianwood City: - -HM02 Fly -Secret Potion -TM01 - -Cianwood is pretty small, and you will not be spending much time here. Heal at -the Pokémon Center and clear a space so you can acquire another Pokémon. This -may be a good opportunity to see which of your Pokémon can learn HMs Surf and -Strength. You'll still want to have one Pokémon that can use HM01 Cut. - -To the left of the Pokémon Center is a house where a PokeManiac will give you -Level 15 Shuckie the Shuckle if you talk to him so that your thief rival won't -steal that too. You can return it later if you'd like. You're good at taking -care of Pokémon, right? I assume you are. You probably were playing with -Tomagachis before you turned this game on. Remember to feed your pets and give -your pets water! - -Thard_Verad adds, "You can keep Shuckie the Shuckle if it likes you enough when -you try to return it. Let any Shuckle hold a Berry as you run around. Eventually -it will turn into Berry Juice. (Restores 20 HP) Berry Juice is nice if you do a -Little Cup battle." - -Now head to the building below that. It's the Pharmacy. A guy inside the house, -wearing sunglasses will give you some medicine, a.k.a. Secretpotion. You will -give this back to Jasmine in Olivine for Ampharos to light back the lighthouse. -If you visit the home in the top part of town, he will offer to take pictures of -your Pokémon. You can then print them out if you have the Gameboy Printer. - -Anyways, it's now time to battle in the Cianwood City Gym and face Chuck, the -fighting gym leader. Now enter the gym. - -If you're only leveling up your starter, here's what your lineup might look -like: - -Level 40 Starter Evolution -Level 21 Sudowoodo -Level 21 Tentacool -Level 20 Eevee -Level 15 Shuckle -Level 10 Spearow "Kenya" - -Your lineup should now look like this: - -Level 27 Starter Evolution -Level 27 Sudowoodo -Level 28 Raticate -Level 29 Eevee -Level 15 Shuckle - -Let's go! Head up and you'll fight all the Blackbelts. Fighting type moves are -super effective against normal and rock types, so try not to use them where -possible against Chuck. Your evolved starter will do well. If you've been -leveling up one Pokémon, you can just repeat your strongest move and it should -sweep the gym. If you have a more balanced lineup, it may be tougher to do as -much damage. - -Blackbelt Yoshi -Level 27 Hitmonlee (804 EXP) -You get: 648G - -Before you can react, the other Blackbelt fights you. You got double teamed. - -Blackbelt Lao -Level 27 Hitmonchan (810 EXP) -You get: 648G - -Keep moving up. - -Blackbelt Nob -Level 25 Machop (471 EXP) -Level 25 Machoke (565 EXP) -You get: 600G - -Use Strength to push your way to the last trainer before the gym leader. - -Blackbelt Lung -Level 23 Mankrey (364 EXP) -Level 23 Mankey (364 EXP) -Level 25 Primeape (798 EXP) -You get: 600G - -Now that you've fought every trainer, leave the gym, heal at the Pokémon Center, -and then return to the gym. To get past the boulders, you need a Pokémon with HM -Strength. Then push the left and right boulders each up one, and move the center -boulder left or right. - -Save before the battle. If you aren't regularly saving while playing this game, -I'm not sure what to tell you. You must have a lot of confidence in your -batteries or in your abilities to win battles without resetting. Congrats on -your confidence. I'm sure that it will be worth something someday. - ---- -GYM LEADER CHUCK -Level 27 Primeape (861 EXP) -Level 30 Poliwrath (1188 EXP) -You get: Storm Badge, 3000G, & TM01 (Dynamic-Punch) ---- - -Strategy VERSUS Primeape: If you're just leveling up your starter, just use the -strongest move. Flying Pokémon could also be effective because flying moves beat -fighting. Raticate's hyper fangs can also work well. - -Strategy VERSUS Poliwrath: Use the strongest move if you're using your starter. -If you're using a more balanced team, you may need to use a variety of -high-power moves, like low kicks from Sudowoodo or hyper fangs from Raticate. A -combination of Pokémon unleashing damage on Poliwrath should be able to take it -down. It's a takedown. - -With the victory over Chuck, you have FIVE badges, another TM (Dynamic-Punch), -and more moolah. Woot. You're also within easy range for a 6th badge. Level 70 -and under Pokémon all obey you and now you can use HM02 Fly, which you'll be -getting in just a second. Dynamic Punch is the deadly punch Poliwrath gave you -earlier, but it has crappy aim and will only work half the time. - -Talk to the lady outside the gym and she will give you HM02 Fly after she -notices your fifth badge. Apparently, she's Chuck's wife and wants him to stop -being so chubby. I think that's more a matter of diet than training at this -point. Chuck is probably eating too many chips and drinking too much soda. - -Teach HM02 Fly to all your bird Pokémon (flying or otherwise) as it's a good -70-power move in a playthrough that can also get you quickly around the map. -You'll want to return to Olivine now so that you can give the medicine to -Jasmine and the Ampharos. - -============================================================================== -============================================================================== -============================================================================== - -(c15) -------------------------------- diff --git a/docs/gold-walkthrough/section-09-olivine-city-gym.txt b/docs/gold-walkthrough/section-09-olivine-city-gym.txt deleted file mode 100644 index 27818174..00000000 --- a/docs/gold-walkthrough/section-09-olivine-city-gym.txt +++ /dev/null @@ -1,81 +0,0 @@ ----- 15 > Olivine City Gym ---- -------------------------------- - -Video Playthrough Link (Routes 40-43, Cianwood City Gym, Olivine City Gym, Lake -of Rage): https://www.youtube.com/watch?v=HQoKpSr3VaE - -Make sure you've got a bird Pokémon using HM02 Fly. If you are leveling up an -Eevee to try to evolve it into an Espeon or Umbreon, fly to Goldenrod City. -Otherwise, fly straight back to Olivine City and skip the next paragraph. - -|--------------| -|Goldenrod City| -|--------------| - -Welcome back. Here, we'll want to go into the Goldenrod City underground, so -head to the southwestern portion of town and do so. Go in and you'll find the -three shops again. Talk to the man who is running the second/middle shop. Pay -the 500G for Eevee to get groomed. You want Eevee happy for evolution, right? =) -Eevee's happiness meter should get a nice boost. Now get out of there and fly -back to Olivine City, then enter the Olivine City Lighthouse. - -|------------------| -|Olivine Lighthouse| -|------------------| - -Climb up the Olivine Lighthouse in the EXACT same way you came up. This means -falling to the left of that one lass. Once you get all the way up, talk to -Jasmine and say Yes to the fact that Ampharos will be cured by the medicine. Now -Jasmine will return to her gym, so that's good. Now drop down out of the -Lighthouse like you did before and re-enter Olivine City. - -|------------| -|Olivine City| -|------------| - -You have two choices here. You can fight the gym, or try to gain more experience -first by flying to Ecruteak City and go through the Lake of Rage and then come -back (which is in the next parts of this guide). I'm not your mom, who keeps -buying random items with your money. You do have choices in life. If you want to -get a nice shiny Pokémon to assist you in your next fight first, take that. If -you want to see how you fare now, you can go straight into the Olivine City Gym -to take on Jasmine. - -Enter the Olivine City Gym. This is a steel-based gym with electric elements. -Your starter should do okay even if there's a type disadvantage. Sudowoodo and -Raticate will do well too, although the Gyarados that you can get at the Lake of -Rage will fare the best with Dragon Rage. - -Keep moving up in the gym. You will find there are no apprentices of any kind. -Jasmine does not take apprentices. She's too good for that. Jasmine remembers -your help and thanks you, then remembers that she's a gym leader and that she -needs to be serious. Save before the battle, then put either your starter -Pokémon, Raticate, or Gyarados in the front position when you start fighting. - ---- -GYM LEADER JASMINE -Level 30 Magnemite (571 EXP) -Level 30 Magnemite (571 EXP) -Level 35 Steelix (1470 EXP) -You get: Mineral Badge, 3,500G, & TM23 (Iron Tail) ---- - -Strategy VERSUS Magnemites: Use the strongest starter move, fire, or fighting. -Beware of thunder waves that can paralyze your Pokémon. Magnemite actually does -pretty decent damage with Sonicboom. - -Strategy VERSUS Steelix: Use the strongest starter move, fire, or fighting. -Beware of thunder waves that can paralyze your Pokémon. Iron Tail can do a -decent amount of damage. - -Woot! That's your sixth badge! This gym leader also thinks you're better now -(some gym leaders are arrogant), and with the Mineral Badge your Pokémon's -defense is raised slightly. You also get TM23, Iron Tail, the attack Steelix -used. - -============================================================================== -============================================================================== -============================================================================== - -(c16) --------------------------------------------------- diff --git a/docs/gold-walkthrough/section-10-the-lake-of-rage-and-shiny-gyarados.txt b/docs/gold-walkthrough/section-10-the-lake-of-rage-and-shiny-gyarados.txt deleted file mode 100644 index 238da328..00000000 --- a/docs/gold-walkthrough/section-10-the-lake-of-rage-and-shiny-gyarados.txt +++ /dev/null @@ -1,223 +0,0 @@ ----- 16 > The Lake of Rage and Shiny Gyarados ---- --------------------------------------------------- - -Video Playthrough Link (Routes 40-43, Cianwood City Gym, Olivine City Gym, Lake -of Rage): https://www.youtube.com/watch?v=HQoKpSr3VaE - -Video Playthrough Link (Mahogany Town & Lake of Rage): -https://www.youtube.com/watch?v=z5_tkLJ0fvw - -|-------------| -|Ecruteak City| -|-------------| - -Fly to Ecruteak City, then head east onto Route 42. Bring Pokémon that can use -HMs Surf and Cut. - -|--------| -|Route 42| -|--------| - -Pokémon found in Route 42: - -#021 Spearow -#041 Zubat -#056 Mankey (Gold only) -#179 Mareep -#180 Flaaffy - -Items found in Route 42: - -Grn Apricorn -Pnk Apricorn -Super Potion -Ultra Ball -Ylw Apricorn - -Grab the Ultra Ball you see right after you enter the route. Head a little east. -I suggest waiting to go into Mt. Mortar until you've gotten HM07 Waterfall so -you can have the full Mt. Mortar experience. It's like going for fast food -rather than your luxury dining experience. I'd suggest taking the luxury dining -experience here. - -Instead, head east and surf across that lake now, on a Tentacool, Wooper, -Krabby, or whatever. I'm not exactly sure how you fit on any of these Pokémon, -given that you are larger than each Pokémon. But you fit like it's a small boat -custom built for your comfort. Then, use Cut below and grab the three Apricorns -(Green, Pink, and Yellow). Keep going and then grab the Super Potion. - -|--------| -|Route 42| -|--------| - -Surf across the second lake and talk to the fisherman for a battle. - -Fisher Chris -Level 18 Qwilfish (385 EXP) -You get: 720G - -You'll see two more trainers. Fight them both. - -PoKeManiac Shane -Level 16 Nidorina (400 EXP) -Level 16 Nidorino (403 EXP) -You get: 960G - -I'm not sure why the PoKeManiac needed to be so xenophobic, but he was easy to -beat. Now fight the final trainer on the route. - -Hiker Benjamin -Level 14 Diglett (243 EXP) -Level 14 Geodude (258 EXP) -Level 16 Dugtrio (523 EXP) -You get: 512G - -Now ride east into Mahogany Town. - -|-------------| -|Mahogany Town| -|-------------| - -Items found in Mahogany Town: - -TM16 - -Heal at the Pokémon Center, then explore Mahogany Town. It's small in size, but -big in swindling. The guy in the house in the north is trying to sell you -Slowpoketails for 9800G. Suspicious. Then if you try to go east, the guy wants -you to head up to the Lake of Rage and tries to sell you Ragecandybars. If you -buy one, that's cool, but he still won't let you pass. So rude. - -Well, I guess it's time to head north onto Route 43. Make sure you have at least -15 Great Balls before you head up, because there are a lot of catching -opportunities on Route 43 and at the Lake of Rage. - -|---------------------------| -|Route 43 & The Lake of Rage| -|---------------------------| - -Pokémon found in Route 43: - -#017 Pidgeotto -#048 Venonat -#164 Noctowl -#179 Mareep -#180 Flaaffy -#230 Girafarig - -Items found in Route 43: - -Bitter Berry -Max Ether - -Head up the path to fight some five trainers. Let's commence. - -Camper Spencer -Level 17 Sandshrew (337 EXP) -Level 18 Sandslash (592 EXP) -Level 19 Zubat (219 EXP) -You get: 380G - -As you go through Route 43 you will see a building blocking the right side of -the road. Don't go through it. Team Rocket will charge you 1,000G to go through. -You must be frugal! You've gotten rich taking money from random trainers on -shady routes, but you won't stay rich if you give Team Rocket your hard earned -money. - -Instead, you should go the long way up to the Lake of Rage. As you near the end -of Route 43 you will find two routes to the Lake of Rage. The left path will -bring you two some items (be sure to have a Pokémon with HM01 Cut), and you will -also find Wesley on Wednesday. He will give you the Blackbelt item, which powers -up Fighting attacks. The right path will lead to the Lake of Rage. Head up for -more battling. Remember to grab the Max Ether, too, which is to the right of -Picnicker Tiffany. - -Picnicker Tiffany -Level 20 Clefairy (291 EXP) -You get: 400G - -Keep moving up. - -PoKeManiac Brent -Level 19 Lickitung (516 EXP) -You get: 1140G - -You're trying to save civilization from a dangerous threat on the Lake of Rage, -but naturally, this is the time your Mom calls you. Mom's bought another item -that you apparently might like. Keep moving up the path. Two more trainers to -go. Haha, the next guy mentions your rival making fun of his Pokémon. He's not -just a criminal, he's also mean! Shocking. - -PoKeManiac Ron -Level 19 Nidoking (793 EXP) -You get: 1140G - -Keep going north! - -PoKeManiac Ben -Level 19 Slowbro (667 EXP) -You get: 1140G - -Bike down the left side past a few of the first trainers, then go as far left as -you can. You'll come across another tr`ainer: - -Fisher Marvin -Lv 10 Magikarp (42 EXP) -Lv 10 Gyarados (457 EXP) -Lv 15 Magikarp (63 EXP) -Lv 15 Gyarados (? EXP) -You get: 600G - -Now, ride your bike up the west side through the forest. You can use HM01 Cut on -various trees to get Max Ether and TM43 (far northeast). If you go to the far -northwest through the forest, you'll find a house in the middle of nowhere. The -person there will give you TM10 Hidden Power. Now, head back the way that you -came, then north to the Lake of Rage. - -|------------| -|Lake of Rage| -|------------| - -Items found at the Lake of Rage: - -Blackbelt -Max Ether -Red Scale -TM10 Hidden Power -TM43 Secret Power - -If you talk to the people around, they will tell you that something strange has -been going on. If you surf into the Lake of Rage you will find a Gyarados, but -this is no ordinary Gyarados. This is a Level 30 red Gyarados. It is special -because it is an off-brand color "shiny" Pokémon. Color variations of any -Pokémon are typically rare, and therefore cool and desirable. If only people -felt the same way about other people and things that look different in our -society. - -You should save before the battle and make sure you catch it without knocking it -out. If you are using your best Pokémon, you might not want to use its best -moves. Use Great Balls to try to catch it. Once you've caught it, you've now -become a shiny Pokémon trainer. Congrats on this elusive accomplishment. - -After you capture the Red Gyarados you will receive the Red Scale. If you bring -that item to Mr. Pokémon he will give you the EXP Share item. When you reach the -Lake of Rage, talk to someone with a cape on him. He's looking at a sign. When -someone is looking at a sign, it is a sign you should interrupt his train of -thought and talk to him. - -It turns out that he is Lance, the dragon trainer from Blue/Red/Yellow. He's the -fourth Elite Four member, remember? What do you mean you haven't played the -original games? Go and play one of them after you've beaten this game! Anyway, -talk to him, and agree to help him investigate the matter. I have no idea what -this entails but people like when you say Yes, and he did say that you were a -trainer of "considerable skill." You should be quite proud. - -It seems like there's now a problem in Mahogany Town. You can fly back to -Mahogany Town. - -============================================================================== -============================================================================== -============================================================================== - -(c17) --------------------------------------------------------- diff --git a/docs/gold-walkthrough/section-11-team-rocket-hideout-and-mahogany-town-gym.txt b/docs/gold-walkthrough/section-11-team-rocket-hideout-and-mahogany-town-gym.txt deleted file mode 100644 index d464b3c3..00000000 --- a/docs/gold-walkthrough/section-11-team-rocket-hideout-and-mahogany-town-gym.txt +++ /dev/null @@ -1,334 +0,0 @@ ----- 17 > Team Rocket Hideout and Mahogany Town Gym ---- --------------------------------------------------------- - -Video Playthrough Link (Mahogany Town & Lake of Rage): -https://www.youtube.com/watch?v=z5_tkLJ0fvw - -Video Playthrough Link (Mahogany Town Gym): -https://www.youtube.com/watch?v=CoPpbons5bY - -|-------------| -|Mahogany Town| -|-------------| - -Heal at the Pokémon Center and withdraw your Gyarados so that it's in your -lineup. If you head to the middle of the town where a guy is selling things, -you'll find Lance inside basically forcing his way through that house. He'll -show you the secret stairway to Team Rocket's Hideout. - -If you're just leveling up your Starter, your lineup should look something like -this: - -Level 44 Starter Evolution -Level 30 Gyarados -Level 21 Sudowoodo -Level 20 Eevee -Level 21 Tentacool -Level 10 Kenya the Spearow - -If a more balanced lineup, it should now look something like this: - -Level 27 Starter Evolution -Level 28 Sudowoodo -Level 30 Eevee -Level 30 Gyarados -Level 31 Raticate -Level 5 Pidgey - -|-------------------| -|Team Rocket Hideout| -|-------------------| - -Items in the Team Rocket Hideout: - -Ice Heal -Hyper Potion -Nugget -Protein -X Accuracy -TM46 - -You and Lance must now stop Team Rocket's operations, but you're still left on -your own. Head downstairs and left after you do so. But you have been spotted. -You intruder! - -Rocket Grunt (M) -Level 17 Drowzee (368 EXP) -Level 19 Zubat (219 EXP) -You get: 760G - -A second Rocket Grunt runs out to greet you. Hooray. You see, every time you -pass one of those statues, two Team Rocket grunts will fight you. - -Rocket Grunt (M) -Level 16 Zubat (184 EXP) -Level 17 Grimer (327 EXP) -Level 18 Rattata (219 EXP) -You get: 720G - -See? You really should read the dialogue. I'm not saying this as a hypocrite who -doesn't read some of the dialogue himself. Anyway, you'll spot another statute -while heading left. If you pass by that statue, you'll fight the same two Grunts -again, with the Drowzee and Zubat, and the Zubat, Grimer, and Rattata. If you go -down further, you'll fight a Rocket Grunt with many Rattatas. You could say that -he's a Rattata connoisseur. - -Rocket Grunt (M) -Level 16 Rattata (195 EXP) -Level 16 Rattata (195 EXP) -Level 16 Rattata (195 EXP) -Level 16 Rattata (195 EXP) -You get: 640G - -Does Team Rocket have an undercover Zubat, Grimer, and Rattata breeding -operation? I mean, probably. No one wants to admit that publicly, because that's -embarrassing. Anyway, if you head back past that statute and go down, you'll see -a pattern of zig-zags that avoids the statutes and leads to a scientist with -glasses. We know he's smart because he's wearing glasses. Let's fight him and -get him to give us some answer. - -Scientist Jed -Level 20 Magnemite (381 EXP) -Level 20 Magnemite (381 EXP) -Level 20 Magnemite (381 EXP) -You get: 2000G - -Talk to the scientist again. He'll tell you about Warp Panels that can take you -back to the entrance. I told you he was smart. Then go to the computer next to -him and press A to turn off the switch for the statues. Good thing the scientist -isn't a security expert. Now, no more Team Rocket Grunts will swarm you every -time you reach a statue. - -You can now roam around the floor like you own the place. Pick up the X -Accuracy, Hyper Potion, and Nugget. Then make your way around to the -southwest part of the room and take the staircase down to the next floor -(Negative Floor 2). Lance, who is not only a Pokémon champion but also a healer, -will meet with you and heal all your Pokémon. What a nice, kind-hearted guy. -Then head to the right and beat both Rocket Grunts. - -Rocket Grunt (M) -Level 18 Venonat (288 EXP) -Level 18 Venonat (288 EXP) -You get: 720G - -Now head to the right. - -Rocket Grunt (M) -Level 18 Golbat (658 EXP) -You get: 720G - -Head downstairs to Negative Floor 3. Lance says we need two passwords to unlock -the doors on this level. Unfortunately, one of the passwords is not 123456. But -if you beat the right Rocket Grunts, they'll spill the beans. Fortunately for -you, the hero, they are both on this level. Go up and left and you can pick up a -Protein and an Ice Heal. Then loop all the way back down and to the right, go -past the trainer that Lance beat up for more intel, and go up to fight the -scientist. - -Scientist Ross -Level 22 Koffing (537 EXP) -Level 22 Koffing (537 EXP) -You get: 2200G - -Head up to fight the Team Rocket Grunt above that. She says she MIGHT have the -password. She does! You've got to give that to the Team Rocket Grunts. They -aren't liars. They are, in fact, people of integrity. - -Rocket Grunt (F) -Level 18 Ekans (238 EXP) -Level 18 Gloom (508 EXP) -You get: 720G - -Now that you've beat her, you have the password after you talk to her. It's -SLOWPOKETAIL. I know, it's shocking. Then head left for your next battle. - -Scientist Mitch -Level 24 Ditto (313 EXP) -You get: 2400G - -Head left to the final trainer to the left of the table. Fight him. - -Rocket Grunt (M) -Level 19 Raticate (471 EXP) -You get: 760G - -Talk to him again now that you've beat him, and he'll give you his password -(RATICATE TAIL). Get the items (Dire Hit and Full Heal), go up the stairs at the -upperright part of the floor, then head left on Negative Floor 2. Fight the Team -Rocket member that spots you...or that you reach. - -Rocket Grunt (M) -Level 17 Rattata (207 EXP) -Level 17 Rattata (207 EXP) -Level 17 Zubat (196 EXP) -You get: 680G - -Head up the stairs to Negative Floor 1. Head right and then before you reach the -locked door, your rival will come and talk to you. He can't battle you though, -cause Lance just destroyed him. "He said I don't love and trust my Pokémon -enough." Press A at the door to open it and go into Giovanni's office. No idea -where Giovanni is, but there's a Rocket Executive that you can square off with. - -Rocket Executive (M) -Level 22 Zubat (253 EXP) -Level 24 Raticate (595 EXP) -Level 22 Koffing (537 EXP) -You get: 1584G - -He quickly runs off after he loses. Talk to the Murkrow next to the Rocket -Executive and it'll give you the final password, HAIL GIOVANNI. Now head to the -second stairs that you did NOT come out of, and grab TM46 from there, then go -back down to the same floor as where you just beat the executive. - -Head back the way you came, past the spinning Team Rocket member, and go back -upstairs. Go back down to the floor with the gate blocking you from entering the -area with machines inside. We're going ALL the way back to where you met Lance -for the second time within the Team Rocket hideout. It's the second floor where -Lance healed your Pokémon. - -You'll find a passworded door right there. Open it, and you'll quickly get -surrounded by two Team Rocket executives. Now you'd have to take both -executives, but Lance comes just in time. This time, you're fighting the female -Team Rocket boss while Giovanni's apprentice takes on Lance. Spreading the fun? -The battle isn't too difficult though, so just defeat her with any combo. - -Rocket Executive (F) -Level 23 Arbok (724 EXP) -Level 23 Gloom (649 EXP) -Level 25 Murkrow (573 EXP) -You get: 1800G - -She later says that you would be powerful enough to be a Rocket Executive. Well, -not quite. You're strong enough to run all of Team Rocket. But you are a kind -soul. You're here to do good for the world. After the Team Rocket Executives -lose, they run away like the losers that they are. Lance will then talk to you -and he'll want you to help him destroy the Electrodes supplying the base with -electricity. - -Lance runs to the right to take the Electrodes on the right, so you'll take the -left. The Electrodes will typically try to Self Destruct, so move your Sudowoodo -into the first spot temporarily as Sudowoodo can absorb a lot of damage. If -you're lucky enough to have any Electrode not use Self Destruct, you can try to -catch it with a Great Ball. - -Level 23 Electrode (492 EXP) - -Level 23 Electrode (492 EXP) - -Level 23 Electrode (492 EXP) - -The Lake is saved, and Lance thinks you're the hero. Why thank you, Lance. You -are the hero that the world deserves! Lance gives you HM06 Waterpool, an item he -just found lying around in the hideout. Excellent. You'll need the 7th badge -from the Mahogany City Gym to use it, though. Follow Lance's way out to the -stairs, then use the warp next to it to head out of the hideout. You know your -way out now. - -|-------------| -|Mahogany Town| -|-------------| - -Heal at the Pokémon Center, then enter the Mahogany Town Gym. I hope all those -trips to the ice rink as a kid pay off, because you'll be sliding all over the -place. This gym will actually have lower-level Pokémon than Jasmine. Fire and -grass moves will be effective. It's an ice gym! - -Skate to the left to fight the very first trainer. - -Boarder Ronald -Level 24 Seel (513 EXP) -Level 24 Seel (513 EXP) -Level 25 Dewgong (942 EXP) -You get: 1728G - -Slide up twice, then right one, then down one, then left one to fight another -trainer. - -Boarder Douglas -Level 24 Shellder (498 EXP) -Level 25 Cloyster (1087 EXP) -Level 24 Shellder (498 EXP) -You get: 1728G - -Talk to the trainer again to get Gym Leader Pyrce's secret. He meditates under a -waterfall every day. When you're listening to music as you meditate, it's -actually a recording from Pryce's special waterfall. Now slide down, then to the -right. - -Boarder Brad -Level 26 Swinub (433 EXP) -Level 26 Swinub (433 EXP) -You get: 1872G - -Slide once to the right, then down to face another trainer. - -Skier Clarissa -Level 28 Dewgong (1056 EXP) -You get: 2016G - -Head up twice, then slide left to the final trainer. - -Skier Roxanne -Level 28 Jynx (822 EXP) -You get: 2016G - -Slide down and out of the gym to heal at the Pokémon Center before you return to -fight the gym leader. Go to just left of the left pillar then go up, up, right, -down, left, up and finally right. You will be standing in front of the gym -leader in your ice skates. Your time falling down at the ice rink as a kid did -pay off after all! - -Good luck against Pryce. This will be a piece of cake after fighting Jasmine. He -thinks he's amazing because he has been fighting since before you were born. But -you're an underage kid who's been biking around the world on a rental bike. How -could you be stopped? - -If you are focusing on leveling up your starter, your lineup should be around: - -Level 47 Starter Evolution -Level 30 Gyarados -Level 22 Sudowoodo -Level 21 Tentacool -Level 20 Eevee -Level 10 Kenya the Spearow - -If you have a balanced lineup, your lineup should be around: - -Level 31 Raticate -Level 31 Gyarados -Level 31 Bayleef or Quilava, or Croconaw -Level 31 Eevee -Level 31 Sudowoodo -Level 5 Pidgey - ---- -GYM LEADER PRYCE -Level 27 Seel (577 EXP) -Level 29 Dewgong (1093 EXP) -Level 31 Piloswine (1062 EXP) -You get: Glacier Badge, 3,100G, & TM16 (Icy Wind) ---- - -Strategy VERSUS Seel: Use the highest-powered attack from your highest level -Pokémon that is not a fire or water type. Grass moves are ideal. Finally, a gym -that somewhat favors anyone who started with Chikorita! - -Strategy VERSUS Dewgong: Use the highest-powered attack from your highest level -Pokémon that is not a fire or water type. Grass moves are still ideal. - -Strategy VERSUS Piloswine: Pilowsine is a bit tougher. It is higher level and -more defensive. But it is also slow. Use the highest-powered attack from your -highest level Pokémon that is not a fire or water type. Grass moves are still -ideal. - -You're worthy of the badge, and you get the SEVENTH badge. W00t! This raises -your Pokémon's special stats, you get more mooooney, and you have TM16 Icy Wind. -You can now use Whirlpool, once you find that in a dark cavern. Skate your way -to the bottom of the gym, then exit. One more badge to go! - -============================================================================== -============================================================================== -============================================================================== - -(c18) --------------------------------------- diff --git a/docs/gold-walkthrough/section-12-team-rocket-radio-tower.txt b/docs/gold-walkthrough/section-12-team-rocket-radio-tower.txt deleted file mode 100644 index 131255a2..00000000 --- a/docs/gold-walkthrough/section-12-team-rocket-radio-tower.txt +++ /dev/null @@ -1,324 +0,0 @@ ----- 18 > Team Rocket Radio Tower ---- --------------------------------------- - -Video Playthrough Link (Team Rocket in Goldenrod City, & Route 44): -https://www.youtube.com/watch?v=UBCRvlP4Vqw - -You will now get a phone call by Elm. This is informing you that Goldenrod City -has been overrun by Team Rocket! Wow! How could Team Rocket invade two cities at -once? It's truly a spectacular accomplishment for the villains of our story. -Heal at the Pokémon Center, then fly to the city on whatever bird Pokémon you've -decided can carry the weight of yourself and your bike. - -|--------------| -|Goldenrod City| -|--------------| - -You will now see that Team Rocket has pretty much overrun the whole city. You're -now 10 years old, so you're totally capable of retaking the city. Go inside the -Radio Tower. This is where all of the Team Rocket trouble is going on. Head -inside and towards the stairs to fight your first Rocket Grunt foe. - -Rocket Grunt (M) -Level 24 Raticate (595 EXP) -Level 24 Raticate (595 EXP) -You get: 960G - -Easy. Keep heading up the stairs to the second floor and head left. - -Rocket Grunt (F) -Level 26 Arbok (819 EXP) -You get: 1040G - -Talk to the male Rocket Grunt next to her for the third battle. - -Rocket Grunt (M) -Level 21 Rattata (256 EXP) -Level 21 Rattata (256 EXP) -Level 23 Rattata (280 EXP) -Level 23 Rattata (280 EXP) -Level 23 Rattata (280 EXP) -You get: 920G - -Keep moving left. You'll meet yet another Rocket Grunt that's supposed to be -guarding the place. But I don't see him moving his hands around. If he can't do -that, he definitely can't stop Yao Ming from dunking the ball. - -Rocket Grunt (M) -Level 26 Zubat (300 EXP) -Level 26 Zubat (300 EXP) -You get: 1040G - -He claims Team Rocket's comeback is nearing. Keep heading left. - -Rocket Grunt (M) -Level 23 Grimer (442 EXP) -Level 23 Grimer (442 EXP) -Level 25 Muk (840 EXP) -You get: 1000G - -Head up to the third floor. There are four trainers left to fight. Keep moving. -You like to move it move it. - -Rocket Grunt (M) -Level 26 Weezing (963 EXP) -You get: 1040G - -Head up towards the fourth floor. - -Rocket Grunt (M) -Level 23 Koffing (561 EXP) -Level 23 Grimer (442 EXP) -Level 23 Zubat (265 EXP) -Level 23 Rattata (280 EXP) -You get: 920G - -If you talk to him again, he notes that he failed his duties and will be docked -pay for this. Take that! Keep going to the right and you'll encounter a -scientist named Marc. - -Scientist Marc -Level 27 Magnemite (514 EXP) -Level 27 Magnemite (514 EXP) -Level 27 Magnemite (514 EXP) -You get: 2700G - -Two more trainers. Go up to the fourth floor. Keep going to another Rocket -Grunt. - -Rocket Grunt (M) -Level 22 Zubat (253 EXP) -Level 24 Golbat (879 EXP) -Level 22 Grimer (423 EXP) -You get: 880G - -Now go up to the scientist to fight him. - -Scientist Rich -Level 30 Porygon (835 EXP) -You get: 3000G - -Now, head up to the fifth floor. The battle against the mini-boss will begin -now. - -Rocket Executive (M) -Level 30 Koffing (732 EXP) -Level 30 Koffing (732 EXP) -Level 30 Koffing (732 EXP) -Level 32 Weezing (1185 EXP) -Level 30 Koffing (732 EXP) -Level 30 Koffing (732 EXP) -You get: 2160G - -You beat Team Rocket here, but it's not over yet. The Rocket Executive gives you -the Basement Key. The director is now in the Underground Warehouse now. He's -been kidnapped or something. Leave the Radio Tower and then heal at the Pokémon -Center. Then head to the northwestern part of town and enter the -northwesternmost building. This is the other Underground entrance. - -Head down the stairs. While you're in the Underground, you can stop by the -Pokémon Salon to give Eevee a Happiness boost. Now, head down the hallway that -had the locked door you saw WAY back. Enter it now that you have the Basement -Key, then head down the stairs. Save now. - -Try going left and your rival will meet you in another battle. He thinks he can -take care of Team Rocket alone and tries to get rid of you. - -|------------------------------------------| -|Rival | -|Level 30 Golbat (1092 EXP) | -|Level 32 Starter Evolution (1425-1440 EXP)| -|Level 28 Magnemite (534 EXP) | -|Level 30 Haunter (810 EXP) | -|Level 32 Sneasel (904 EXP) | -|You get: 1920G | -|------------------------------------------| - -If you're just leveling up your starter, any high-powered attack should do, -other than not using your same-type attack on the starter evolution and instead -picking a normal type move. - -If you're using a balanced team, Sudowoodo's rock slides would work well against -Golbat and Magnemite. Gyarados would do well against the fire or water starters. -Use your starter's same-type attacks against Haunter. Against Sneasel, you can -use the strongest power moves or consider Sudowoodo's low kicks. - -Your rival has no clue why he lost. He's not the sharpest tool in the shed. He -thinks of Lance's earlier comments and doubts himself. Yay! He still has his -dream that will never succeed. Now, go back and heal at the Pokémon Center, then -take on the Rocket Grunts. Believe me, there are so many Rocket Grunts that -you'll want to. I hope that by this point, you've learned to trust me. I am a -trustworthy person. After you heal, return and take on the first Rocket Grunt. - -Rocket Grunt (M) -Level 27 Rattata (328 EXP) -You get: 1080G - -Move on left. - -Rocket Grunt (M) -Level 23 Muk (772 EXP) -Level 23 Koffing (560 EXP) -Level 25 Rattata (304 EXP) -You get: 1000G - -Move on left... - -Rocket Grunt (M) -Level 24 Koffing (585 EXP) -Level 24 Muk (807 EXP) -You get: 960G - -Now, see the three switches next to each Rocket member you defeated? Go back to -the 1st trainer, and switch the one next to him, then the one next to the 2nd -trainer, then the one to the 3rd trainer. So, you should have turned on the -switches in the same order you beat the trainers. You can now go down from the -third trainer to fight an unexpected foe. - -Burglar Eddie -Level 26 Growlithe (500 EXP) -Level 24 Koffing (585 EXP) -You get: 2112G - -Now turn off switches 3 and 2 (the last two you did), and then snag the item you -see. It is a Full Heal. Exit to the stairs then come back up so the switches are -reset, and turn them on in the opposite direction (3, 2, then 1) from the left -to the right. This will FINALLY clear a route. Head down along the route and -you'll fight another burglar. You might ask, how did the burglars trap -themselves in the middle of a Team Rocket hideout? I don't know, I'm not a mind -reader! I can't read burglars' minds! - -Thard_Verad observed, "How did the burglars get trapped in the Radio Tower -basement? Maybe they were sneaking around when a Grunt flipped the switches? :P" - -Burglar Duncan -Level 23 Koffing (561 EXP) -Level 23 Koffing (561 EXP) -Level 25 Magmar (894 EXP) -You get: 2024G - -Head right to fight the other Rocket Grunt. - -Rocket Grunt (F) -Level 25 Gloom (706 EXP) -Level 25 Gloom (706 EXP) -You get: 1000G - -OK, now go through the door, then keep going on the beaten path and eventually a -Rocket Grunt will spot you. - -Rocket Grunt (M) -Level 24 Raticate (594 EXP) -Level 24 Golbat (879 EXP) -You get: 960G - -Grab the Max Ether to the right and continue on the path up. Next battle! - -Rocket Grunt (M) -Level 26 Grimer (501 EXP) -Level 23 Weezing (852 EXP) -You get: 920G - -Now, head up the stairs to grab the Amulet Coin, which after equipped will be -able to DOUBLE the amount of gold you get from each battle on a Pokémon. You'll -finally be a rich 10-year old! Head back down the stairs and then follow the -path until you reach the boss of this freak place. - -Rocket Grunt (M) -Level 25 Koffing (610 EXP) -Level 25 Koffing (610 EXP) -You get: 1000G - -Talk to him again and he'll tell you they're basically holding on until the -return of Giovanni. Carry on and take TM35, which is next to the Director. Talk -to the Director who is now freed thanks to you. You receive a Card Key now. - -Remember the part of the Radio Tower that hasn't been liberated by you yet? Now -it's time to retake that area. So your job is to save the Pokémon worldwide. -Exit by the way you came. When you get blocked, use the switch. Heal at the -Pokémon Center. Access the PC in here and deposit a few items from your first -pocket of your pack, cause it's obvious you'll be getting even more items. -Deposit EVERYTHING you won't be using IN any battles. - -Then, return to the Radio Tower. Head up to Floor 2, then Floor 3. Head to the -very right part of the room where you'll spot a shutter thing. Go to the switch -and put in the key. Head right to face the first Rocket. - -Rocket Grunt (M) -Level 24 Raticate (595 EXP) -Level 26 Koffing (634 EXP) -You get: 1040G - -Go up the stairs to Floor 4, then fight the next Grunt. - -Rocket Grunt (F) -Level 21 Ekans (279 EXP) -Level 21 Ekans (279 EXP) -Level 24 Gloom (678 EXP) -Level 23 Oddish (384 EXP) -You get: 960G - -Attempt to go up to the fifth floor and you'll have your third battle. No one -apparently has a name in Team Rocket. They're all anonymous. They must remove -people's names at the time of initiation. Sad times. - -Rocket Executive (M) -Level 36 Golbat (1318 EXP) -You get: 2592G - -And so goes the Team Rocket fortress. Whee! Advance on to Floor 5 and fight the -same boss you fought before. It's amazing how easy Team Rocket is to beat, -right? Aww, they're such softies inside. ^_^. Here, you'll want to use primarily -the strongest attacks from your starter Pokémon, unless it's grass, in which -case you should use normal. - -Rocket Executive (F) -Level 32 Arbok (1008 EXP) -Level 32 Murkrow (733 EXP) -Level 32 Vileplume (1261 EXP) -You get: 2304G - -She still wishes you were part of Team Rocket. Why wouldn't you use your -strength to do bad things instead? But we all want things we can't have. - -Go up to the other Executive. You may recall that Lance beat him last time. He -wants to call back Giovanni by taking over the Radio Tower. You'd think that -Team Rocket leaders could afford cell phones, but they're not the tech savvy -times. You can stop him with water, ground, rock, or fighting moves. Sudowoodo's -rock slides can be super effective. - -Rocket Executive (M) -Level 33 Houndour (805 EXP) -Level 33 Koffing (805 EXP) -Level 35 Houndoom (1530 EXP) -You get: 2520G, Demise of Team Rocket - -Like Giovanni, the Rocket Executive disbands Team Rocket. Well, good job! There -goes the menace of Team Rocket. The director will come up to greet and thank -you. He gives you a Clear Bell. Now you can exit the Radio Tower. - -If you're just leveling up your Starter, your lineup should look something like -this: - -Level 53 Starter Evolution -Level 30 Gyarados -Level 21 Sudowoodo -Level 20 Eevee -Level 21 Tentacool -Level 10 Kenya the Spearow - -If a more balanced lineup, it should now look something like this: - -Level 35 Starter Evolution -Level 34 Sudowoodo -Level 33 Gyarados -Level 33 Raticate -Level 32 Eevee -Level 9 Pidgey - -============================================================================== -============================================================================== -============================================================================== - -(c19) ------------------------------------------------ diff --git a/docs/gold-walkthrough/section-13-ice-path-and-blackthorn-city-gym.txt b/docs/gold-walkthrough/section-13-ice-path-and-blackthorn-city-gym.txt deleted file mode 100644 index 4d602247..00000000 --- a/docs/gold-walkthrough/section-13-ice-path-and-blackthorn-city-gym.txt +++ /dev/null @@ -1,317 +0,0 @@ ----- 19 > Ice Path and Blackthorn City Gym ---- ------------------------------------------------ - -Video Playthrough Link (Team Rocket in Goldenrod City, & Route 44): -https://www.youtube.com/watch?v=UBCRvlP4Vqw - -Video Playthrough Link (Ice Path): https://www.youtube.com/watch?v=evrwXY9s1uo - -Video Playthrough Link (Blackthorn City Gym & Dark Cave): -https://www.youtube.com/watch?v=pWBRqXMdut8 - -|--------| -|Route 44| -|--------| - -Pokémon on Route 44: - -Poliwag (water) - -Items on Route 44: - -Burnt Berry -Ultra Ball - -Take the elevator and stairs back down, leave the building, fly back to Mahogany -Town, heal at the Pokemon Center, and head east onto Route 44. - -Psychic Phil -Level 24 Natu (565 EXP) -Level 24 Kadabra (807 EXP) -You get: 832G - -Head up to grab the Burnt Berry from the tree, then the item east of that -trainer (Max Repel). Continue heading east and talk to the Fisherman. Also, you -get another call from your Mom. Should you really have trusted my advice at the -beginning of this walkthrough to give your mom access to your bank funds? No -comment. - -Fisher Edgar -Level 25 Remoraid (417 EXP) -Level 25 Remoraid (417 EXP) -You get: 1000G - -Keep heading east... - -Cooltrainer Cybil -Level 25 Butterfree (854 EXP) -Level 25 Bellossom (985 EXP) -You get: 1200G - -Keep heading east along this strange route after you beat her... - -Cooltrainer Allen -Level 27 Chameleon (820 EXP) -You get: 1296G - -Wow, that's two straight trainers that give you respect. You got game. Go pat -yourself on the back. Continue along the path, and then when you can go up, grab -the Ultra Ball. Then head along the northern route around the lake and you'll -find more trainers. - -PoKeManiac Zach -Level 27 Rhyhorn (780 EXP) -You get: 1620G - -Then talk to the fisherman to the west. He screams at you for losing a Poliwag -that he was trying to catch. Kind of sounds like it was his fault for not paying -attention, am I right? - -Fisher Wilton -Level 23 Goldeen (546 EXP) -Level 23 Goldeen (546 EXP) -Level 25 Seaking (910 EXP) -You get: 1000G - -Now head back east... - -Bird Keeper Vance -Level 25 Pidgeotto (604 EXP) -Level 25 Pidgeotto (604 EXP) -You get: 600G - -Now, you'll be heading into the Ice Path. Good luck with the puzzles! You got -this! By this, I mean a puzzle game in the middle of a role-playing game. Fly -back to Mahogany Town to heal at the Pokémon Center. Make sure you have a -Pokémon with HM Strength, because your 10-year old character isn't buff enough -to move boulders physically or with his mind. Then ride all the way east back -down to the route to a cave called the Ice Path. - -|--------| -|Ice Path| -|--------| - -Pokémon found in the Ice Path: - -#042 Golbat -#124 Jynx -#220 Swinub -#225 Delibird (Silver only) - -Items found in the Ice Path: - -Full Heal -Iron -Nevermeltice -PP Up -HM07 Waterfall -TM44 Rest - -The Ice Path has several puzzles. Follow the ice path. The first one's simple: -right, down, right, down. Then, go up the stairs, follow the route, go down the -stairs, follow the route, and you'll hit your first ice maze. Go up, left, up, -right, up, right, down, left, up, left, down, right, down, right, up, and then -right. Also, get ambushed by Golbats and Swinubs. Aren't those Swinubs so cute? -Maybe it's not so bad to get ambushed by them after all. - -When you complete this first ice maze, get HM07 Waterfall. To do so, head right -(from the bottom square), up, right, left, down, left, and right to take the HM. -Afterwards, just go left, down, and left to get out of the area. HM07 Waterfall -is a good move that you can teach to water types later. Head up the stairway and -along the icy path. Head down the stairs and then down the ladder. - -The next puzzle is in the next room. There are four boulders and four holes that -the boulders can fall into. You'll want a Pokémon with the HM Strength. - -Let's get the first boulder down the southwest hole. Move the bottom boulder -right, then 6 squares down, 3 squares to the left, 1 square up, 1 square to the -left, and 2 squares up. There goes one boulder. - -Now let's get the second boulder. Go all the way to the right and then up. Move -that boulder 1 square to the left, 4 squares down, 1 square to the right, 2 -squares down, and then 5 squares down. - -The bottom path has another boulder. You've moved the right one down, now let's -move the left one up. Push this boulder two up, then one left, and two up. -Simply push it two to the right, then up, and then left to smack it in. Fall -through the hole below it and then go left, up, and right to snag the item, Full -Heal! Head down the ladder then rock smash the rock near you. Grab the -Nevermeltice, then head right and go up, down the stairs, and with the ladder -going down, to the next stage. - -Finally, there's the remaining one. Move that one two to the left, then three -up, then push that one back down one. Then push it left one and down one. - -Head to the right side, and then you'll want to slide down to the part before -you fall off. Find the stairway, then go the opposite direction and slide down. -Slide right and you have the TM for Rest, TM44. Now go left and up, then go back -around. This time, go up the stairway and down the other one and you'll reach an -island of solid ice in which you can go up the ladder. - -Move towards the right and down the stairway. Then go left, down, left, and then -down. Simply head left on the weird slippery ice path to get that Iron. Once you -head back out, go right, up, right, down, left, up, left, up, and then left. -Woot. You're done. Climb up the steps and go up the ladder. - -Go up even more steps to get the Protein, then slide down at the very left to -grab a PP Up. Slide back up and drop anywhere in the middle of the area and then -go down from there. Walk out of the cave and finally, into Blackthorn City! - -|---------------| -|Blackthorn City| -|---------------| - -Head southwest and heal your Pokémon at the Pokémon Center. In the northeast -house, there's a lady that would trade her Rhydon for a Dragonair. In the house -in the south, the Move Deleter can delete HM moves from your Pokémon. - - -|---------------| -|Blackthorn City| -|---------------| - -When you reach Blackthorn City, make sure you restock on Ultra Balls so you're -back up to 30 Ultra Balls. Then heal your Pokémon at the Pokémon Center and -deposit any extraneous items. In the northeast house, there's a lady that would -trade her Rhydon for a Dragonair. In the house in the south, the Move Deleter -can delete HM moves from your Pokémon. - -Head up to the top of the city to reach the gym. This is a dragon-based gym, the -first of its kind! - -If you're just leveling up your Starter, your lineup should look something like -this: - -Level 57 Starter Evolution -Level 30 Gyarados -Level 22 Sudowoodo -Level 21 Tentacool -Level 10 Kenya the Spearow -Level 6 Bellsprout - -If you have a balanced lineup, your team might look like this: - -Level 36 Gyarados -Level 35 Raticate -Level 35 Quilava -Level 15 Dratini -Level 4 Wooper - -Head left to fight your first trainer battle. If you are just using your starter -Pokémon, the HM move Strength should take out every trainer in the gym, -including the gym leader. The advice below would be for a more balanced lineup. -If you're doing that, Gyarados should do pretty good damage with that Dragon -Rage. - -Cooltrainer Paul -Level 34 Dratini (487 EXP) -Level 34 Dratini (487 EXP) -Level 34 Dratini (487 EXP) -You get: 1632G - -Move up above him and head up the stairs. You'll find out you need to move even -more boulders. Use any Pokémon that you have that has Strength. For the two -above you, move the right one up two, and the left one down two and into the -hole. Now head down to fight... - -Cooltrainer Fran -Level 37 Seadra (1228 EXP) -You get: 1776G - -You'll see two more boulders nearby. Push the right one down three, and the left -one up nine, then to the right two to make that fall. Head up to fight the last -dragon trainer on this level. - -Cooltrainer Cody -Level 34 Horsea (604 EXP) -Level 36 Seadra (1195 EXP) -You get: 1728G - -Push the boulder right next to you three to the right, then move the other one -down one. Easy. Now, head down the stairs you saw earlier next to the second -hole you encountered. Head two up, then to the left to fight that trainer. - -Cooltrainer Mike -Level 37 Dragonair (1141 EXP) -You get: 1776G - -Head across the boulder path you just created and then to the final trainer -before Clair. - -Cooltrainer Lola -Level 34 Dratini (487 EXP) -Level 36 Dragonair (1110 EXP) -You get: 1728G - -Now that you've finished beating everyone, use the warps to return to the -entrance to heal at the Pokémon Center, if needed. - -Save before you battle, and make sure you have a few Revives, Full Heals, and -Hyper Potions in case you need it. Clair says she can hold her own against the -Elite Four as the dragon master. We'll see about that, my good friend! - ---- -GYM LEADER CLAIR -Level 37 Dragonair (1141 EXP) -Level 37 Dragonair (1141 EXP) -Level 40 Kingdra (1773 EXP) -Level 37 Dragonair (1141 EXP) -You get: Rising Badge, 4,000G, TM24 (Dragon Breath) ---- - -Strategy VERSUS Dragonairs: Use HM Strength to do a lot of damage, or Body Slam -if you have that on a Meganium. Try not to use grass, fire, or water moves, as -the Dragonairs resist those moves. Beware: Dragonairs can use Fire Blast, which -can do a decent amount of damage against a Meganium, and Thunder Wave, which can -paralyze your Pokémon from being able to attack. - -Strategy VERSUS Kingdra: Kingdra is a water Pokémon that can use dragon moves -like Dragon Pulse. Grass and normal moves are ideal here from your starter. - -This would be your 8th badge, but Clair won't give you it just yet. Apparently, -she's a sore loser and she won't give you the badge until you get the Dragon -Fang in the Dragon's Den. Now, leave the gym. - -|------------| -|Dragon's Den| -|------------| - -Pokémon in Dragon's Den: - -#129 Magikarp -#147 Dratini - -Items in Dragon's Den: - -TM24 Dragonbreath - -The Dragon's Den is located across the sea above the gym. Make sure you have in -your lineup a water Pokémon that can use the HMs Surf and Waterfall. Heal at the -Pokémon Center, then head in talking to the bodyguard in front of it. Does that -guy just stand out there every day? That's a pretty boring work shift. Go in, -head down the ladder, and go through the door, and then you'll get to battle. - -Surf around counterclockwise from the left side and you'll spot a Whirlpool. -Remove it, then keep surfing eastward. Eventually, you'll find Dragon Fang just -lying on the ground. Clair will then come and give you the Rising Badge and TM24 -(Dragon Breath). She insists that it's not her own breath. Maybe it's the breath -one of our Dragonairs. No one will ever know. It's a state secret. - -Clair then gives you instructions to go to the Pokémon League, by heading from -New Bark Town and surfing east to the Pokémon League. She tells you that she'll -feel even worse if you lost at the Pokémon League because she lost to you so you -must win. - -Now head back out the way you came and when you leave the cave (at the -entrance), Professor Elm will give you a call. If you swing by the lab, you'll -get something really awesome. However, Elm still has his last starter Pokémon -just sitting out there, waiting to be stolen by your thief Rival again. Elm -doesn't really know how to properly store his valuables. Head to the Pokémon -Center and heal your Pokémon. - -============================================================================== -============================================================================== -============================================================================== - -(c20) ----------------------------------------------- diff --git a/docs/gold-walkthrough/section-14-ho-oh-gold-and-lugia-silver.txt b/docs/gold-walkthrough/section-14-ho-oh-gold-and-lugia-silver.txt deleted file mode 100644 index a1720a16..00000000 --- a/docs/gold-walkthrough/section-14-ho-oh-gold-and-lugia-silver.txt +++ /dev/null @@ -1,193 +0,0 @@ ----- 20 > Ho-Oh (Gold) and Lugia (Silver) ---- ----------------------------------------------- - -Video Playthrough Link (Whirl Islands to Lugia): -https://www.youtube.com/watch?v=cfCBHjF5hFU - -Make sure you buy Ultra Balls at the Pokemart so you have at least 30 Ultra -Balls. You will need them to catch a rare legendary Pokémon and they have very -low catch rates. You should also consider having a few Revives and Hyper -Potions, too. Now, you can either head to Ecruteak City (if you have Gold -version) or Olivine City (Silver version). I will cover both below. - -At this stage, you can only get Ho-oh or Lugia depending on what version you -have. You'll have a chance to catch the other later in the game. - -Gold = Ho-oh -Silver = Lugia - -Thard_Verad adds, "Apriballs - Three of them are broken: Fast Ball only works on -three species (NOT Raikou, Entei, or Suicune); Love Ball works on same gender, -not opposite; Moon Ball checks the wrong item and therefore never gets any -boosted catch rate. Level, Lure, Heavy, and Friend all work as intended." - -|------------------------| -|Tin Tower - Gold Version| -|------------------------| - -Pokémon in Bell Tower: - -Rattata - -Go back to Ecruteak City and then go into the Bell Tower entry house. Talk to -the man and he'll let you through because you have the Ecruteak City Badge. Then -follow up the trail to the tower itself. - -Go up and to the left and another bald man will let you through because you have -the beautiful Rainbow Wing. You can then go up the ladder. Keep going and wild -Rattatas will start converging on you like zombies. Consider a Max Repel or two -to drive away all the zombies. Then go up the ladder, hop left for a Full Heal, -then hop all the right. Then hop all the way left, then keep hopping right. On -the middle platform, you might get stuck a bit because you don't see the next -jump easily, but it's the second square from the top. You'll make it to the -northeast side and you can go up the ladder. You are now a true bunny. Are you a -bad bunny? - -On the next level, jump down, left, down, left, down, down to get to the Ultra -Ball. Jump right, down, left, down, down, and you'll get to a PP Up. If you go -up the ladder, it won't get you anywhere, so no need to do that. You can -continue with jumps left (x5), down, right (x2), down, left (x2), down, left -(x2) to get an Escape Rope. You'll be next to another ladder. That ladder will -also take you nowhere. Jump right, right, left, left, and you'll be on the -northwest side where you do want to take that ladder up. - -On the next level, jump right twice, go all the way to the right and down, jump -down, left, left, and you'll get a Rare Candy. You can then return to the -previous level's bottom right corner and go all the way back up to the top left -corner to get back up to the beginning of the level. Then jump right and right -again, then down, left, down (x2), and take the right down (x4) up to the next -level (Left down takes you back to the bottom left ladder from the previous -level). - -On the next level, go up and you'll get a Max Potion. Then go right across the -bridge, up, and then left across the upper (not middle) bridge to get a Full -Heal. Then hop down twice to get up the next ladder. - -On the next level, go up, go through the next two warps, jump on the top side -left (x8), down (x3), right (x3), and through that warp to get to a Max Elixir. -Go back through the warp and jump right (x4) to return to the start. Warp back -through the top right warp and then back through the other warp you went -through. You'll be back to the very start of this paragraph. Hop to the right -(x4) to get a Max Revive. Then hop down (x5), go left and down, warp, cross the -bridge down, and warp again. - -Go left and you'll get a HP Up. Keep going left and down and warp again. Then -cross the bridge up left and take the warp. Go up the ladder, cross the planks -down, then go up the ladder and you'll be in front of a Level 40 Ho-Oh. I -suggest that you save before you fight Ho-Oh because this is the only chance -you'll be able to get Ho-oh in the entire game. If you knock it out on a -critical hit, lose all your Pokémon and black out, or run out of balls to throw -at it, you would want to return to your save and retry. - -The Ho-Oh in-game music gives off great vibes. As we all know, the vibes are -what matters. We must have the vibes. Ho-Oh is a fire/flying type Pokemon with -Safeguard and Ancient Power (damage w/ stat boosts), Punishment, and Sacred -Fire. Use your highest level Pokémon to whittle down Ho-Oh's HP first without -knocking it out, then try to throw as many Ultra Balls as you can at it. - -Because the catch rate is only about 2% at low health, even with Ultra Balls, -this may involve a dozen or more resets. Ultimately, you'll want to maximize -your chances by throwing many Ultra Balls at it. You'll want to do so when it is -lower on HP (yellow or red on the HP meter), and ideally when it is asleep. Use -Revives and Hyper Potions as is helpful to keep your Pokémon alive to keep -throwing balls at it. However, all things even, focus on using Ultra Balls when -Ho-Oh is low on health, even when it could knock out your Pokémon, to increase -the number of chances yo have. You can slightly increase your catch chances if -you put Ho-Oh to sleep or paralyze it. - -After you catch Ho-Oh, go back down the stairs and then you can use an Escape -Rope to leave. You can then fly back to Blackthorn City. Be sure to also -withdraw Ho-Oh at a Pokémon Center so that you have Ho-Oh as part of your -lineup. - -|------------------------------| -|Whirl Islands - Silver Version| -|------------------------------| - -Fly to Olivine City. Make sure you have a Pokémon on your team that has Flash, -like Bellsprout. Also make sure you teach HM06 Whirlpool to a Pokémon. I suggest -Tentacool or Krabby, who can use Strength, Surf, Whirlpool, and Waterfall. -You'll need eight badges in order to use Whirlpool. - -Pokémon at the Whirl Islands: - -#041 Zubat -#086 Seel -#098 Krabby -#116 Horsea -#117 Seadra - -Items at the Whirl Islands: - -Calcium -Escape Rope -Guard Specs -Max Revive x2 -Ultra Ball - -Surf southwest and west until you reach the northeast cave of the Whirl Islands. -I suggest using a Max Repel to reduce the number of Tentacool encounters on the -way there. The whirlpool is just northwest of where you fought Swimmer Kara. You -can press A on any whirlpool to remove it temporarily from the map. It's -incredible how much stamina these swimmers have, just staying in the water days -after you last fought them. - -Once you find the northeast cave entrance, enter it and you'll see darkness. Use -HM Flash, then head up and leap the ledge to the right on that bike. You'll see -a Ultra Ball on the cliff. To get to it and continue on your journey, leap down -another ledge to the right, go up the steps, grab the Max Revive, and head down -the ladder. Head south, then jump to the left. Keep jumping down and you'll get -an Escape Rope. Woo! - -Now go down and then left, then up the ladder. You can now head out the door to -breathe, but that's pointless. Why would you do that? Instead, surf east, then -grab the Guard Specs. Then head down the ladder, head up along the path, head -down, and then up the cliff. It's like a roller coaster, I know. - -There's yet another item here. It's a Calcium. Now, on that cliff, head down to -the ladder and take it. Now, go down out the door. That's most of the items in -the Whirl Islands. - -Now that you've gone from the northeast to the southeast portion of the Whirl -Islands, go counterclockwise and surf your way southwest out of the reefs and -then Whirlpool out. Head east, then north, and you'll find Swimmer Kara again. -Take that Whirlpool out and head back into the entrance that you originally came -from. - -Use Flash again. Drop down the first cliff again, but this time, drop down the -top cliff rather than the bottom cliff. Then head down the ladder. Continue -along the path south and head down that ladder. Grab the Max Revive to the left -of you, then surf south. There are two items on the cliff but you can't reach -those. - -Instead, surf down the waterfall, then head left and through the door. Head in, -and surf north to Lugia. Save before you interact with Lugia. I suggest that you -save before you fight him because this is the only chance you'll be able to get -Lugia in the entire game. If you knock it out on a critical hit, lose all your -Pokémon and black out, or run out of balls to throw at it, you would want to -return to your save and retry. - -Because the catch rate is only about 2% at low health, even with Ultra Balls, -this may involve a dozen or more resets. Ultimately, you'll want to maximize -your chances by throwing many Ultra Balls at it. You'll want to do so when it is -lower on HP (yellow or red on the HP meter), and ideally when it is asleep. - -Focus on using Ultra Balls when Lugia is low on health, even when it could knock -out your Pokémon. Use Revives and Hyper Potions as is helpful to keep your -Pokémon alive to keep throwing balls at it. You can slightly increase your catch -chances if you put Lugia to sleep or paralyze it. - -Thard_Verad adds, "Lugia is just heavy enough for Heavy Balls to have higher -catch rates than Ultra Balls. Give those a try." - -After you catch Lugia, you can use an Escape Rope to leave. You can then fly -back to Mahogany Town. Be sure to also withdraw Lugia at the Pokémon Center so -that you have Lugia as part of your lineup. - - -============================================================================== -============================================================================== -============================================================================== - -(c21) ------------------------------------------ diff --git a/docs/gold-walkthrough/section-15-mount-mortar-and-dark-cave.txt b/docs/gold-walkthrough/section-15-mount-mortar-and-dark-cave.txt deleted file mode 100644 index cea5c41f..00000000 --- a/docs/gold-walkthrough/section-15-mount-mortar-and-dark-cave.txt +++ /dev/null @@ -1,222 +0,0 @@ ----- 21 > Mount Mortar and Dark Cave ---- ------------------------------------------ - -Video Playthrough Link (Mt. Mortar): https://www.youtube.com/watch?v=eOMK3CvQT8Q - -Video Playthrough Link (Blackthorn City Gym & Dark Cave): -https://www.youtube.com/watch?v=pWBRqXMdut8 - -Video Playthrough Link (Route 45 Part 1): -https://www.youtube.com/watch?v=y-5IXC6iwII - -Video Playthrough Link (Route 45, 46, & Tohjo Falls): -https://www.youtube.com/watch?v=VGAzyc8aM8o - -Video Playthrough Link (Route 27 Part 1): -https://www.youtube.com/watch?v=eL4FDqyquSY - -Video Playthrough Link (Route 27 Part 2): -https://www.youtube.com/watch?v=dwPL_w2_xE0 - -Video Playthrough Link Route 26 & Victory Road): -https://www.youtube.com/watch?v=Jign264AzYI - -Video Playthrough Link (Elite Four & Champion Battles): -https://www.youtube.com/watch?v=VcFLopf6A28 - -|-------------| -|Mahogany Town| -|-------------| - -Head to the Pokémon Center and heal your Pokémon. Make sure you have Pokémon -that can use the HMs Fly, Flash, Surf, and Waterfall (and Strength if you want -to do the optional areas which can get you additional items). You'll also want -to clear up one spot (so you only have 5 Pokémon) so that you have space to -accept a Pokémon in Mt. Mortar. Also carry a few Max Repels and Escape Ropes, -which you can get from the Pokemart. Then, take Route 42 westward. Alternative, -fly to Ecruteak City, then take Route 42 eastward. Either way, you'll come -across three cave entrances to Mt. Mortar. - -|----------| -|Mt. Mortar| -|----------| - -Pokémon in Mt. Mortar: - -#020 Raticate -#067 Machoke -#075 Graveler -#118 Goldeen -#119 Seaking -#236 Tyrogue (only one!) - -Items in Mt. Mortar: - -Carbos -Dragon Scale -Escape Rope x2 -Full Restore -Hyper Potion x2 -Max Ether -Max Potion -Max Revive -PP Up -Rare Candy -TM40 Aerial Ace - -First, Mt. Mortar has three entrances: a west entrance, a middle entrance (Surf -required), and an east entrance. Regardless of which entrance you use, you'll -want to use HM Flash, or you won't be able to see anything. - -The west entrance and east entrance lead to each other, as well as a giant room -where you can zig zag around, use Strength, and get two items, to include a -Hyper Potion and Escape Rope. I'd suggest using a Max Repel to get through this -section if you want to check it out, because there are a lot of random Pokémon -encounters. - -Where you really want to go is through the central entrance, which requires your -surfing east from either the west entrance (easier), or by surfing west from the -east entrance. Even if you want to catch some of the Pokémon in Mount Mortar, -I'd still suggest using at least one Max Repel to avoid some of the numerous -random Pokémon encounters. - -If you head up and down the ladder, then go down the steps, you can surf -clockwise around and reach a Hyper Potion and a Max Ether. - -The path where you can get to a new Pokémon is in fact through the central -entrance, but instead of going up and down the ladder, you take one step to the -side, go as far left/right as you can, and then Surf upward. You'll spot a big -waterfall. Use Waterfall on it, then surf all the way to the top, get back on -land, and then head through the door. - -Head back down the cliff, then go right. You'll hit a dead end, so surf to the -right. There's two routes. The left one takes you to a dead end but to a Max -Potion. Go all the way back to the entrance of this cave area, then surf left to -a Rare Candy. Go back to the lake where you can land to the left or to the right -and take the right path this time. Surf across that lake, go on the path all the -way to the left, then surf up and land again to the right. - -Keep heading right. There's two routes. Take the bottom one to get TM40 first, -then go to the top one to continue. Head all the way up and surf up. Grab the -Escape Rope, then continue along the path around the bend to the left. When you -see an item, go around to grab the Dragon Scale, then continue heading left. -Head left all the way up the ladder...phew, that's FINALLY two floors. Two more -to go. - -Run/bike down the steps, then head around and grab the free Iron on the cliff. -Head back to just before the steps and head east. Go along the path and take the -left intersection down the cliff. You'll see another item. Get the Max Revive, -then go back up and continue left. At the end of the path you can head down the -ladder. - -Now that you're back on an isolated area of Floor 2, head east and talk to the -Karate King. If you beat him, you'll get to return to your beloved babysitting -duties with the award of a new Pokémon! - -Blackbelt Kiyo -Level 34 Hitmonlee (1012 EXP) -Level 34 Hitmonchan (1020 EXP) -You get: 816G - -After your victory, you will be given a free Tyrogue. This is the baby version -of Hitmontop, Hitmonlee, and Hitmonchan. If you go down the stairs from the -ladder you came through, you can move the rock with HM Strength to get out. But -make life easier and use one of the Escape Ropes you brought to get out of this -place. - -|---------------| -|Blackthorn City| -|---------------| - -Fly back to Blackthorn City. You'll now be heading down to the Dark Cave, which, -is, well, dark. Bring a Pokémon that can use HM05 Flash, then use it so you can -actually see what's in Dark Cave. Or just stumble around blindly. I guess it's -your prerogative. - -Also, bring a Pokémon that can use HM03 Surf because your main character can't -swim across water. I guess at the young age of 10 years old, s/he just didn't -have the chance to take any swimming lessons. When you're ready, you'll want to -go south towards Route 45. - -If you're just leveling up your Starter, your lineup should look something like -this: - -Level 57 Starter Evolution -Level 30 Gyarados -Level 22 Sudowoodo -Level 21 Tentacool -Level 10 Kenya the Spearow -Level 6 Bellsprout - -If you have a balanced lineup, your team might look like this: - -Level 36 Gyarados -Level 35 Raticate -Level 35 Quilava -Level 15 Dratini -Level 4 Wooper - -|---------| -|Dark Cave| -|---------| - -Pokémon found in the Dark Cave: - -#041 Zubat -#042 Golbat -#074 Geodude -#075 Graveler (it explodes and goes BOOM) -#129 Magikarp (only on water, it can't flop onto land) -#202 Wobbuffet - -Items found in the Dark Cave: - -Blackglasses -Full Heal -Hyper Potion -Revive -TM 13 - -Okay, instead of taking a Route 45 path, enter the Dark Cave to the left before -there are even any jumps. Also, bring a Pokémon that can use HM03 Surf because -your main character can't swim across water. I guess at the young age of 10 -years old, s/he just didn't have the chance to take any swimming lessons. -Consider using a Max Repel at some point in Dark Cave. There are cool Pokémon to -catch, such as the rarer and wonderful Wobbuffett, but there are only so many -Geodudes you can look at. - -Follow the route down to the sea, then surf down, then go up. Go left and down -the steps, then head left and up to that guy. He'll give you -Blackglasses because he wants you to forget that you saw him. Dark-type moves -are increased in power with it. - -Now, you can use your best Tony Hawk skateboarding skills by jumping off ledges. -Jump all the way south and then head out the door to another part of the cave. -Surf down, then rock smash the rock next to you. Grab the Potion you see, then -head out the door onto Route 31. Fly back to Blackthorn City, heal at the -Pokemon Center, then head back south to actually take Route 45. - -If your lineup leans on your starter, it might look something like this: - -Level 57 Starter Evolution -Level 30 Gyarados -Level 22 Sudowoodo -Level 21 Tentacool -Level 10 Kenya the Spearow -Level 6 Bellsprout - -If you're going for a balanced lineup, it might look something like this: - -Level 15 Dratini -Level 5 Togepi -Level 10 Wooper -Level 36 Eevee -Level 36 Raticate -Level 36 Typhlosion - -============================================================================== -============================================================================== -============================================================================== - -(c22) ----------------------------------------- diff --git a/docs/gold-walkthrough/section-16-routes-45-46-26-and-27.txt b/docs/gold-walkthrough/section-16-routes-45-46-26-and-27.txt deleted file mode 100644 index ac989a59..00000000 --- a/docs/gold-walkthrough/section-16-routes-45-46-26-and-27.txt +++ /dev/null @@ -1,394 +0,0 @@ ----- 22 > Routes 45, 46, 26, and 27 ---- ----------------------------------------- - - -Video Playthrough Link (Route 45 Part 1): -https://www.youtube.com/watch?v=y-5IXC6iwII - -Video Playthrough Link (Route 45, 46, & Tohjo Falls): -https://www.youtube.com/watch?v=VGAzyc8aM8o - -Video Playthrough Link (Route 27 Part 1): -https://www.youtube.com/watch?v=eL4FDqyquSY - -Video Playthrough Link (Route 27 Part 2): -https://www.youtube.com/watch?v=dwPL_w2_xE0 - -Video Playthrough Link Route 26 & Victory Road): -https://www.youtube.com/watch?v=Jign264AzYI - -Video Playthrough Link (Elite Four & Champion Battles): -https://www.youtube.com/watch?v=VcFLopf6A28 - -|--------| -|Route 45| -|--------| - -Pokémon found in Route 45: - -#074 Geodude -#075 Graveler -#207 Gligar (Gold only) -#216 Teddiursa (Gold only) -#227 Skarmory (Silver only, legendary catch rate) -#231 Phanpy (Silver only) - -Items found in Route 45 & 46: - -Berry -Dire Hit -Elixer -Max Potion -Mystery Berry -PRZCure Berry -Revive -X Special - -There are two roads diverged in a yellow wood. If you take the one less traveled -by, it will make all the difference. - -Haha, just kidding. But Routes 45 and 46 do have forks in the road. If you want -to fight every trainer and grab every item, you can fly back to Blackthorn City -to go down the other forks in the road. - -LEFT PATH: - -The left path is an entrance to the Dark Cave. You can keep going down for an -Elixir. There's also one trainer you can battle. - -Hiker Parry -Level 29 Onix (670 EXP) -You get: 1856G - -MIDDLE PATH: - -The right path splits into both a middle path and a right path. - -Hiker Erik -Level 24 Machop (385 EXP) -Level 24 Machop (385 EXP) -Level 27 Graveler (774 EXP) -You get: 1728G - -RIGHT PATH: - -Cooltrainer Ryan -Level 25 Pidgeot (920 EXP) -Level 27 Electrabuzz (901 EXP) -You get: 1296G - -There's a Max Potion south of Ryan. There's one more trainer: - -Cooltrainer Kelly -Level 27 Marill (920 EXP) -Level 24 Warortle (735 EXP) -Level 24 Warortle (735 EXP) -You get: 1152G - -COMBINED PATH: - -The path combines after Cooltrainer Kelly, then splits off again. The left path -has nothing, so take the right path. - -RIGHT PATH: - -Blackbelt Kenji -Level 28 Machoke (776 EXP) -You get: 672G - -Talk to him again and he will give you his phone number. Look at you, getting -everyone's digits just like that. You're so cool. The path splits off again here -into a left and a right path. - -LEFT PATH: - -You can grab a Revive and a Nugget. Nom nom. Oh wait, these are gold nuggets, -not chicken nuggets. Please save them for resale instead. There are also two -trainers here. - -Hiker Timothy -Level 27 Diglett (468 EXP) -Level 27 Dugtrio (885 EXP) -You get: 864G - -Why do I do things? Because they're there. I guess it's a thing to do. - -Camper Quentin -Level 27 Fearow (1041 EXP) -Level 30 Primeape (957 EXP) -Level 30 Tauros (1350 EXP) -You get: 600G - -You can drop down the right side to grab the Mystery Berry. - -RIGHT PATH: - -Taking the right path eventually leads you to Trainer Bailey. - -|--------| -|Route 46| -|--------| - -Pokémon found in Route 46: - -#019 Rattata -#021 Spearow -#074 Geodude - -There are, again, the two paths. Upon reaching Route 46 you will notice an -entrance to a cave. This is the Dark Cave you saw WAY back in Route 31. - -LEFT PATH: - -Camper Ted -Level 17 Mankey -You get: 680G - -You can pick up a Dire Hit to the left of Ted. You can go down to fight a -Picnicker. - -Picnicker Erin -Level 16 Ponyta (520 EXP) -Level 16 Ponyta (520 EXP) -You get: 640G - -Grab the Berry and PrzCure Berry to the left of it, then drop down just below -the cave. - -RIGHT PATH: - -You'll fight Hiker Bailey, a Geodude hoarder. - -Hiker Bailey -Level 13 Geodude (238 EXP) -Level 13 Geodude (238 EXP) -Level 13 Geodude (238 EXP) -Level 13 Geodude (238 EXP) -Level 13 Geodude (238 EXP) -You get: 582G - -Here, you can fly back up to Blackthorn City, heal at the Pokémon Center, and -take different paths down Routes 45 and 46 so that you can also face the other -trainers and get the other items. If you don't care about that or you've already -done that, you can just fly to New Bark Town to skip having to bike Route 29. -This is, by the way, how people stop getting exercise. They end up taking better -forms of transportation that make them lazy and they suddenly don't do cardio -anymore. Sad times. - -|-------------| -|New Bark Town| -|-------------| - -Talk to Professor Elm in his lab. He'll give you a Master Ball. You are the -lucky winner of the Master Ball sweepstakes. Since you have gotten eight badges, -you can now enter the Pokémon League. Yay! Now go into your own house and if you -talk to your mom, you can withdraw money (which is a whole fifth of what you've -gotten over time). Visit your room if you'd like. You've been gone from a long -time from home. - -Now, fly back to Cherrygrove City and withdraw Togepi temporarily. Fly back to -New Bark Town, talk to Elm again, and you'll get an Everstone. Now fly back to -Cherrygrove City and put your original Pokémon back in the lineup. Also deposit -any items that you don't need in your PC. Head to the east of the town, then -surf eastward onto Route 27. - -|--------| -|Route 27| -|--------| - -Pokémon found on Route 27: - -#020 Raticate -#024 Arbok -#028 Sandslash -#072 Tentacool (in water) -#077 Ponyta -#084 Doduo -#085 Dodrio -#195 Quagsire - -Items found on Route 27: - -TM22 Solar Beam -TM37 Sandstorm - -Are you finished building up your Pokémon and with doing everything else you -want to do in Johto, at least for now? If so, it's time to head towards the -Elite Four. Bring the team you want that also has all the HMs you need. - -Now surf eastward from New Bark Town. When you land on the other side, the first -guy you see will tell you that you've entered Kanto. If you've played the -original games (Red, Blue, or Yellow), you'll recognize that the land looks very -familiar. If you access your Pokegear, it will show that you are now in Kanto! - -You'll see a cave entrance. Don't go in yet, because caves are scary! Instead, -surf southeast of the cave entrance and you'll find a Rare Candy. I'm not really -sure that you should be feeding your Pokémon candy off the shore of a lake. It -doesn't seem sanitary, but it's rare so I guess that makes it okay. I tend to -save my Rare Candies but they level up your Pokémon by one level. Surf back to -shore and then enter the cave into Tohjo Falls. - -|-----------| -|Tohjo Falls| -|-----------| - -Pokémon found in Tohjo Falls: - -#019 Rattata -#079 Slowpoke -#119 Seaking - -You'll notice a Waterfall. Head along the sea route and down the other -waterfall. Those are the falls. Did you expect more? I know, the name sounded -more grand than it really was. Now head out the door you see back to Route 27. - -|--------| -|Route 27| -|--------| - -A trainer will immediately battle you right after you exit the Tohjo Falls. - -Cooltrainer Megan -Level 32 Bulbasaur (438 EXP) -Level 32 Ivysaur (966 EXP) -Level 32 Venusaur (1425 EXP) -You get: 1536G - -Enter the house, and speak to the lady there. If one of your Pokémon is happy -she will give you TM37 Sandstorm. Then head east and surf across the lake. - -Cooltrainer Blake -Level 33 Magneton (1138 EXP) -Level 31 Exeggcute (651 EXP) -Level 31 Quagsire (909 EXP) -You get: 1488G - -Keeping head east and you'll battle a few more trainers. - -Cooltrainer Brian -Level 35 Sandslash (1222 EXP) -You get: 1680G - -Psychic Gilbert -Level 30 Starmie (1330 EXP) -Level 30 Exeggcute (630 EXP) -Level 34 Girafarig (1084 EXP) -You get: 1088G - -Surf down from this trainer along the Coral till you find a Whirlpool. Remove -the Whirlpool then land on the island with the item. - -Bird Keeper Jose -Level 35 Farfetch'd (705 EXP) -You get: 816G - -You could exchange phone numbers with him, but you'll be out of room on your -list. Grab the item above him (TM22 Solar Beam), then surf back to the bridge. -Then keep heading along the path and you'll fight the final trainer of the -route. - -Cooltrainer Reena -Level 31 Starmie (1374 EXP) -Level 31 Starmie (1374 EXP) -Level 33 Nidoqueen (1371 EXP) -You get: 1488G - -After this battle, you can head east to Route 26. - -|--------| -|Route 26| -|--------| - -Pokémon found on Route 26: - -#020 Raticate -#024 Arbok -#028 Sandslash -#077 Ponyta -#084 Doduo -#085 Dodrio -#195 Quagsire - -Items found on Route 26: - -Ice Berry -Max Elixir - -Welcome to Route 26. Your first battle is against a fisherman. His name is -Scott. - -Fisher Scott -Level 30 Qwilfish (642 EXP) -Level 30 Qwilfish (642 EXP) -Level 34 Seaking (1237 EXP) -You get: 1360G - -Keep moving up the right path to fight more trainers. - -Psychic Richard -Level 36 Espeon (1519 EXP) -You get: 1152G - -Keep heading up till you find a house. Talk to the woman in there and she will -heal all your Pokémon for free. She's basically a one-person Pokémon Center. -Don't you just love free veterinarian health care? If you've ever had to pay vet -bills, you'll come to appreciate this lady very much. Give her a hearty round of -applause. - -Cooltrainer Joyce -Level 36 Pikachu (631 EXP) -Level 32 Blastoise (1440 EXP) -You get: 1536G - -Head up and you can grab the Ice Berry. You can move up further and then fight -another trainer who thinks he's cool. I think I'm pretty cool, but I don't go -around telling people that I am. - -If you go down the left path at this point, you will find a house with Monica's -journal. Why are you snooping around? Okay, fine, I won't judge. It's a list! -Lists are amazing. It's all the trainers you can get swag from, along with the -days and locations that they will be: - -Monday: Monica, Route 40 -Tuesday: Tuscany, Route 29 -Wednesday: Wesley, Lake of Rage -Thursday: Arthur, Route 36 -Friday: Frieda, Route 32 -Saturday: Santos, Blackthorn City -Sunday: Sunny, Route 37 - -Once you get back to where you were, the right path leads you to more trainers. - -Cooltrainer Gaven -Level 32 Victreebel (1309 EXP) -Level 32 Krabby (1411 EXP) -Level 32 Flareon (1357 EXP) -You get: 1536G - -Cooltrainer Jake -Level 33 Parasect (902 EXP) -Level 35 Golduck (1305 EXP) -You get: 1680G - -You can go up, drop down the left side, and do your best BMX bike tricks, and -grab the Max Elixir. You are using your bike, right? You're singularly -responsible for that bike shop's business, so get on it! Afterwards, bike back -down and get your Pokémon healed again with the free health care lady. Then go -back up the right side past Jake and you'll encounter one more trainer. - -Cooltrainer Beth -Level 36 Rapidash (1478 EXP) -You get: 1728G - -If you were to talk to her again you could switch numbers, but it's probably out -of room. Keep heading up, and you will eventually find yourself in the checking -station to the Elite Four. The guard will check your badges and if you have all -eight will let you pass, head in the door and into Victory Road. You can also -pick up an Max Elixir if you take the jumps back to heal. - -============================================================================== -============================================================================== -============================================================================== - -(c23) ---------------------------- diff --git a/docs/gold-walkthrough/section-17-victory-road.txt b/docs/gold-walkthrough/section-17-victory-road.txt deleted file mode 100644 index c2798ee6..00000000 --- a/docs/gold-walkthrough/section-17-victory-road.txt +++ /dev/null @@ -1,99 +0,0 @@ ----- 23 > Victory Road ---- ---------------------------- - -Video Playthrough Link Route 26 & Victory Road): -https://www.youtube.com/watch?v=Jign264AzYI - -Video Playthrough Link (Elite Four & Champion Battles): -https://www.youtube.com/watch?v=VcFLopf6A28 - -|------------| -|Victory Road| -|------------| - -Pokémon at Victory Road: - -#042 Golbat -#075 Graveler -#111 Rhyhorn -#232 Donphan (Silver only) - -Items found in Victory Road: - -Full Heal -Full Restore -Max Revive -TM26 Earthquake -X Special - -Victory Road is nothing like it used to be. This eastern half of Victory Road is -now much easier now. There are no puzzles to solve now. There are also no -trainers stalking you as you roam around the dungeon. Head up the stairway and -up the cliff. If you go right, you can pick up a Full Heal and Max Revive and -then return to the cliff. If you go left, you can keep going and then go up the -ladder. Have you ever wondered why the cliffs have guardrails and why dungeons -have ladders? It has been a mystery to me. - -On the second floor, go right up the cliff, then head down. Going left takes you -to a X Special. Head right again and then up along the path, and that'll lead -you up to another ladder. - -Save when you get to the third floor. Go up the cliff to your left, go down the -ladder, grab the Full Restore, and jump down. Head back up the ladder to the -third floor. - -Now, take the path to the right. You'll spot a door to leave the cave. Before -exiting the cave, your Rival and challenge you to a rematch. He overconfidently -exclaims that you're so much weaker than he is even though he has never beaten -you in his life. - -|------------------------------------------| -|Rival | -|Level 34 Sneasel (961 EXP) | -|Level 36 Golbat (1318 EXP) | -|Level 34 Magneton (1207 EXP) | -|Level 38 Starter Evolution (1693-1710 EXP)| -|Level 35 Haunter (945 EXP) | -|Level 35 Kadabra (1087 EXP) | -|You get: 2280G | -|------------------------------------------| - -Strategy VERSUS Sneasel: If you're using your starter, use your highest power -move. If you're sticking with a balanced lineup, use Eevee and its quick attacks -to finish it off. - -Strategy VERSUS Golbat: If you're using your starter, use your highest power -move. If you're sticking with a balanced lineup, use Raticate's -hyper fangs and a quick attack. - -Strategy VERSUS Magneton: Magneton is a steel type, so it will absorb normal -moves. If you have Typhlosion, fire wheel is effective. Otherwise, stick with -the strongest move you've got. - -Strategy VERSUS Starter Evolution: If you're using your starter, use your -highest power move that isn't an attack from your starter's type. If you're -using a balanced lineup, use Raticate and have it hit with hyper fans. - -Strategy VERSUS Haunter: Use your highest power move that isn't a normal or -fighting attack, as those will be ineffective. - -Strategy VERSUS Kadabra: Use your highest power move that isn't a fighting move. - -Woop woop! Oh, sorry, I was imitating the victory celebration from a Wooper. -Congrats on beating your Rival one more time. Your rival finally understands he -sucks and all, but he hasn't given up hope. It's commendable, truly. That -arrogance really keeps him going. It's a beautiful story of a thief trying to -live his dreams. He retreats, and now you can advance out of the cave. - -But before you do that, go left instead of out the door, and fall down the hole -to get TM26 Earthquake. Earthquake is one of the best ground moves in the game. -Then drop down the ledge and head back up the ladder to get back to the third -floor. Go back through the third floor to leave the dungeon with an extra TM in -your pocket. - -============================================================================== -============================================================================== -============================================================================== - -(c24) ------------------------------ diff --git a/docs/gold-walkthrough/section-18-pok-mon-league.txt b/docs/gold-walkthrough/section-18-pok-mon-league.txt deleted file mode 100644 index ec774e81..00000000 --- a/docs/gold-walkthrough/section-18-pok-mon-league.txt +++ /dev/null @@ -1,296 +0,0 @@ ----- 24 > Pokémon League ---- ------------------------------ - -Video Playthrough Link Route 26 & Victory Road): -https://www.youtube.com/watch?v=Jign264AzYI - -Video Playthrough Link (Elite Four & Champion Battles): -https://www.youtube.com/watch?v=VcFLopf6A28 - -|--------------| -|POKEMON LEAGUE| -|--------------| - -Route 32 is very short, and you'll immediately be at the Pokémon League building -at the Indigo Plateau. You'll now be able to fly to this location directly. The -left side of the building is the Pokémon Center, while the right side is the -Pokemart. If you want to head back to Johto, you can talk to the old man with -the Abra next to the Pokémon Center portion of Indigo Plateau's HQ and Abra will -teleport you back. - -Now, your goal is to take on five powerful trainers in a row. You mostly want to -have high-power moves of different types, plus priority moves (like Quick -Attack), status moves (like paralysis or sleep), and recovery moves (like -Recover or Leech Seed) in your lineup. Note that you do NOT need to have every -HM in your lineup in these fights, so you can leave Cut and Whirlpool off the -lineup for now. - -You don't need any Pokémon that can use HMs. However, certain HMs are great to -teach to your Pokémon because they are also strong attacks. Surf is 95 Power, -Strength is 80 Power, Waterfall is 80 Power, and Fly is 80 Power. Consider -teaching these moves to every Pokémon in your six-Pokémon lineup if they can -learn them. You can go back to the Move Deleter in Blackthorn City later in the -game if you want to replace the moves later. - -Here's a potential lineup in which you're just leveling up your starters, along -with their power levels: - -Feraligatr - SURF (95), STRENGTH (80), Hydro Pump (120), Bite (60) -Lugia - Aero Blast (100/5 PP), SURF (95), FLY (80), Recover -Donphan - STRENGTH (80), Fury Attack (15x2-5), Rollout (30), Flail -Gyarados - Surf (95), WATERFALL (80), Strength (80), Bite (60 flinch) -Raticate - Quick Attack (40), Hyper Fang (80 flinch), STRENGTH (80), CUT (50) -Sudowoodo - Strength (80), Rock Throw (50), Low Kick (50 flinch), Flail - -If you want a balanced lineup, your Pokémon will all need to be at least Level -40 or higher, because your next opponents will all have Pokemon from Level 40 -through Level 50. The best training ground would be back in Victory Road, -although Routes 46 and 47 aren't bad, either. If you trade a Red Scale to Mr. -Pokémon, he will give you EXP Share, which will share all EXP that you gain with -every Pokémon in your lineup so you don't have to keep switching Pokémon back -and forth. - -If you're leveling up an Eevee, put Eevee at the start of your lineup to -increase its happiness towards an Espeon/Umbreon evolution. Eevee can evolve to -Espeon (a psychic type) in the daytime, and an Umbreon (dark type) in the night -time. This can take a while as you can only the requisite happiness through -leveling up, walking around a lot, and visiting the Goldenrod City salon guy. -Goldenrod City also has extra TMs you can buy, so you can buy Thunder Punch and -Fire Punch and use it on Pokémon and still be able to reuse those TMs in the -future. - -If you have a balanced lineup, your lineup to the Elite Four should be at least -something like the following: - -Level 47 Espeon -Level 41 Dragonair -Level 40 Gyarados -Level 40 Sudowoodo -Level 40 Typhlosion -Level 38 Raticate - -When you do feel ready for the ultimate challenge, make sure you have at least -ten Hyper Potions, ten Revives, and ten Full Heals. You can sell any Potions, -Super Potions, and Paralyz Heal/Awakenings etc. to make room for the more -versatile Hyper Potions and Full Heals. - -You can't go back to the Pokémon Center to heal between battles, and no one will -heal your Pokémon, so having items on hand will be helpful for HP loss, KOs, or -paralysis situations. You've traveled across the land, searching far and wide. -Now it's time. - -Fully heal your Pokémon and save after every trainer battle at the Elite Four. -Keep in mind that you cannot readily replenish your PP, as ethers are single -use. You'll want to balance out your PP so that you don't run out of any move -too early through the battles. Use type-advantaged moves were possible, and if -you can use a lower-powered move to knock out a Pokémon, do that. It is a battle -of endurance, and it is a grind. Allocate an hour to fighting the Pokémon -League. The trainers will sap your energy. - -The first Elite Four member, Will, uses psychic Pokémon. Avoid fighting moves. -Dark moves will be effective. - -|-----------------------------| -|Elite Four Will | -|Level 40 Xatu (1465 EXP) | -|Level 41 Jynx (1203 EXP) | -|Level 41 Slowbro (1440 EXP) | -|Level 42 Xatu (1539 EXP) | -|Level 41 Exeggutor (1861 EXP)| -|You get: 4200G | -|-----------------------------| - -Strategy VERSUS Xatus: Use the most high-powered move that you've got that's not -a fighting move. - -Strategy VERSUS Jynx: Use the most high-powered move that you've got. Jynx is -also an ice type, so you can also use fire moves. - -Strategy VERSUS Slowbro: Use the most high-powered move that you've got. Slowbro -is also a water type, so you can also use a strong electric type move. - -Strategy VERSUS Exeggutor: Use the most high-powered move that you've got. -Exeggutor is also a grass type, so you can also use fire moves. - -Next up is Elite Four Koga. He was the poison type Fuchsia City Gym Leader in -Red, Blue, and Yellow, and now he's become an Elite Four member. Poison types -are super effective against grass types, so don't put a Meganium or other grass -type in. Instead, focus on using ground and psychic type moves. - -|------------------------------| -|Elite Four Koga | -|Level 40 Ariados (1147 EXP) | -|Level 43 Forretress (1086 EXP)| -|Level 41 Venomoth (1212 EXP) | -|Level 42 Muk (1413 EXP) | -|Level 44 Crobat (1923 EXP) | -|You get: 4400G | -|------------------------------| - -Koga has three Full Restores that he may use. - -Strategy VERSUS Ariados - Ariados is both a poison and bug type. This means you -can fry Ariados with fire or flying type moves alongside the typical psychic -moves. - -Strategy VERSUS Forretress - Use the most high-powered move that you've got -that's not a grass move. Psychic moves are super effective. - -Strategy VERSUS Venomoth - Use the most high-powered move that you've got that's -not a grass move. Psychic moves are super effective. Since it's also a flying -type, you can also use electric or ice moves to take it down. Beware of -Venomoth's Confuse Ray. Not that you could stop it even if I told you it was -coming. - -Strategy VERSUS Muk - Use the most high-powered move that you've got that's not -a grass move. Psychic moves are super effective. Muk has Toxic, so Muk can -poison your Pokémon. - -Strategy VERSUS Crobat - Use the most high-powered move that you've got that's -not a grass move. Psychic moves are super effective. Since it's also a flying -type, you can also use electric or ice moves to take it down. - -The third Elite Four member is Bruno. He has mostly fighting type Pokémon but -threw in a rock type in his lineup just for funsies. Fighting types are super -effective against normal, ice, rock, dark, and steel types. The best moves to -use against them are flying and psychic type moves. Avoid using dark moves -against fighting types. - -|------------------------------| -|Elite Four Bruno | -|Level 42 Hitmontop (1242 EXP) | -|Level 42 Hitmonchan (1260 EXP)| -|Level 42 Hitmonlee (1242 EXP) | -|Level 43 Onix (994 EXP) | -|Level 46 Machamp (1902 EXP) | -|You get: 4600G | -|------------------------------| - -Strategy VERSUS Hitmontop: The good news about battling fighting types is that -you can use pretty much any move against them. Use your -highest power move from your best Pokémon against it. Flying and psychic moves -also work well. - -Strategy VERSUS Hitmonchan: Hitmonchan is still a fighting type, but it can be -more of a challenge to fight if it gets off its electric, fire, or ice punches. -The strategy otherwise holds. Use your highest power move from your best Pokémon -against it. Flying and psychic moves also work well. - -Strategy VERSUS Hitmonlee: Hitmonlee's similar to Hitmontop. Use your highest -power move from your best Pokémon against it. Flying and psychic moves also work -well. Watch out for Swagger, which will confuse your Pokémon. - -Strategy VERSUS Onix: Onix is the one rock type. Use water, grass, fighting, -ground, or steel moves to beat it. Use your highest power move from your best -Pokémon that isn't a normal move. - -Strategy VERSUS Machamp: Machamp is the same as the other fighting types. Use -your highest power move from your best Pokémon against it. Flying and psychic -moves also work well. - -You've got one more Elite Four member to face. She wants to be entertained by -you. I think you can handle that. Your Pokémon are quite entertaining and they -can entertain by winning the battle! - -|-----------------------------| -|Elite Four Karen | -|Level 42 Umbreon (1773 EXP) | -|Level 42 Vileplume (1656 EXP)| -|Level 45 Gengar (1831 EXP) | -|Level 44 Murkrow (1008 EXP) | -|Level 47 Houndoom (2053 EXP) | -|You get: 4700G | -|-----------------------------| - -Karen has one Full Restore that she can use. - -Strategy VERSUS Umbreon: Umbreon is a dark type. It is weak to fighting and bug -moves and is immune to psychic moves. You should be able to just use your -highest-powered attack to beat Umbreon. Beware of Umbreon's Confuse Ray, which -will confuse your Pokémon. - -Strategy VERSUS Vileplume: Vileplume is a grass and poison type. It is weak to -fire and psychic moves. You should be able to just use your highest-powered -attack to beat Vileplume, which may be flame wheels from Typhlosion if that was -your starter. - -Strategy VERSUS Gengar: Gengar's a ghost type that's weak against ghost and dark -moves. Don't use any normal attacks on it because it has no effect. Otherwise, -use your highest-powered attack that's not a normal, poison, or bug move against -Gengar. - -Strategy VERSUS Murkrow: Murkrow's a dark and flying type. Like Umbreon, it is -weak to flying and bug moves and immune to psychic moves. As a flying type, -Murkrow is also immune to ground moves. Otherwise, you can use your -highest-powered attack against Murkrow. Consider using ice or electric moves as -they are super effective. - -Strategy VERSUS Houndoom: Houndoom's a fire and dark type who will be holding a -Sitrus Berry. You should be able to just use your highest-powered attack if it's -not a psychic, fire, or grass move. - -Congrats on beating the Elite Four. Guess you're now more elite than them now! -Now, heal all your Pokémon. The final challenge for Pokémon League champion -begins. Three of Champion Lance's Pokémon are actual dragon types, while the -rest are essentially dragons but not dragon types. - -|------------------------------| -|Champion Lance | -|Level 44 Gyarados (2017 EXP) | -|Level 47 Dragonite (2194 EXP) | -|Level 47 Dragonite (2194 EXP) | -|Level 46 Aerodactyl (1990 EXP)| -|Level 46 Charizard (2059 EXP) | -|Level 50 Dragonite (2335 EXP) | -|You get: 5000G | -|------------------------------| - -Lance has three Full Restores on hand for the battle. - -Strategy VERSUS Gyarados: Gyarados is two levels higher than in the original -game. Gyarados is a water and flying type, not a dragon type. Avoid using fire, -water, or ground moves against it. Use the highest powered move from your -highest level Pokémon otherwise. Electric and rock moves would be super -effective. - -Strategy VERSUS Dragonites: Do not use fire, water, grass, or ground type moves -due to Dragonite's dual dragon/flying typing. You can otherwise use the highest -powered move of any other type from your highest level Pokémon. Ice and rock -types are particularly useful moves against the Dragonites, especially because -dragon and flying types are both weak against ice moves. - -The Dragonites are strong. They have a powerful Hyper Beam move (although it -must recharge), it can paralyze your Pokémon with Thunder Wave, and it also has -strong Dragon Rush and Thunder attacks. The Level 50 Dragonite has Safeguard and -Outrage instead. Have Full Heals on hand -for the fight. One of the Dragonites may be holding a Sitrus Berry. - -Strategy VERSUS Aerodactyl: Aerodactyl is a rock and flying type, not a dragon -type. Use the highest powered move from your highest level Pokémon otherwise. -Ice and water moves are ideal. - -Strategy VERSUS Charizard: Charizard's weaknesses as a Fire/Flying type are -Water, Electric, and Rock, especially Water and Rock. - -Congratulations! You have defeated the Elite Four! Lance doesn't seem angry that -he lost but rather, happy that you were the one to become the new champ. He -congratulates you. - -After defeating the Johto League Elite Four members, you've beaten the first -half of the game. A news reporter named Mary wants to interview you, but Lance -shoos you into the other room to be inducted into the Hall of Fame. Guess Mary -won't get to interview you after all! Professor Oak also arrives just to hang -out. Lance will then enter your entire Pokémon lineup into the Hall of Fame, -even the ones that sat there on the bench and watched your Lv 66 starter do all -the hard work. - -The credits will roll, while playing some snappy music, and finally the game -will say "The End". Don't turn your DS off yet, because the game isn't actually -over. - -============================================================================== -============================================================================== -============================================================================== - -(c25) ------------------------------------------------ diff --git a/docs/gold-walkthrough/section-19-s-s-aqua-and-vermilion-city-gym.txt b/docs/gold-walkthrough/section-19-s-s-aqua-and-vermilion-city-gym.txt deleted file mode 100644 index d790214f..00000000 --- a/docs/gold-walkthrough/section-19-s-s-aqua-and-vermilion-city-gym.txt +++ /dev/null @@ -1,246 +0,0 @@ ----- 25 > S.S. Aqua and Vermilion City Gym ---- ------------------------------------------------ - -Video Playthrough Link (Vermilion City & Saffron City Gyms): -https://www.youtube.com/watch?v=5Hsa1kt1CyM - -|-------------| -|New Bark Town| -|-------------| - -After the credits finish rolling, you will arrive back in New Bark Town. Go into -Prof. Elm's lab because he wants to show you something. He'll even waste -previous cell phone minutes to call you. Now that's dedication! Go to his lab, -where someone could still go and steal the last starter Pokémon that he hasn't -given to another 10-year old trainer. Prof. Elm will give you a S.S. Ticket! - -|----------| -|Saint Aqua| -|----------| - -Item on Saint Aqua: - -Meal Coat - -Welcome to the second half of the game. Now that you've beaten the Pokémon -League, you can go to Kanto, the world of the original Pokémon Red, Blue, and -Yellow games. You can visit all the cities again, and fight most of the gym -leaders again, too! Kanto is not particularly linear. We'll take a route that is -about minimizing travel fatigue and avoiding underleveled teams. - -You have a decision point going into Kanto that you should be aware of. You'll -be relatively overleveled through most of Kanto. This means you can skip most of -the trainer battles, if you wanted to speedrun or get to your next game in the -backlog. However, there is a significant level spike at the end of the game. -Therefore, I would still suggest that you fight most or all the trainers, or the -end of the game could be a challenging one for you. - -A balanced lineup that you could take to Kanto is: - -Level 49 Espeon -Level 42 Typhlosion -Level 41 Dragonair -Level 41 Sudowoodo -Level 40 Gyarados -Level 38 Raticate - -Fly to Olivine City. Go south from the Pokémon Center to a docking pay called -the Olivine Port Fast Ship Pier. Go through the door, take the stairs down -twice. Then keep going down and talk to the guy at the entrance to board the -Fast Ship S.S. Aqua. As with the S.S. Anne from the Red, Blue, and Yellow games, -you'll fight Pokémon trainers bored with sea travel while the ship sails. - -The ship will begin sailing to Kanto. Go down a few steps and an old man will -bump into you. He tells you that his granddaughter is missing. Head left into -the room you see, and you'll start fighting trainers. - -Hiker Noland -Level 31 Sandslash (1081 EXP) -Level 33 Golem (1251 EXP) -You get: 1056G - -You'll see three other cabins on the top part of the ship. The cabin on the far -left is yours, and you can go to that cabin's bed heal all your Pokémon. No -other cabin on the north side currently has trainers. You can head to the bottom -half of the ship and down to the left side to fight another trainer. - -Firebreather Lyle -Level 28 Koffing (684 EXP) -Level 31 Flareon (1314 EXP) -Level 28 Koffing (684 EXP) -You get: 1344G - -If you head two cabins to the right, you'll find another trainer to battle. - -PoKeFan Colin -Level 32 Delibird (1254 EXP) -You get: ???G - -The same cabin also has twin trainers. - -Twins Meg & Peg -Level 31 Teddiursa (823 EXP) -Level 31 Phanpy (823 EXP) -You get: 620G - -Head to the cabin to the right and you'll speak to the old man again. Go -downstairs (next to his room) and a sailor will block you like he's blocking you -from dunking on him. Talk to him and you'll learn that there's a sailor who's -not pulling his weight at work. Go back down towards your cabin but into the -cabin that's next to yours. Apparently a sailor has been napping on the job. -That's wage theft! - -Sailor Stanly -Level 31 Machop (583 EXP) -Level 33 Machoke (1032 EXP) -Level 26 Psyduck (445 EXP) -You get: 1040G - -Defeat the sailor and he'll return to work. Then go back downstairs and the -sailor boss will let you pass. He also tells you that a little girl passed by -there as if it wasn't his responsibility to make sure little children don't run -around the whole ship. Head left, then down, and you'll face another trainer. - -Juggler Fritz -Level 29 Mr. Mime (844 EXP) -Level 29 Machoke (906 EXP) -Level 29 Magmar (1036 EXP) -You get: 1160G - -Head up, left, and then go down into the next "room." - -Sailor Jeff -Level 32 Raticate (795 EXP) -Level 32 Raticate (795 EXP) -You get: 1280G - -Now head up and left and talk to the girl that's just staring at a wall for some -reason. She tells you that she's soooo bored that she'll battle you. - -Picnicker Debra -Level 33 Seaking (1201 EXP) -You get: 660G - -Head down from Debra and up the stairs. Then go through the door and talk to the -girl. She'll find out that her grandpa is worried about her. Then she teleports -back to her grandfather's cabin and the grateful grandfather gives you a Metal -Coat. Okay, that wasn't really teleporting, but time travel is just as -wonderful. Now, the ship will arrive in the Kanto. Go back up to where you -entered and the guy will stop blocking the door. - -|--------------| -|Vermilion City| -|--------------| - -Item in Vermilion City: - -Rare Candy - -Now that you get off the S.S. Aqua, and you find yourself at Vermilion City! Go -down the stairs, then up the stairs and out the door. You'll see your first -glimpse of the Vermilion City three years later. Follow the route north and -you're out of the Vermilion Port. Note the new Pokémon music for Kanto. It's an -interesting remix. The Fast Ship sails Wednesday and Sunday if you want to -return for another cruise. - -North of the gym is the Pokémon Club house. As with the original games, the -Chairman of the Pokémon Fan Club is there. If you listen to him, he'll babble on -forever about his wonderful Pokémon. Eventually, he'll give you a Rare Candy as -compensation for listening to him express his feelings. - -The northwest part of town has the Pokémon Center. Heal, make sure you have a -Pokémon with Cut, and cut down the tree blocking the gym. Look around and you'll -see that the gym looks pretty much the same and other features from Red, Blue, -and Yellow are still here. - -Anyway, there's a power outage or something, because his usual puzzle from -Red/Blue/Yellow is gone. There are three trainers before Lt. Surge. This is a -gym with mostly electric Pokémon where you can usually just use the most -high-powered moves, although the Magnemites and Magnetons are also steel types. -Fire and fighting moves are preferred against them. - -Guitarist Vincent -Level 27 Magnemite (514 EXP) -Level 33 Voltorb (727 EXP) -Level 32 Magnemite (609 EXP) -Level 32 Magnemite (609 EXP) -You get: 1024G - -Before you can even move, you'll face an Electrode-hoarding trainer who likes -his Electrodes to go boom boom. You can either knock out all the Electrodes in -one hit before they explode, or use a rock type that can withstand the -self-destructing nature of these ball-shaped Pokémon. - -Juggler Horton -Level 33 Electrode (1060 EXP) -Level 33 Electrode (1060 EXP) -Level 33 Electrode (1060 EXP) -Level 33 Electrode (1060 EXP) -You get: 1320G - -Head right, then up to face the next trainer. - -Gentleman Gregory -Level 37 Pikachu (649 EXP) -Level 33 Flaaffy (826 EXP) -You get: 2376G - -You can heal before your gym battle or go straight into it, but save first. He -says he'll zap you like he did in war against his enemies. Every Pokémon can -confuse or paralyze your Pokémon, so try to avoid using two-turn moves that are -doubly affected by that if paralysis or confusion is in effect. - ---- -GYM LEADER LT. SURGE -Level 44 Raichu (1149 EXP) -Level 40 Magneton (1380 EXP) -Level 46 Electrabuzz (1537 EXP) -Level 40 Electrode (1285 EXP) -Level 40 Electrode (1285 EXP) -You get: Thunder Badge, 4600G ---- - -Lt. Surge has a Full Restore that he can use to fully restore the health and -status of one of his Pokémon. - -Strategy VERSUS Raichu: Use the most high-powered move from your highest level -Pokémon that. Alternatively, use rock or ground moves which are super effective -against electric Pokémon. Ground Pokémon will be immune to its electric attacks. -Raichu has Thunder Wave, and will likely use it to paralyze your Pokémon. If -your Pokémon is paralyzed, you may want to avoid using a two-turn move because -the chances of that connecting are significantly reduced. - -Strategy VERSUS Magneton: Use the most high-powered moves from your highest -level Pokémon. Ground Pokémon will be immune to its electric attacks. Magneton -is also a steel type, so fire and fighting moves are ideal. Magneton can use -Supersonic, which can confuse your Pokémon. If your Pokémon is paralyzed or -confused, you may want to avoid using a two-turn move because the chances of -that connecting are significantly reduced. - -Strategy VERSUS Electrabuzz: Use the most high-powered move from your highest -level Pokémon that. Alternatively, use rock or ground moves which are super -effective against electric Pokémon. Ground Pokémon will be immune to its -electric attacks. Electabuzz has Thunder Wave, and will likely use it to -paralyze your Pokémon. If your Pokémon is paralyzed, you may want to avoid using -a two-turn move because the chances of that connecting are significantly -reduced. - -Strategy VERSUS Electrodes: Use the most high-powered move from your highest -level Pokémon that. Alternatively, use rock or ground moves which are super -effective against electric Pokémon. Ground Pokémon will be immune to its -electric attacks. Electrode has Thunder Wave, and will likely use it to paralyze -your Pokémon. Electrode may use Double Team, an annoying move that increases -evasion. If your Pokémon is paralyzed, you may want to avoid using a two-turn -move because the chances of that connecting are significantly reduced, -especially if Electrode has already used Double Team. - -Lt. Surge will give you the Thunder Badge and 4,600G. Thunder Badge increases -Pokémon speed. Yay! Congrats on your first badge in Kanto. You are the hero of -your own story. - -============================================================================== -============================================================================== -============================================================================== - -(c26) -------------------------------- diff --git a/docs/gold-walkthrough/section-20-saffron-city-gym.txt b/docs/gold-walkthrough/section-20-saffron-city-gym.txt deleted file mode 100644 index e4353869..00000000 --- a/docs/gold-walkthrough/section-20-saffron-city-gym.txt +++ /dev/null @@ -1,135 +0,0 @@ ----- 26 > Saffron City Gym ---- -------------------------------- - -Video Playthrough Link (Vermilion City & Saffron City Gyms): -https://www.youtube.com/watch?v=5Hsa1kt1CyM - -|-------| -|Route 6| -|-------| - -Heal at the Pokémon Center, then head north onto Route 6. Route 6 is kind of a -barren wasteland. There's a large man who blocks the entire entrance to the -Underground Path. He looks like he can take you down because you're too weak to -move your own boulders. He says that the Power Plant has issues right now, so -you can't take the Magnet Train between Saffron City to Goldenrod City yet. So -make your way all the way up through the checkpoint house and you'll be into -Saffron City. - -|------------| -|Saffron City| -|------------| - -Items in Saffron City: - -Focus Band -TM29 Psychic - -Ride your trusty bike to the southeast side of Saffron City, which is the -Pokémon version of Tokyo. There's a house labeled Mr. Psychic's House. Mr. -Psychic is there. He does some magic tricks and gets you TM29 Psychic, one of -the best Psychic moooves in the game. - -Next, enter Silph Co. This is the multi-story building above the Pokémon Center -where the main character battles Team Rocket in Red, Blue, and Yellow. Talk to -the man blocking the pathway up to the first floor and he'll give you Up-Grade. -It's amazing how many people are employed in this land that just block doorways -and stairways. - -On the northwest side of town is the Magnet Train, which you can take between -Johto and Kanto. It's currently closed because the Power Plant is down. - -Now, head out and go to the upper-right corner of Saffron City. There's a -PokeMart if you want to buy or sell items. You're probably low on item space at -this point, and your bike takes up the same amount of room as a tiny potion in -your backpack. Ah, such is life. Further north, you'll spot two gyms. The former -fighting gym has a Focus Band you can pick up and attach to a Pokémon for -battle. - -Afterwards, head to the gym on the right. It's Saffron City's official gym. -You'll want to avoid using fighting moves there, while high-powered and dark -moves are going to be your friends. Your playable character doesn't really have -friends other than those trainers who call you out of the blue to tell you about -how they've dressed up their Nidoran in pretty clothing, so you take what you -can get. The teleporter puzzles are back from the Red, Blue, and Yellow games. - -Medium Rebecca -Level 35 Drowzee (765 EXP) -Level 35 Hypno (1237 EXP) -You get: 1400G - -Head left through the teleporter, then fight Psychic Franklin. - -Psychic Franklin -Level 37 Kadabra (1149 EXP) -You get: 1184G - -Head left through the teleporter and fight your next Psychic. - -Psychic Jared -Level 32 Mr. Mime (735 EXP) -Level 32 Exeggcute (672 EXP) -Level 32 Exeggcute (672 EXP) -You get: 1120G - -Head left through the teleporter, then right to another medium. She can see into -your soul, because that's the kind of thing you tell strangers the first time -you talk to them. - -Medium Doris -Level 34 Slowpoke (720 EXP) -Level 36 Slowbro (1264 EXP) -You get: 1440G - -So, you've got two choices here. You can go back and heal, or not go back and -heal. I know, so many choices. If you want to go straight into the fight, just -go to the bottom left teleporter and you'll go straight to the gym leader. - -If you want to go back to the Pokémon Center to heal first, embrace the maze -life. Head right through the teleporter, then left, then left/right (whichever -way works) till you find that one trainer again (Franklin), then go the opposite -teleporter direction to outside the gym to heal at the Center, then head back -in. I know, I got confused even trying to explain it. After healing, you'd head -through the 1st teleporter, then go up, left, down, and then down. - -If you have a balanced lineup, it might look something like this: - -Level 49 Espeon -Level 43 Sudowoodo -Level 43 Typhlosion -Level 42 Raticate -Level 41 Dragonair -Level 41 Gyarados - -You're now at Sabrina. She says, “I knew you were coming. Three years ago I had -a vision of your arrival.” That's pretty cool. Maybe she can read you your -fortune without you having to pay for another tarot card reader. Those readings -sure don't come cheap. - ---- -GYM LEADER SABRINA -Level 46 Espeon (1941 EXP) -Level 46 Mr. Mime (1339 EXP) -Level 48 Alakazam (1912 EXP) -You get: Marsh Badge, 4800G ---- - -Strategy VERSUS Espeon: Use your highest powered move, ideally with your -starter. You could also use a Pokémon that bites. Nom nom. - -Strategy VERSUS Mr. Mime: Use your highest powered move, ideally with your -starter. - -Strategy VERSUS Alakazam: Use your highest powered move, ideally with your -starter. - -Sabrina says that your power far exceeds what I foresaw. You now have 10 badges! -She'll give you a Marsh Badge and 4,800G. Now to get out of the gym, head out -the only teleporter, then go right, right, up, and right. - -============================================================================== -============================================================================== -============================================================================== - -(c27) ------------------------------------------------- diff --git a/docs/gold-walkthrough/section-21-power-plant-and-cerulean-city-gym.txt b/docs/gold-walkthrough/section-21-power-plant-and-cerulean-city-gym.txt deleted file mode 100644 index 3c793aec..00000000 --- a/docs/gold-walkthrough/section-21-power-plant-and-cerulean-city-gym.txt +++ /dev/null @@ -1,304 +0,0 @@ ----- 27 > Power Plant and Cerulean City Gym ---- ------------------------------------------------- - -Video Playthrough Link (Routes 9-10 & 24, Rock Tunnel, & Kanto Power Plant): -https://www.youtube.com/watch?v=Gr2hFv0JgM8 - -Video Playthrough Link (Route 25, Cerulean City Gym, Lavender Town, & Celadon -City Gym): https://www.youtube.com/watch?v=bxbOG0kR4QE - -|-------| -|Route 5| -|-------| - -Items on Route 5: - -Cleanse Tag - -Heal at the Pokémon Center, then head north through the route-changing station -and north onto Route 5. There are no trainers here, just that man who's blocking -the entire door to the Underground Path. If you bike all the way up to Cerulean -City, you can hop down some ledges on your BMX bike to the old Daycare Center -from Red, Blue, and Yellow. Inside is a lady who will give you a Cleanse Tag to -ward off shadows. Now, actually head north into Cerulean City. - -|-------------| -|Cerulean City| -|-------------| - -You can talk to the people in Cerulean City but no one will give you any items -here. The Bike Shop from Red, Blue, and Yellow has moved to Goldenrod City. -That's why you have to show off your bike in Kanto for the bike sales of a shop -owner that you don't really care about, but you like getting free things. - -Enter the Cerulean City Gym and go up and you'll find that no one is there. -Where's Misty? Make sure you have Pokémon that can use Cut and Surf, then head -east onto Route 9 through the northeast part of town. - -|-------| -|Route 9| -|-------| - -Pokémon found on Route 9: - -#019 Rattata -#020 Raticate -#021 Spearow -#022 Fearow -#056 Mankey (Gold only) -#057 Primeape (Gold only) - -Cut the tree at the entrance. Keep heading east and you'll fight some trainers. - -Picnicker Edna -Level 30 Nidorina (751 EXP) -Level 34 Raichu (888 EXP) -You get: 680G - -Go above her and you'll find another trainer. - -Camper Sid -Level 32 Dugtrio (1048 EXP) -Level 29 Poliwrath (1149 EXP) -Level 29 Primeape (925 EXP) -You get: 580G - -Head back down and right to another trainer. - -Camper Dean -Level 33 Golduck (1230 EXP) -Level 31 Sandslash (1081 EXP) -You get: 620G - -Head right on the green, bottom path for a bit and you'll face a hiker. - -Hiker Sidney -Level 34 Dugtrio (1114 EXP) -Level 32 Onix (739 EXP) -You get: 1024G - -Head back up to Camper Dean, and keep going up and to the right to another -hiker. - -Hiker Tim -Level 31 Graveler (889 EXP) -Level 31 Graveler (889 EXP) -Level 31 Graveler (889 EXP) -You get: 992G - -Head east for your final battle on the route. - -Picnicker Heidi -Level 32 Skiploom (931 EXP) -Level 32 Skiploom (931 EXP) -You get: 640G - -Head further east and you'll end up on the northern part of Route 10. - -|--------| -|Route 10| -|--------| - -Pokémon found on Route 10: -#020 Raticate -#021 Spearow -#022 Fearow -#100 Voltorb -#118 Goldeen (Water) -#119 Seaking (Water) -#125 Electabuzz -#195 Quagsire - -There's a Pokémon Center in the middle of nowhere. The Rock Tunnel is to your -left, but don't enter just yet. Instead, head back up and surf down the river -until you hit a dead end and you'll be at the Power Plant. - -|-----------------| -|Kanto Power Plant| -|-----------------| - -Talk to the power plant employees, including the manager on the right side. -Someone stole the Machine Part powering the Magnet Plan. Some are depressed, and -others are furious. Thievery!! As you try to leave, they'll learn that a -mysterious man in Cerulean that may have done the thievery. Fly back to Cerulean -City. - -|-------------| -|Cerulean City| -|-------------| - -Head into the Cerulean City gym and a Team Rocket member will run into you. -He'll speak in broken English. It's pretty funny. He'll leave the gym. Head up -to Route 24. - -|--------| -|Route 24| -|--------| - -Pokémon on Route 24: - -#063 Abra -#069 Bellsprout -#070 Weepinbell - -You'll find the Team Rocket guy, with his strange grammar, on what was the -Nugget Bridge. He's pretty easy to beat, but what a fun guy. - -Rocket Grunt -Level 30 Golbat (1098 EXP) -You get: 1200G - -After that ridiculously easy victory, he'll tell you to look for the Machine -Part in the middle of the Cerulean Gym. You could go to the gym now, but it -makes more sense to keep walking up to Route 25. - -|--------| -|Route 25| -|--------| - -Pokémon on Route 25: - -#063 Abra -#069 Bellsprout - -Items on Route 25: - -Nugget -Potion -Protein - -If you take the northwest path, you'll find a Potion under a bush. Can you -really trust medication from a random bush? No harm, no foul, I guess. You can -then head east where six schoolyard bullies want to beat you up under the guise -of giving you a prize. - -Schoolboy Dudley -Level 35 Oddish (585 EXP) -You get: 1120G - -Lass Ellen -Level 30 Wigglytuff (700 EXP) -Level 34 Granbull (1296 EXP) -You get: 816G - -Schoolboy Joe -Level 33 Tangela (1173 EXP) -Level 33 Vaporeon (1386 EXP) -You get: 1056G - -Lass Laura -Level 28 Gloom (792 EXP) -Level 31 Pidgeotto (750 EXP) -Level 31 Bellossom (1221 EXP) -You get: 744G - -Before you can even move, there comes the fifth trainer, pouncing on his prey. -Just kidding, he's actually the prey. - -Camper Lloyd -Level 34 Nidoking (1420 EXP) -You get: 680G - -Lass Shannon -Level 29 Paras (435 EXP) -Level 29 Paras (435 EXP) -Level 32 Parasect (877 EXP) -You get: 768G - -Where's the prize? Did they lie to you? That's really mean. Keep going right and -you'll fight some random person who was just waiting for your Pokémon to get -weakened before fighting you. - -Super Nerd Pat -Level 36 Porygon (1002 EXP) -You get: 1152G - -After you beat the Super Nerd, keep going right and that guy will give you a -Nugget. They sell for 5,000G at the Pokemarts. - -Cut the tree above Pat and then grab a free Protein. Head on east to what was -Bill's place in Red, Blue, and Yellow. You'll see that Misty is on a date. Ooh -la la. The guy quickly runs away when he sees you. You could outrun him on a -bike but you're a kind soul who wouldn't do such a thing. But Misty is pissed. -You're a pest now. But she does spot your shiny Johto badges and say that she -would battle you. - -Now, enter Bill's House and you'll find his grandfather there. We don't know -where Bill is, but at least he didn't turn into a Pokémon. If you show his -grandfather certain Pokémon like Growlithe, Lickitung, Oddish, Pichu, or Staryu, -he'll give you the respective rewards of a Fire Stone, an Everstone, a Leaf -Stone, a Thunder Stone, and a Water Stone. - -Now, fly back to Cerulean City. - -|-------------| -|Cerulean City| -|-------------| - -Item in Cerulean City Gym: - -Machine Part - -Heal at the Pokémon Center. Then head into the Cerulean City Gym. This is a -water type gym, so grass and electric type moves work well. Or you can just use -your highest-powered moves throughout the gym. Head on the left path to fight -the first swimmer. - -Swimmer Briana -Level 35 Seaking (1275 EXP) -Level 35 Seaking (1275 EXP) -You get: 700G - -You'll be next to a lake that is four squares (2x2). Press A on the top left -water square and you'll get the Machine Part that the Team Rocket Grunt -previously hid. Now, head on the right path to face another swimmer. - -Swimmer Parker -Level 32 Horsea (568 EXP) -Level 32 Horsea (568 EXP) -Level 35 Seadra (1162 EXP) -You get: 280G - -Move up to take on the final trainer before Misty. - -Swimmer Diana -Level 37 Golduck (1378 EXP) -You get: 740G - -If you need to heal at the Pokémon Center, do so. In any case, you can now fight -Misty for the 11th gym badge, you pest. - ---- -GYM LEADER MISTY -Level 42 Golduck (1566 EXP) -Level 42 Quagsire (1233 EXP) -Level 44 Lapras (2064 EXP) -Level 47 Starmie (2083 EXP) -You get: Cascade Badge, 4,700G ---- - -Strategy VERSUS Golduck: It seems that Misty has finally evolved her Psyduck. -Golduck has a move that can disable one of your Pokémon's four moves. Use your -highest powered move, ideally with your starter. You can also use grass or -electric moves. Don't use fire or water moves. - -Strategy VERSUS Lapras: Use your highest powered move, ideally with your -starter. You can also use grass or electric moves. Don't use fire or water -moves. - -Strategy VERSUS Starmie: Use your highest powered move, ideally with your -starter. You can also use grass, electric, or dark moves, as Starmie is also a -psychic move. Don't use fire, water, or fighting moves. - -Strategy VERSUS Quagsire: Use your highest powered move, ideally with your -starter. You can also use grass or electric moves. Don't use fire or water -moves. Beware of Quagsire's Earthquake if you have Typhlosion. - -After Misty falls, you get your 11th badge and 4,700G. Nice! - -============================================================================== -============================================================================== -============================================================================== - -(c28) ------------------------------------------------ diff --git a/docs/gold-walkthrough/section-22-rock-tunnel-and-celadon-city-gym.txt b/docs/gold-walkthrough/section-22-rock-tunnel-and-celadon-city-gym.txt deleted file mode 100644 index cf7ac10d..00000000 --- a/docs/gold-walkthrough/section-22-rock-tunnel-and-celadon-city-gym.txt +++ /dev/null @@ -1,313 +0,0 @@ ----- 28 > Rock Tunnel and Celadon City Gym ---- ------------------------------------------------ - -Video Playthrough Link (Route 25, Cerulean City Gym, Lavender Town, & Celadon -City Gym): https://www.youtube.com/watch?v=bxbOG0kR4QE - -|-----------| -|Power Plant| -|-----------| - -Item in Power Plant: - -TM07 Zap Cannon - -Heal at the Pokémon Center and make sure you have a Pokémon in your lineup that -has the HMs Cut, Surf, and Flash. Head east on Route 9 and surf to the Power -Plant again. Head in and talk to the Manager, and he'll give you a reward for -the Machine Part: TM07 Zap Cannon! Now surf back up to Route 10 and enter Rock -Tunnel this time. - -|-----------| -|Rock Tunnel| -|-----------| - -Pokémon in Rock Tunnel: - -Cubone -Geodude -Kangaskhan -Zubat - -Items in Rock Tunnel: - -Iron -PP Up -Revive -TM47 Steel Wing - -You'll want to use HM Flash to get through the Rock Tunnel. Head along the path -to the right and go down the ladder. Then head left. Keep heading left at the -intersection to get an Iron, then head back and up and to the right on the curvy -path. - -Head up the ladder, then head down and right down another ladder. Head up from -there to grab the Revive, then come back down and go left and down for a PP Up. -Head up the ladder at the leftmost part of that section of the "tunnel". Move -down and left and you can grab TM47 Steel Wing from the cliff. Then bike down -and out of the cave and you'll be on the south side of Route 10. - -|--------| -|Route 10| -|--------| - -Welcome to the other side of Route 10. There are two trainers. - -Hiker Jim -Level 35 Machamp (1447 EXP) -You get: 1120G - -Go down and to the left and you'll find one more trainer to battle. - -PoKeFan Robert -Level 33 Quagsire (967 EXP) -You get: 2640G - -Keep going down and you'll be in Lavender Town. - -|-------------| -|Lavender Town| -|-------------| - -Lavender Town is still creepy, but a bit less so. They used to have a Pokémon -Tower in the Red, Blue, and Yellow games on the northeast part of town that's -now the Kanto Radio Tower. You can walk in and talk to the director who thanks -you for saving his job. You're so amazing that you can take out international -criminal organizations and save people's jobs! He will thank you by giving you -an EXPN Card for the Pokegear Radio and you can now tune into Kanto channels. - -You'll also find Mr. Fuji in the Soul House to the south of the Kanto Radio -Tower, and the Name Rater in the southern house. - -|-------| -|Route 8| -|-------| - -Item on Route 8: - -PRZCure Berry - -There are five trainers here. You can pick up a PRZCure Berry when you enter the -route. Otherwise, you can fight a bunch of trainers on your way west. - -Super Nerd Tom -Level 32 Magnemite (609 EXP) -Level 32 Magnemite (609 EXP) -Level 32 Magnemite (609 EXP) -You get: 1024G - -Super Nerd Sam -Level 34 Grimer (655 EXP) -Level 34 Muk (1143 EXP) -You get: 1088G - -You'll now encounter three bikers by the Kanto Pokémon Federation. They sound -scarier than they really are. First up is Biker Zeke, who collects ball-shaped -Pokémon that do chain smoking. - -Biker Zeke -Level 32 Koffing (781 EXP) -Level 32 Koffing (781 EXP) -You get: 1024G - -Biker Harris -Level 34 Flareon (1441 EXP) -You get: 1088G - -Talk to him again and he says "Wiped out by some punk from Johto." Now fight the -next trainer, who has a whole collection of Koffings. You could say that he's a -true Koffing connoisseur. - -Biker Dwayne -Level 27 Koffing (658 EXP) -Level 28 Koffing (684 EXP) -Level 29 Koffing (708 EXP) -Level 30 Koffing (732 EXP) -You get: 960G - -The mighty Kanto Pokémon Federation has quickly gone down. Yay! Now head to -Saffron City and heal at the Pokémon Center. - -|------------| -|Saffron City| -|------------| - -Head to the northwest of town, to the left of Silph Co. Now barge inside that -family's home and walk up the stairs. You'll see Mimic Girl from Red, Blue, and -Yellow. Talk to her and she'll imitate you, then ask for a PokeDoll. If you can -give her the one she wants, you'll get the Rail Pass, which will make travel -back to Johto nearly instant. - -|--------------| -|Vermilion City| -|--------------| - -Now, fly back to Vermilion City. Enter the Pokémon Lovers Club in the west part -of the city. in this city. It's the same one in where you got the Rare Candy. -Instead, talk to the guy near the Clefairy. Turns out the PokeDoll IS the -Clefairy. He'll just give you the doll, no questions asked. What a trusting -person. Now, fly back to Saffron. - -|------------| -|Saffron City| -|------------| - -Re-enter the Mimic Girl's house and up to her room/floor. Talk to her and give -it back, and she'll give you a Magnet Train Pass. How does she even have a -Magnet Train Pass to hand over to you? She's just a little girl! It's for -unlimited use on the Magnet Train that is at the northwest corner of the city, -to the left of that route-changing house. - -|-------| -|Route 7| -|-------| - -Pokémon on Route 7: - -#017 Pidgeotto -#037 Vulpix -#052 Meowth - -Head west from Saffron City to Celadon City by taking Route 7. The Saffron City -guard will check your Pokedex as ID. That's pretty weird, man. Anyway, there are -no trainers on this route, so just walk into Celadon City. There are no guards -on the Celadon City side. They don't hold prejudices against Saffron City -residents like that. Apparently, the Underground Path is also sealed due to all -the hooligan battles. - -|------------| -|Celadon City| -|------------| - -Items in Celadon City: - -Leftovers -TM03 Curse - -Heal at the Pokémon Center. To your left is a building with a front entrance and -a back entrance. It's the Celadon Mansion. It's where you can get Eevee in Red, -Blue, and Yellow. Head up three stairs, then go into the room. Talk to the guy -and he will tell you a scary story. We know it's scary because some biker rolls -down Cycling Road and most importantly, the guy tells you that it is. At the -end, he'll give you TM03 Curse. Now head back down the few stairs and out the -door. If you go through the front entrance, you'll meet the game designers. They -put pixelated versions of themselves in the game. Groovy! - -The Celadon Department Store is in the northwest side of town. Some guy says it -has a similar layout as the Goldenrod City Department Store, but he's not an -observant person, unlike you. You'll find that there are different TMs and some -things are on different floors here. - -Level 2: Potions, Heals, Ultra Balls, Escape Ropes, Repels -Level 3: TM10, TM11, TM17, TM18, TM37 Sandstorm -Level 4: Pokedolls, Mail -Level 5: Stat increase & X Items (temporary stat boosts) -Level 6: Vending Machine heal items - -You can play slots in the middle of town, because it's important to start -teaching 10-year olds how to start gambling. The Celadon Game Corner allows for -a coin exchange for prizes in the building to the right. There are people there, -but you'd want to talk to the two counters to exchange coins for prizes, which -can be TMs or Pokémon: - -TM32 1500 -TM29 3500 -TM15 7500 - -Mr. Mime 3333 -Eevee 6666 -Porygon 9999 - -The Celadon City Restaurant is on the south-central portion of the city. Like -all the best items in Pokémon, you should look in the trash can. You'll get -Leftovers, an excellent item which heals your Pokémon every turn. If you then go -out and head right to a dead end, press A and you'll get a random PP Up! Yay! - -Head to the southwest part of town with a Pokémon that can use the HM01 Cut and -you'll arrive at Erika's gym. An old man lurks outside Erika's gym saying, "This -gym is great! Only girls are allowed here!" He's totally not creepy at all. -You'll face a few trainers in Erika's gym. High-power, fire, and ice type moves -are good, while grass and water types will not be effective. - -Twins Jo & Zoe -Level 35 Victreebel (1432 EXP) -Level 35 Vileplume (1380 EXP) -You get: 700G - -Picnicker Tanya -Level 37 Exeggutor (1680 EXP) -You get: 740G - -"Do you think a girls-only gym is rare? Only girls are allowed here!" - -Lass Michelle -Level 32 Skiploom (931 EXP) -Level 34 Jumpluff (1281 EXP) -Level 33 Hoppip (522 EXP) -You get: 816G - -Head up to face the final minor trainer of the gym. - -Beauty Julia -Level 32 Paras (480 EXP) -Level 32 Exeggcute (672 EXP) -Level 35 Parasect (960 EXP) -You get: 3080G - -The lineup (from Elite 4) that you would have at this point is: - -Level 44 Sudowoodo -Level 45 Dragonair -Level 45 Raticate -Level 47 Typhlosion -Level 47 Gyarados -Level 49 Espeon - -Leave the gym, fly to the Pokémon Center (it's faster) and heal, return to -Julia, then go up to Erika. Save, then it's time to battle! - ---- -GYM LEADER ERIKA -Level 42 Tangela (1494 EXP) -Level 46 Victreebel (1882 EXP) -Level 46 Bellossom (1813 EXP) -Level 41 Jumpluff (1545 EXP) -You get: Rainbow Badge, 4,600G, TM19 Giga Drain ---- - -Erika will have three Full Restores that she can use in battle to heal her -Pokémon back to full health and normal status. - -Strategy VERSUS Tangela: Use your highest-powered attack from your starter. If -you have a high enough level Pokémon with fire or ice attacks, like Typhlosion -or Ho-Oh, you can utilize them. - -Strategy VERSUS Victreebel: Use your highest-powered attack from your starter. -If you have a high enough level Pokémon with fire or ice attacks, like -Typhlosion or Ho-Oh, you can utilize them. - -Strategy VERSUS Bellossom: Bellossom looks like it will happily dance for you, -but it has Synthesis, so it can heal half of its health every turn for quite a -while. It can be pretty annoying. It also has SolarBeam, which is a solid -two-turn grass move that could really damage your Water Pokémon. Use your -highest-powered attack from your starter. If you have a high enough level -Pokémon with fire or ice attacks, like Typhlosion or Ho-Oh, you can utilize -them. - -Strategy VERSUS Jumpluff: Yes, as much as it looks like a water type, it's a -grass type. Use your highest-powered attack from your starter. If you have a -high enough level Pokémon with fire or ice attacks, like Typhlosion or Ho-Oh, -you can utilize them. - -Erika congratulates you and gives you your 12th badge. Yay! You're amazing! Give -yourself a pat on the back, or do a hair flip. TM19 Giga Drain is a good move, -but unfortunately, it's only a 5 PP move. I am deeply saddened by this. I know -you are, too, because how could it not be sad? It's a travesty. Anyway, now walk -out the convoluted maze out the gym. - -============================================================================== -============================================================================== -============================================================================== - -(c29) ------------------------------------------------- diff --git a/docs/gold-walkthrough/section-23-routes-11-19-and-fuchsia-city-gym.txt b/docs/gold-walkthrough/section-23-routes-11-19-and-fuchsia-city-gym.txt deleted file mode 100644 index 7c29dc7e..00000000 --- a/docs/gold-walkthrough/section-23-routes-11-19-and-fuchsia-city-gym.txt +++ /dev/null @@ -1,468 +0,0 @@ ----- 29 > Routes 11-19 and Fuchsia City Gym ---- ------------------------------------------------- - -Video Playthrough Link (Routes 11-13): -https://www.youtube.com/watch?v=bZQxBI2v0As - -Video Playthrough Link (Routes 14-15): -https://www.youtube.com/watch?v=-zeVjtH0UWI - -Video Playthrough Link (Routes 16-19 & Fuchsia City Gym): -https://www.youtube.com/watch?v=YYq_0WosMX4 - -|--------| -|Route 12| -|--------| - -Pokémon on Route 12: - -#072 Tentacool (water) -#129 Magikarp (water) - -Fly to Lavender Town, then go south. If you talk to the fishermen, you'll -distract them from their fishing and they'll fight you with their catches from -their fishing rods. - -Fisher Kyle -Level 28 Seaking (1020 EXP) -Level 31 Poliwhirl (870 EXP) -Level 31 Seaking (1128 EXP) -You get: 1240G - -Fisher Martin -Level 32 Remoraid (534 EXP) -Level 32 Remoraid (534 EXP) -You get: 1280G - -After fighting Fisher Martin, you'll have a chance to take the route westward. -Do so. - -|--------| -|Route 11| -|--------| - -Pokémon on Route 11: - -#019 Rattata -#096 Drowzee -#097 Hypno - -Items on Route 11: - -Berry -Revive - -Follow the curvy road and you'll fight four trainers. Why did the road have to -be so curvy? No idea. You could have at least gotten a roller coaster experience -out of it or something. - -Psychic Herman -Level 30 Exeggcute (630 EXP) -Level 30 Exeggutor (1362 EXP) -Level 30 Exeggcute (630 EXP) -You get: 960G - -Keep going left and you'll run into your old classmate Owen. Did you forget who -he was? Yeah, me too. - -Youngster Owen -Level 35 Growlithe (682 EXP) -You get: 560G - -Young trainer, follow the route up and left and you will find salvation. Or at -least another youngster just like yourself. - -Youngster Jason -Level 33 Sandslash (1152 EXP) -Level 33 Crobat (1441 EXP) -You get: 528G - -If you take the path up and then right, you can grab a Berry and also a Revive -from the bush. Your parents probably told you not to pick up wild berries, and -yet, here we are, disobeying our parents. Nice work. Now, head back towards -Youngster Jason and go further west and you'll face one more trainer. - -Psychic Fidel -Level 34 Xatu (1245 EXP) -You get: 1088G - -Head west and you'll find that a Snorlax is blocking you from entering Diglett's -Cave or reaching Vermilion. Remember this. You'll come back to that later, my -dear friend. Now, head back east to Route 12. - -|--------| -|Route 12| -|--------| - -Items on Route 12: - -Calcium -Super Rod - -Head down, then talk to the fisherman. - -Fisher Stephen -Level 25 Magikarp (106 EXP) -Level 25 Magikarp (106 EXP) -Level 31 Qwilfish (663 EXP) -Level 31 Tentacruel (1360 EXP) -You get: 1240G - -Head south and go into the house. Talk to the guy who is the Fishing Guru's -younger brother. Answer Yes to his question and you'll get a Super Rod! Woot. -Anyway, keep heading down Route 12. - -Fisher Barney -Level 30 Gyarados (1375 EXP) -Level 30 Gyarados (1375 EXP) -Level 30 Gyarados (1375 EXP) -You get: 1200G - -Head down the path and you'll see an item that is blocked by a tree. Cut down -the tree so you can get a single item: Calcium. I'm sure that was worth all the -environmental damage you just caused. Now, head south onto Route 13. - -|--------| -|Route 13| -|--------| - -Pokémon on Route 13: - -#017 Pidgeotto -#030 Nidorina -#187 Hoppip - -Head south. Fight the left one first. - -Bird Keeper Perry -Level 34 Farfetch'd (684 EXP) -You get: 816G - -Then the right guy. - -Bird Keeper Bret -Level 32 Pidgeotto (774 EXP) -Level 32 Fearow (1110 EXP) -You get: 768G - -Now the left guy. It's like a dance. Left, right, left. It takes two to tango. -This third trainer hoards mice. - -PoKeFan Joshua -Level 23 Pikachu (403 EXP) -Level 23 Pikachu (403 EXP) -Level 23 Pikachu (403 EXP) -Level 23 Pikachu (403 EXP) -Level 23 Pikachu (403 EXP) -Level 23 Pikachu (403 EXP) -You get: 1840G - -Head around that trainer, then head left. - -PoKeFan Alex -Level 29 Nidoking (1210 EXP) -Level 29 Slowking (1018 EXP) -Level 29 Seaking (1056 EXP) -You get: 2320G - -Head left again in the weird maze to face a hiker. - -Hiker Kenny -Level 27 Sandslash (942 EXP) -Level 29 Graveler (832 EXP) -Level 31 Golem (1174 EXP) -Level 29 Graveler (832 EXP) -You get: 928G - -|--------| -|Route 14| -|--------| - -Pokémon on Route 14: - -#030 Nidorina -#195 Quagsire - -Welcome to Route 14. If you need to heal on HP and PP, feel free to fly back to -Lavender Town, heal at the Pokemon Center, and then bike back down. Otherwise, -you can keep going. You'll spot a trainer as you ride down. Just above him, -there's a tree that can be cut. Cut it and go left for a fight. - -PoKeFan Trevor -Level 33 Psyduck (565 EXP) -You get: 2640G - -Cut the tree above him and you have a chance to trade a Chansey for an -Aerodactyl with the female trainer there. You can also try to catch wild Pokémon -in this small patch of grass. Otherwise, head right to face the trainer you -skipped. - -PoKeFan Carter -Level 29 Bulbasaur (397 EXP) -Level 29 Squirtle (409 EXP) -Level 29 Charmander (403 EXP) -You get: 2320G - -Head down to face... - -Bird Keeper Roy -Level 29 Fearow (1006 EXP) -Level 35 Fearow (1215 EXP) -You get: 840G - -Head left and cut the tree, onto... - -|--------| -|Route 15| -|--------| - -Pokémon on Route 15: - -#017 Pidgeotto -#030 Nidorina -#033 Nidorino - -Cut the tree on your way west. Grab the PP Up as you ride west, then bike back -to where you cut the tree, go down, and then go west to fight six straight -trainers. You discovered a whole line of teachers and schoolboys on a field -trip. They're way too excited to get into schoolyard fights with you. - -Schoolboy Johnny -Level 29 Bellsprout (522 EXP) -Level 33 Victreebel (1350 EXP) -Level 31 Weepinbell (1002 EXP) -You get: 1056G - -Teacher Colette -Level 36 Clefairy (523 EXP) -You get: 2592G - -Schoolboy Billy -Level 27 Paras (405 EXP) -Level 27 Paras (405 EXP) -Level 27 Poliwhirl (757 EXP) -Level 35 Ditto (457 EXP) -You get: 1120G - -Teacher Hillary -Level 32 Aipom (643 EXP) -Level 36 Cubone (670 EXP) -You get: 2592G - -Schoolboy Tommy -Level 32 Xatu (1171 EXP) -Level 34 Alakazam (1354 EXP) -You get: 1088G - -Schoolboy Kipp -Level 27 Voltorb (595 EXP) -Level 27 Magnemite (514 EXP) -Level 31 Voltorb (684 EXP) -Level 31 Magneton (1069 EXP) -You get: 992G - -|------------| -|Fuchsia City| -|------------| - -Item in Fuchsia City: - -Burnt Berry - -You'll learn from various people that the Safari Zone in Fuchsia City has closed -since three years ago when Red, Blue, and Yellow took place. The warden fled the -premises. If you go to the building, even the door is gone. What happened to it? -You can still pick up a Burnt Berry as a consolation prize in the northwest part -of town. - -You can heal at the Pokémon Center and go fight the gym. Or you can heal and -then get more experience first on Routes 16, 17 (Cycling Road), and 18. The -choice is up to you. I'm not your teacher. Well, I guess I might be, if you're -reading this guide. I'd suggest checking out Routes 16-18 to check it out, if -nothing else. The new Kanto region is worth exploring. - -|--------| -|Route 16| -|--------| - -Pokémon on Route 16: - -#022 Fearow -#088 Grimer - -Fly to Celadon City and go west. You can use Cut on the tree to reach it; it's -north of the route-changing building. If you enter the one building there and -come back out, the tree will grow back and you'll have to cut it again. Guess -that environmental destruction doesn't actually last that long in this world. - -|--------| -|Route 17| -|--------| - -Pokémon on Route 17: - -#022 Fearow -#088 Grimer - -You could skip Route 16 and go straight through the route-changing building to -Route 17. This is Cycling Road, so get on your bike. Remember you need to -advertise for your friend, the Goldenrod City Bike Shop dude. It's still -possible to bike uphill but you're better off biking downhill. Bike down and -you'll run into your first biker. Possibly literally. It's okay, because -trainers in this world can get hit by a bike and hold you back like they're a -wall. - -Biker Riley -Level 34 Weezing (1260 EXP) -You get: 1088G - -After this point, there will be branching left and right paths. Head down the -left route and you'll fight your next biker. - -Biker Glenn -Level 28 Koffing (684 EXP) -Level 30 Magmar (1072 EXP) -Level 32 Weezing (1185 EXP) -You get: 1024G - -You'll get another chance to stay left or go right. When you see a bridge across -the river to the right, cross it and fight the next biker. - -Biker Joel -Level 32 Magmar (1144 EXP) -Level 32 Magmar (1144 EXP) -You get: 1024G - -If you talk to him afterwards, he says, "I look cool, but I'm weak, so I'm not -really cool." I'm not sure that's how the coolness factor works but I have no -reason to question him. Now, keep rolling down until you run into the last -trainer. - -Biker Charles -Level 30 Koffing (732 EXP) -Level 30 Weezing (1111 EXP) -Level 30 Charmeleon (912 EXP) -You get: 960G - -Roll down to the end of Cycling Road and you'll be on Route 18. - -|--------| -|Route 18| -|--------| - -Pokémon on Route 18: - -#022 Fearow -#088 Grimer - -Head east and you'll face some guy that...hey, he almost has my name! - -Bird Keeper Bob -Level 34 Noctowl (1179 EXP) -You get: 816G - -He tells you that the shortcut to Celadon is the uphill Cycling Road. That -doesn't sound wise. Now head down into the grass to fight the last trainer on -the road to Fuchsia City. - -Bird Keeper Boris -Level 30 Doduo (616 EXP) -Level 32 Dodrio (1083 EXP) -Level 28 Doduo (576 EXP) -You get: 768G - -Now head east into Fuchsia City! - -|------------| -|Fuchsia City| -|------------| - -Fuchsia is such a hard color to spell. I always think the s is before the c. -Anyway, you can go to the southwest part of town again to heal at the Pokémon -Center, and then enter the gym. Head in and you'll find that everyone looks the -same. They're all imitating Janine. Janine's located in the middle-left side of -the gym. But it's good to battle everyone for the experience and we can sort of -see the walls. - -High-powered moves and psychic moves are good for Janine's gym. Head up and talk -to the first trainer. - -Lass Linda -Level 30 Bulbasaur (411 EXP) -Level 34 Venusaur (1515 EXP) -Level 32 Ivysaur (966 EXP) -You get: 816G - -Now head to the gym entrance (but don't exit the gym) and head to the far right -and up. Then, fight that trainer. Ignore the fact that she says she's Janine. -She isn't. - -Picnicker Cindy -Level 36 Nidoqueen (1495 EXP) -You get: 720G - -Head up then left, then talk to that trainer. It should be obvious by now that -the real Janine wouldn't be spinning around. :P - -Camper Barry -Level 36 Nidoking (1503 EXP) -You get: 720G - -Now head down and face the fourth trainer. - -Lass Alice -Level 30 Gloom (847 EXP) -Level 34 Arbok (1071 EXP) -Level 30 Gloom (847 EXP) -You get: 720G - -At this point, you'll see the real Janine. Save, and heal if you need to. Janine -is Koga's daughter, and has taken over for Dad after he joined the Elite Four. -Janine fights like a weaker version of Koga. - ---- -GYM LEADER JANINE -Level 36 Crobat (1573 EXP) -Level 33 Ariados (946 EXP) -Level 39 Venomoth (1152 EXP) -Level 36 Weezing (1333 EXP) -Level 36 Weezing (1333 EXP) -You get: Soul Badge, 3,900G, TM06 (Toxic) ---- - -Strategy VERSUS Crobat: Use the highest-powered move from your starter, keeping -in mind that Meganium will be weak against poison moves in this gym. Also -acceptable are psychic moves which have a type advantage against poison. -Crobat's also a flying type, so you can also use ice and electric moves like -Blizzard or Thunder. - -Strategy VERSUS Espeon: Unleash psychic from Espeon. Use the highest-powered -move from your starter. Also acceptable are psychic moves which have a type -advantage against poison. - -Strategy VERSUS Ariados: Use the highest-powered move from your starter, keeping -in mind that Meganium will be weak against poison moves in this gym. Also -acceptable are psychic moves which have a type advantage against poison. Ariados -is also a bug, so fire moves from a Pokémon like Ho-Oh (e.g., Sacred Fire) or -Typhlosion (e.g., Flame Wheel) will be super effective. - -Strategy VERSUS Venomoth: Use the highest-powered move from your starter, -keeping in mind that Meganium will be weak against poison moves in this gym. -Also acceptable are psychic moves which have a type advantage against poison. -Venomoth is also a bug, so fire moves from a Pokémon like Ho-Oh (e.g., Sacred -Fire) or Typhlosion (e.g., Flame Wheel) will be super effective. - -Strategy VERSUS Weezings: Use the highest-powered move from your starter, -keeping in mind that Meganium will be weak against poison moves in this gym. -Also acceptable are psychic moves which have a type advantage against poison. - -Thirteen gyms down, three to go. You have defeated Janine. Congrats! She'll even -give you TM06 Toxic, which is a poison that zaps enemy HP. She hopes to become -better than Koga and you. - -============================================================================== -============================================================================== -============================================================================== - -(c30) ------------------------------------------- diff --git a/docs/gold-walkthrough/section-24-snorlax-and-pewter-city-gym.txt b/docs/gold-walkthrough/section-24-snorlax-and-pewter-city-gym.txt deleted file mode 100644 index 5bcf4f96..00000000 --- a/docs/gold-walkthrough/section-24-snorlax-and-pewter-city-gym.txt +++ /dev/null @@ -1,192 +0,0 @@ ----- 30 > Snorlax and Pewter City Gym ---- ------------------------------------------- - -Video Playthrough Link (Snorlax, Route 2, Pewter City, & Tin Tower): -https://youtu.be/l9CjbBMD7Uk - -Video Playthrough Link (Ho-Oh, Pewter City Gym, Routes 1-4, & Pallet Town): -https://www.youtube.com/watch?v=pFgyFZqai_I - -Anyway, head south onto Route 19... - -|--------| -|Route 19| -|--------| - -You'll find your way blocked. What happened? Well, boulders have washed ashore -in a recent event. I'll tell you more about it later, it's a spoiler kinda. Now -fly back to Vermilion City. - -|-------| -|Snorlax| -|-------| - -Using your upgraded radio, go east to Diglett's Cave. Keep around those four -Ultra Balls from before, you'll need them. When you're up to the Snorlax, save -(wow, I haven't said that for ages). You'll find out that Snorlax is sleeping. -Wake it up by going into your Pokegear option and going to your radio. Turn the -radio up to 20, the maximum frequency, then exit the Pokegear menu. Then press A -and talk to Snorlax. That maximum frequency is the PoKeFlute, so the Snorlax -will wake up in an angry mood. - -Keep it down at red HP or low yellow HP and throw as many Ultra Balls at it. If -you can use a status move like paralysis on it (e.g. Dragonair's Thunder Wave), -that helps, or catch Snorlax while it's still asleep from Rest. But it might -take a few resets. - -Snorlax is a great Pokémon to catch. It is one of the best Pokémon in the game, -and it's Level 50. You'll be able to add this to your team immediately. It is -highly defensive and bulky, and it can sleep for two turns using Rest to heal -all of its HP and status effects. Snorlax also comes with Leftovers, an amazing -item that heals a bit of HP after every attack. You can replace its Rollout move -with HM Strength to inflict extra damage. - -After the battle with Snorlax, the route is cleared to Diglett's Cave and -through Route 8. Go back to the Pokémon Center in Vermilion City to withdraw the -Snorlax so that it is part of your lineup. It already has a Leftovers attached! -Now, enter the cave entrance that Snorlax blocked. This is Diglett's Cave. - -|--------------| -|Diglett's Cave| -|--------------| - -Pokémon in Diglett's Cave: - -#050 Diglett -#051 Dugtrio - -There are no trainers in here, but you can capture both Diglett and Dugtrio here -if you'd like. Head down the ladder, then ride your bike up and then up that -ladder. Exit the door and you'll be on Route 2. - -|-------| -|Route 2| -|-------| - -Items on Route 2: - -Carbos -Dire Hit -Elixir -Nugget -PSNCure Berry - -Welcome to the western part of Kanto. Head up and right to grab the Carbos. -Then, go down to the house and talk to the guy inside for a Nugget for being a -visitor. Cut the tree to the south and shift past the route-changing house. -You'll still be in Route 2. Keep heading down and you'll get an Elixir. Then, -cut the tree just left of it and head north and you'll face a trainer. - -Bug Catcher Rob -Level 32 Beedrill (1089 EXP) -Level 32 Butterfree (1096 EXP) -You get: 512G - -Keep moving up. - -Bug Catcher Doug -Level 34 Ariados (975 EXP) -You get: 544G - -Continue moving up and you can get Dire Hit, a PSNCure Berry, and even a Max -Potion in Viridian Forest. Keep moving up. - -Bug Catcher Ed -Level 30 Beedrill (1021 EXP) -Level 30 Beedrill (1021 EXP) -Level 30 Beedrill (1021 EXP) -You get: 480G - -If you keep heading north, you'll end up in Pewter City. - -|-----------| -|Pewter City| -|-----------| - -Items in Pewter City: - -Ice Berry -Mint Berry -Rainbow/Silver Wing - -Welcome to Pewter City! Talk to the man just to the right of the Pokemart and -you'll get the Silver Wing, which will allow you to go back to -Johto and catch the other Pokémon (Lugia or Ho-Oh) that you haven't already -caught. Now heal and deposit any items you'd like. The Pokémon Center has a lady -that allows you to trade a Gloom for a Rapidash. The northeast side of town has -an Ice Berry and a Mint Berry. And the northwest side has the former Museum of -Science which is now closed. They even filled the doorway in. So sad. - -If you have a team that focuses on leveling up your starter, you might have -something like this: - -Level 77 Feraligatr -Level 50 Snorlax -Level 40 Lugia -Level 33 Donphan (Silver) -Level 30 Gyarados -Level 28 Raticate - -If you're fielding more of a balanced lineup, you might have a lineup like this: - -Level 51 Espeon -Level 50 Snorlax -Level 48 Starter Evolution -Level 47 Gyarados -Level 46 Dragonair -Level 46 Sudowoodo - -For this gym, you'll ideally want to use your starter (if Meganium or -Feraligatr) for this gym, and use their highest-powered grass or water moves. - -Camper Jerry -Level 37 Sandslash (1291 EXP) -You get: 740G - -Save, heal at the Pokemon Center and come back, then talk to Brock to fight him -for the 14th gym badge. Rock types may have a lot of defense, but not against -grass or water moves. If you have Typhlosion as your starter and a lower-level -Lugia (from SoulSilver), Brock may take you a bit longer to beat. All of Brock's -Pokémon are 10-11 levels higher than in the original. - ---- -GYM LEADER BROCK -Level 41 Graveler (1176 EXP) -Level 42 Omastar (1791 EXP) -Level 41 Rhyhorn (1185 EXP) -Level 44 Onix (1017 EXP) -Level 42 Kabutops (1809 EXP) -You get: Boulder Badge, 4,200G ---- - -Strategy VERSUS Graveler: Use your highest-powered move from your highest level -Pokémon. Ideally, this would be a grass or water move (Razor Leaf/SolarBeam or -Surf/Hydro Pump) rather than a fire or normal move. - -Strategy VERSUS Omastar: Use your highest-powered move from your highest level -Pokémon. Ideally this is a grass move, not a fire or normal move. Omastar is a -water and rock type, but a water move will still do regular damage against it. - -Strategy VERSUS Rhyhorn: Use your highest-powered move from your highest level -Pokémon. Ideally, this would be a grass or water move (Razor Leaf/SolarBeam or -Surf/Hydro Pump) rather than a fire or normal move. - -Strategy VERSUS Onix: Use your highest-powered move from your highest level -Pokémon. Ideally, this would be a grass or water move (Razor Leaf/SolarBeam or -Surf/Hydro Pump) rather than a fire or normal move. - -Strategy VERSUS Kabutops: Use your highest-powered move from your highest level -Pokémon. Ideally this is a grass move, not a fire or normal move. Omastar is a -water and rock type, but a water move will still do regular damage against it. - -There you have it: 14 badges! You are then given the Boulder Badge and some -money. The badge makes your Pokémon even more powerful. Like the other gym -leaders, Brock is a little upset that you beat his "hard-rock defense, but happy -to have battled you. Now exit the gym and heal at the Pokémon Center. - -============================================================================== -============================================================================== -============================================================================== - -(c31) ----------------------------------------------- diff --git a/docs/gold-walkthrough/section-25-lugia-gold-and-ho-oh-silver.txt b/docs/gold-walkthrough/section-25-lugia-gold-and-ho-oh-silver.txt deleted file mode 100644 index 56263043..00000000 --- a/docs/gold-walkthrough/section-25-lugia-gold-and-ho-oh-silver.txt +++ /dev/null @@ -1,179 +0,0 @@ ----- 31 > Lugia (Gold) and Ho-Oh (Silver) ---- ----------------------------------------------- - -Video Playthrough Link (Ho-Oh, Pewter City Gym, Routes 1-4, & Pallet Town): -https://www.youtube.com/watch?v=pFgyFZqai_I - -Video Playthrough Link (Seafoam Islands Gym, Routes 19-22, 28, & Viridian City -Gym): https://youtu.be/wgql5WCnx4g - -Now, you have a chance to snatch the other Pokémon you missed earlier...Lugia or -Ho-oh. Head back through with the Magnet Train back to Johto, then go to -wherever you need to go. - -|----------------------------| -|Whirl Islands - Gold Version| -|----------------------------| - -Fly to Olivine City. Make sure you have a Pokémon on your team that has Flash, -like Bellsprout. Also make sure you teach HM06 Whirlpool to a Pokémon. I suggest -Tentacool or Krabby, who can use Strength, Surf, Whirlpool, and Waterfall. -You'll need eight badges in order to use Whirlpool. - -Pokémon at the Whirl Islands: - -#041 Zubat -#086 Seel -#098 Krabby -#116 Horsea -#117 Seadra - -Items at the Whirl Islands: - -Calcium -Escape Rope -Guard Specs -Max Revive x2 -Ultra Ball - -Surf southwest and west until you reach the northeast cave of the Whirl Islands. -I suggest using a Max Repel to reduce the number of Tentacool encounters on the -way there. The whirlpool is just northwest of where you fought Swimmer Kara. You -can press A on any whirlpool to remove it temporarily from the map. It's -incredible how much stamina these swimmers have, just staying in the water days -after you last fought them. - -Once you find the northeast cave entrance, enter it and you'll see darkness. Use -HM Flash, then head up and leap the ledge to the right on that bike. You'll see -a Ultra Ball on the cliff. To get to it and continue on your journey, leap down -another ledge to the right, go up the steps, grab the Max Revive, and head down -the ladder. Head south, then jump to the left. Keep jumping down and you'll get -an Escape Rope. Woo! - -Now go down and then left, then up the ladder. You can now head out the door to -breathe, but that's pointless. Why would you do that? Instead, surf east, then -grab the Guard Specs. Then head down the ladder, head up along the path, head -down, and then up the cliff. It's like a roller coaster, I know. - -There's yet another item here. It's a Calcium. Now, on that cliff, head down to -the ladder and take it. Now, go down out the door. That's most of the items in -the Whirl Islands. - -Now that you've gone from the northeast to the southeast portion of the Whirl -Islands, go counterclockwise and surf your way southwest out of the reefs and -then Whirlpool out. Head east, then north, and you'll find Swimmer Kara again. -Take that Whirlpool out and head back into the entrance that you originally came -from. - -Use Flash again. Drop down the first cliff again, but this time, drop down the -top cliff rather than the bottom cliff. Then head down the ladder. Continue -along the path south and head down that ladder. Grab the Max Revive to the left -of you, then surf south. There are two items on the cliff but you can't reach -those. - -Instead, surf down the waterfall, then head left and through the door. Head in, -and surf north to Lugia. Save before you interact with Lugia. I suggest that you -save before you fight him because this is the only chance you'll be able to get -Lugia in the entire game. If you knock it out on a critical hit, lose all your -Pokémon and black out, or run out of balls to throw at it, you would want to -return to your save and retry. - -Because the catch rate is only about 2% at low health, even with Ultra Balls, -this may involve a dozen or more resets. Ultimately, you'll want to maximize -your chances by throwing many Ultra Balls at it. You'll want to do so when it is -lower on HP (yellow or red on the HP meter), and ideally when it is asleep. - -Focus on using Ultra Balls when Lugia is low on health, even when it could knock -out your Pokémon. Use Revives and Hyper Potions as is helpful to keep your -Pokémon alive to keep throwing balls at it. You can slightly increase your catch -chances if you put Lugia to sleep or paralyze it. - -Thard_Verad adds, "Lugia is just heavy enough for Heavy Balls to have higher -catch rates than Ultra Balls. Give those a try." - -Catch it, then use the Escape Rope you grabbed earlier to get out of here. - -|--------------------------| -|Tin Tower - Silver Version| -|--------------------------| - -Pokémon in Tin Tower: - -#019 Rattata - -Go back to Ecruteak City and then go into the Bell Tower entry house. Talk to -the man and he'll let you through because you have the Ecruteak City Badge. Then -follow up the trail to the tower itself. - -Go up and to the left and another bald man will let you through because you have -the beautiful Rainbow Wing. You can then go up the ladder. Keep going and wild -Rattatas will start converging on you like zombies. Consider a Max Repel or two -to drive away all the zombies. Then go up the ladder, hop left for a Full Heal, -then hop all the right. Then hop all the way left, then keep hopping right. On -the middle platform, you might get stuck a bit because you don't see the next -jump easily, but it's the second square from the top. You'll make it to the -northeast side and you can go up the ladder. You are now a true bunny. Are you a -bad bunny? - -On the next level, jump down, left, down, left, down, down to get to the Ultra -Ball. Jump right, down, left, down, down, and you'll get to a PP Up. If you go -up the ladder, it won't get you anywhere, so no need to do that. You can -continue with jumps left (x5), down, right (x2), down, left (x2), down, left -(x2) to get an Escape Rope. You'll be next to another ladder. That ladder will -also take you nowhere. Jump right, right, left, left, and you'll be on the -northwest side where you do want to take that ladder up. - -On the next level, jump right twice, go all the way to the right and down, jump -down, left, left, and you'll get a Rare Candy. You can then return to the -previous level's bottom right corner and go all the way back up to the top left -corner to get back up to the beginning of the level. Then jump right and right -again, then down, left, down (x2), and take the right down (x4) up to the next -level (Left down takes you back to the bottom left ladder from the previous -level). - -On the next level, go up and you'll get a Max Potion. Then go right across the -bridge, up, and then left across the upper (not middle) bridge to get a Full -Heal. Then hop down twice to get up the next ladder. - -On the next level, go up, go through the next two warps, jump on the top side -left (x8), down (x3), right (x3), and through that warp to get to a Max Elixir. -Go back through the warp and jump right (x4) to return to the start. Warp back -through the top right warp and then back through the other warp you went -through. You'll be back to the very start of this paragraph. Hop to the right -(x4) to get a Max Revive. Then hop down (x5), go left and down, warp, cross the -bridge down, and warp again. - -Go left and you'll get a HP Up. Keep going left and down and warp again. Then -cross the bridge up left and take the warp. Go up the ladder, cross the planks -down, then go up the ladder and you'll be in front of a Level 70 Ho-Oh. I -suggest that you save before you fight Ho-Oh because this is the only chance -you'll be able to get Ho-oh in the entire game. If you knock it out on a -critical hit, lose all your Pokémon and black out, or run out of balls to throw -at it, you would want to return to your save and retry. - -The Ho-Oh in-game music gives off great vibes. As we all know, the vibes are -what matters. We must have the vibes. Ho-Oh is a fire/flying type Pokemon with -Safeguard and Ancient Power (damage w/ stat boosts), Punishment, and Sacred -Fire. Use your highest level Pokémon to whittle down Ho-Oh's HP first without -knocking it out, then try to throw as many Ultra Balls as you can at it. - -Because the catch rate is only about 2% at low health, even with Ultra Balls, -this may involve a dozen or more resets. Ultimately, you'll want to maximize -your chances by throwing many Ultra Balls at it. You'll want to do so when it is -lower on HP (yellow or red on the HP meter), and ideally when it is asleep. Use -Revives and Hyper Potions as is helpful to keep your Pokémon alive to keep -throwing balls at it. However, all things even, focus on using Ultra Balls when -Ho-Oh is low on health, even when it could knock out your Pokémon, to increase -the number of chances yo have. You can slightly increase your catch chances if -you put Ho-Oh to sleep or paralyze it. - -After you catch Ho-Oh, go back down the stairs and then you can use an Escape -Rope to leave. Then, ride the Magnet Train back to Kanto and fly back to Pewter -City. - -============================================================================== -============================================================================== -============================================================================== - -(c32) ----------------------------------------- diff --git a/docs/gold-walkthrough/section-26-mount-moon-and-routes-1-4.txt b/docs/gold-walkthrough/section-26-mount-moon-and-routes-1-4.txt deleted file mode 100644 index 481cb91f..00000000 --- a/docs/gold-walkthrough/section-26-mount-moon-and-routes-1-4.txt +++ /dev/null @@ -1,232 +0,0 @@ ----- 32 > Mount Moon and Routes 1-4 ---- ----------------------------------------- - -|-------| -|Route 3| -|-------| - -Pokémon on Route 3: - -#019 Rattata -#023 Ekans -#024 Arbok - -As you follow the path east on Route 3, you'll face four trainers. - -Youngster Warren -Level 35 Fearow (1215 EXP) -You get: 560G - -Head up, then right to... - -Youngster Jimmy -Level 33 Raticate (819 EXP) -Level 33 Arbok (1039 EXP) -You get: 528G - -Head on right to... - -Firebreather Otis -Level 29 Magmar (1036 EXP) -Level 32 Weezing (1185 EXP) -Level 29 Magmar (1036 EXP) -You get: 1392G - -Head right and along the path, then up to... - -Firebreather Burt -Level 32 Koffing (781 EXP) -Level 32 Slugma (534 EXP) -You get: 1536G - -Before you head into Mount Moon, heal back at the Pewter City Pokémon Center, -come back, and then save, because you're about to have another major battle. - -|----------| -|Mount Moon| -|----------| - -Pokémon at Mt. Moon: - -#041 Zubat -#046 Parus -#074 Geodude - -When you get to Mt. Moon, you will encounter your rival for the first time in a -long time. This may or may not be the final time you battle him, but he has -levelled up his Pokémon and finally has six Pokémon. At this point, you can use -any high-powered moves from your highest-level Pokémon that the Rival's Pokémon -doesn't resist, and you'll be fine. - -|------------------------------------------| -|Rival | -|Level 41 Sneasel (1159 EXP) | -|Level 42 Golbat (1539 EXP) | -|Level 45 Starter Evolution (2005-2025 EXP)| -|Level 41 Magneton (1414 EXP) | -|Level 43 Gengar (1750 EXP) | -|Level 43 Alakazam (1713 EXP) | -|You get: 4500G | -|------------------------------------------| - -Strategy VERSUS Sneasel: Sneasel is a dark type, so use the highest-powered -attack on your highest-level Pokémon that isn't a psychic move. - -Strategy VERSUS Golbat: Golbat is a poison and flying type. Use the -highest-powered attack on your highest-level Pokémon that isn't a ground move. -Golbat is a flying type that resists ground moves. Electric moves will be super -effective. Beware of Confuse Ray, as that will confuse your Pokémon and take -away attack opportunities. - -Strategy VERSUS Starter Evolution: The starter evolution will have the type -advantage on your starter, so don't use same-type attacks from your own starter. -Use the most high-powered attack on your highest-level Pokémon (likely Strength -or Body Slam). Or you can utilize Lugia/Ho-Oh to do the requisite water damage -against Typhlosion or fire damage against Meganium. - -Strategy VERSUS Magneton: Use the most high-powered attack on your highest-level -Pokémon. Magneton does resist most types of moves due to its steel typing, but -fire and fighting moves would be strong against it. - -Strategy VERSUS Gengar: Gengar is a ghost and poison type. Use the most -high-powered attack on your highest-level Pokémon that isn't a normal or -fighting move, as Gengar will completely resist those attacks as a ghost type. - -Strategy VERSUS Alakazam: Alakazam is a pure psychic type. Use the most -high-powered attack on your highest-level Pokémon that isn't a fighting or -psychic move. - -Your rival's not nearly as confident as he was at the beginning of the game, -after losing to you over and over. Save after you beat the Rival, and heal back -at a Pokémon Center if that's useful. There are two ways past Mount Moon. If you -go right from your Rival battle, up the ladder, and then out, you can go to a -shop outside that sells Fresh Water, Soda Pop, and Lemonade. If you then go down -into another cave entrance and down the ladder, you'll make your way through. - -Alternatively, if you go down from the Rival battle, then to the right, and go -out that door, you'll end up in the same spot as going the path above. Either -way, you can face up to three trainers on your east on Route 4. - -Thard_Verad adds, "Monday nights at Mt. Moon, you might catch Clefairy dancing -at the lake. Bring Rock Smash." Also, "After you defeat Silver at Mt. Moon, you -can challenge him at Indigo Plateau once a day on Monday and Wednesday. You can -also visit Dragon Den on Tuesday or Thursday and see him training." - -|-------| -|Route 4| -|-------| - -Pokémon on Route 4: - -#019 Rattata -#021 Spearow -#024 Arbok - -Item on Route 4: - -HP Up - -Route 4 has three trainer battles as you head east. - -Picnicker Hope -Level 34 Flaaffy (852 EXP) -You get: 680G - -Bird Keeper Hank -Level 12 Pidgey (141 EXP) -Level 34 Pidgeot (1252 EXP) -You get: 816G - -Picnicker Sharon -Level 31 Furret (769 EXP) -Level 33 Rapidash (1357 EXP) -You get: 660G - -Head east, then up to grab the HP Up. Now, you can fly back to Pewter City to -heal at the Pokémon Center, head south onto Route 2 and south through Viridian -Forest, and you'll be in Viridian City. - -|-------------| -|Viridian City| -|-------------| - -North from the Pokémon Center is the One-on-One Trainer House. This is probably -your easiest way of getting experience every day. You can go in here every day -to fight Trainer Cal, or a placeholder for Cal. Simply go downstairs in that -house to do so. Head left after you go downstairs and the lady will talk to you. -Say Yes and you can battle Cal after you go down. He will have the final -evolutions of the three starters. - -Thard_Verad adds, "Pokemon Trainer Cal is a placeholder. If you Mystery Gift -with anyone, they can show up at the Trainer House instead." - -Pokémon Trainer Cal -Level 50 Meganium (2227 EXP) -Level 50 Typhlosion (2238 EXP) -Level 50 Feraligatr (2250 EXP) -You get: 5000G - -Strategy VERSUS Meganium: Meganium is a grass type. Avoid using grass or water -moves. Use fire or flying moves from Typhlosion, Ho-Oh, or Lugia against -Meganium. Keep in mind that Meganium has Toxic, and Toxic can inflict poison -damage on your Pokémon every turn. - -Strategy VERSUS Typhlosion: Typhlosion is a fire type and its best move is -Flamethrower, although its fighting-move Focus Blast also packs a punch. Avoid -using fire or grass moves. Use water moves, like Surf, Waterfall, Hydro Pump -from Feraligatr, Lugia, or Gyarados. Rock moves like Ho-Oh's AncientPower can -also be effective. - -Strategy VERSUS Feraligatr: Feraligatr is a strong water type. Avoid using fire -or water moves. Use grass or electric moves against Feraligatr. Keep in mind -that Feraligatr can use Ice Punch against Meganium and all the flying -legendaries (Ho-Oh, Lugia, and others that you catch later). Additionally, -Feraligatrs water moves can still be effective against electric type Pokémon -despite being weak to electric attacks. - -Then head south onto Route 1 towards Pallet Town. - -|-------| -|Route 1| -|-------| - -Pokémon on Route 1: - -#016 Pidgey -#162 Furret - -Item on Route 1: - -Bitter Berry - -Unlike in Red, Blue, and Yellow, there are two Pokémon trainers. You can grab -the Bitter Berry on the way down to fight the two trainers. - -Schoolboy Danny -Level 31 Jynx (909 EXP) -Level 31 Electrabuzz (1035 EXP) -Level 31 Magmar (1108 EXP) -You get: 992G - -Cooltrainer Quinn -Level 38 Ivysaur (1147 EXP) -Level 38 Starmie (1684 EXP) -You get: 1824G - -Now head south into Pallet Town! - -|-----------| -|Pallet Town| -|-----------| - -Talk with Professor Oak in his lab (the biggest building). He notices your Kanto -badges. He tells you that he'll give you a prize if you get all the badges, so -remember that. The houses for Red and Blue are here. You can now fly to Pallet -Town. Now, surf south onto Route 21. - -============================================================================== -============================================================================== -============================================================================== - -(c33) ------------------------------------------------------- diff --git a/docs/gold-walkthrough/section-27-cinnabar-island-and-seafoam-islands-gym.txt b/docs/gold-walkthrough/section-27-cinnabar-island-and-seafoam-islands-gym.txt deleted file mode 100644 index 53be13e0..00000000 --- a/docs/gold-walkthrough/section-27-cinnabar-island-and-seafoam-islands-gym.txt +++ /dev/null @@ -1,133 +0,0 @@ ----- 33 > Cinnabar Island and Seafoam Islands Gym ---- ------------------------------------------------------- - -|--------| -|Route 21| -|--------| - -Pokémon on Route 21: - -#072 Tentacool -#114 Tangela - -Surf south on Route 21. There are a few trainers. Nikki, an avid Seel -enthusiast, wants you to put sunblock on her. - -Swimmer Nikki -Level 28 Seel (600 EXP) -Level 28 Seel (600 EXP) -Level 28 Seel (600 EXP) -Level 28 Dewgong (1056 EXP) -You get: 560G - -There's a fisherman here. He used his Super Rod to catch you, and you didn't -even notice. Please be more aware of your surroundings. - -Fisher Arnold -Level 34 Tentacruel (1492 EXP) -You get: 1360G - -Head way left then down to... - -Swimmer Seth -Level 29 Quagsire (850 EXP) -Level 29 Octillery (1018 EXP) -Level 32 Quagsire (939 EXP) -You get: 256G - -Head down from him and you'll get to Cinnabar Island. - -|---------------| -|Cinnabar Island| -|---------------| - -Cinnabar Island is in a sad state of affairs for its former residents, who fled -the premises. The volcano has done very well for itself. So you were on Team -Volcano, you're a monster, but also, you probably won a lot of money in Team -Rocket's black market gambling scene. You can now enter the Pokémon Center and -heal your Pokémon. Talk to the two people there and they'll tell you that the -volcano erupted a year ago (two years after Red, Blue, and Yellow). - -You'll notice someone looking at what's left of it. It's Blue! He was the -Champion of the Pokémon League until Red beat him. He knows you want a challenge -but he's not in the mood for one yet. He laments, "The volcano erupts, and just -like that, a whole town disappears." I never thought of Blue as a sentimental -guy, but here we are. Blue then levitates and flies off into space, presumably -with Alakazam. Then, surf east to Route 20. - -|--------| -|Route 20| -|--------| - -Pokémon on Route 20: - -#072 Tentacool - -Head east and you'll come across one trainer. - -Swimmer Cameron -Level 34 Marill (421 EXP) -You get: 272G - -Now head on east to the Seafoam Islands. - -|-------------------| -|Seafoam Islands Gym| -|-------------------| - -Since Cinnabar Island's volcano erupted and destroyed Blaine's gym, it's moved -to the Seafoam Islands. It's now in the middle of nowhere. You should see a -small cave at the Seafoam Islands. Go inside and it's Blaine's remote, makeshift -gym. This gym doesn't even fit the entire screen. The poor guy's become a hermit -and a caveman. I just heard him grunting and working on his cave painting. - ---- -GYM LEADER BLAINE -Level 45 Magcargo (1485 EXP) -Level 45 Magmar (1609 EXP) -Level 50 Rapidash (2056 EXP) -You get: Volcano Badge, 5,000G ---- - -Blaine will have two Full Restores available to use on his Pokémon. - -Strategy VERSUS Magcargo: Magcargo has Yawn to put your Pokémon asleep, Recover -to get back half its HP in one turn, and Overheat, a pretty strong fire move -that can do significant damage to a starter Pokémon like Meganium and also cause -burn damage. Don't use Meganium, because it takes damage faster than it can deal -to Magcargo. You can use a strong normal move from your strongest Pokémon that -isn't Meganium, like HM Strength, against Magcargo. Additionally, Ho-Oh's -AncientPower is a super-effective rock move against fire types which also raises -Ho-Oh's special stats. Or you can use Lugia's Hydro Pump/Surf, as water moves -are super effective against Magcargo. Feraligatr's water type moves will be -super effective against all Pokémon in this gym. - -Strategy VERSUS Magmar: Magmar has a Confuse Ray move that can prevent your -Pokémon from attacking. Don't use Meganium, because it takes damage faster than -it can deal to Magmar. You can use a strong normal move from your strongest -Pokémon that isn't Meganium, like HM Strength, against Magcargo. Additionally, -Ho-Oh's AncientPower is a super-effective rock move against fire types which -also raises Ho-Oh's special stats. Or you can use Lugia's Hydro Pump/Surf, as -water moves are super effective against Magmar. - -Strategy VERSUS Rapidash: Rapidash has the moves Flare Blitz (which also speeds -Rapidash up) and Bounce (which is a two-turn Fly type move). Don't use Meganium, -because it takes damage faster than it can deal to Magmar. Additionally, avoid -using two-turn moves, because Bounce can result in your low-PP moves missing -entirely. You can use a strong normal move from your strongest Pokémon that -isn't Meganium, like HM Strength, against Magcargo. Additionally, Ho-Oh's -AncientPower is a super-effective rock move against fire types which also raises -Ho-Oh's special stats. Or you can use Lugia's Hydro Pump/Surf, as water moves -are super effective against Magmar. - -Blaine will give you the Volcano Badge and 5,000G. You now have 15 badges! You -can talk to the guy that comes in, but he's late. Word doesn't travel quickly -across the sea. Now exit the cave. - - -============================================================================== -============================================================================== -============================================================================== - -(c34) -------------------------------------------------- diff --git a/docs/gold-walkthrough/section-28-routes-19-20-and-viridian-city-gym.txt b/docs/gold-walkthrough/section-28-routes-19-20-and-viridian-city-gym.txt deleted file mode 100644 index 12f62b9e..00000000 --- a/docs/gold-walkthrough/section-28-routes-19-20-and-viridian-city-gym.txt +++ /dev/null @@ -1,134 +0,0 @@ ----- 34 > Routes 19-20 and Viridian City Gym ---- -------------------------------------------------- - -Video Playthrough Link (Seafoam Islands Gym, Routes 19-22, 28, & Viridian City -Gym): https://youtu.be/wgql5WCnx4g - -|--------| -|Route 20| -|--------| - -East on Route 20 and to Route 19 are optional, but if you take that route, -you'll get to fight a few more trainers. Surf down and right to face the swimmer -in the water. - -Swimmer Lori -Level 32 Starmie (1419 EXP) -Level 32 Starmie (1419 EXP) -You get: 640G - -Head right, then up, to one more trainer. - -Swimmer Nicole -Level 29 Marill (360 EXP) -Level 29 Marill (360 EXP) -Level 32 Lapras (1501 EXP) -You get: 640G - -Head right and up to Route 19! - -|--------| -|Route 19| -|--------| - -You'll find a drowning guy next to a girl. Talk to the guy for a battle. - -Swimmer Tucker -Level 30 Shellder (622 EXP) -Level 34 Cloyster (1479 EXP) -You get: 272G - -Talk to him again. Yep, seems like he is drowning. Now. talk to the girl to -battle her. She's apparently disgusted by wimpy people like her boyfriend. - -Swimmer Dawn -Level 34 Seaking (1237 EXP) -You get: 680G - -There's one more trainer. Head up, then right a little. - -Swimmer Jerome -Level 26 Seadra (862 EXP) -Level 28 Tentacool (630 EXP) -Level 30 Tentacruel (1317 EXP) -Level 28 Goldeen (666 EXP) -You get: 224G - -And that's all the trainers! You can't go further north because you'll be -blocked by those boulders south of Fuchsia City. So, fly back to Viridian City -instead. - -|-------------| -|Viridian City| -|-------------| - -First, heal at the Pokémon Center. Blue is no pushover. He's not a one-trick -pony. He has more than one type of Pokémon. He's one of only two trainers that -are tougher than Champion Lance in the whole game. There aren't any other -trainers in this gym, though, so Blue appears to have filled the whole gym with -Legos. - -Save before you talk to Blue. He doesn't think that highly of you conquering -Johto. He may have a point, as his Pokemon are much higher level than the Johto -gym leaders and even the other Kanto gym leaders. But you can prove him wrong -right here. You'll want to make sure you're hitting with high powered attacks -for the most part, but also familiar with type strengths and weaknesses. - ---- -GYM LEADER BLUE -Level 56 Pidgeot (2064 EXP) -Level 54 Alakazam (2151 EXP) -Level 56 Rhydon (2448 EXP) -Level 58 Gyarados (2659 EXP) -Level 58 Exeggutor (2634 EXP) -Level 58 Arcanine (2646 EXP) -You get: Earth Badge, 5,800G ---- - -Strategy VERSUS Pidgeot: Pidgeot is a normal and flying type which will use Air -Slash as one of its flying type moves. Pidgeot can be hit with rocks or -electrocuted. Okay, that sure sounds violent when I put it that way. Like -Arcanine, Pidgeot also has a switch-out move named Whirlwind, which can scramble -your lineup. Use your highest-powered attack from your highest level Pokémon -that is not a ground type move. - -Strategy VERSUS Alakazam: Alakazam's a psychic type, so don't use fighting moves -against it. Otherwise, you can use your highest powered attack (e.g., HM -Strength), which is likely coming from your starter evolution. - -Strategy VERSUS Rhydon: Rhydon is a rock and ground type. Use your -highest-powered attack from your highest level Pokémon that is not a normal -move. Good moves to use against Rhydon include any of Meganium's grass moves, -water moves like Hydro Pump or HM Surf from Lugia or Feraligatr, as well as ice -and fighting moves. - -Strategy VERSUS Gyarados: Gyarados is a water and flying type, which is news to -everyone, because it is a dragon type and can't fly. Use your highest-powered -attack from your highest level Pokémon that is not a ground type move. If one of -your Pokémon has TM Thunder (e.g., Ho-Oh), you can use it to great effect here. - -Strategy VERSUS Exeggutor: Two of Exeggutor's favorite moves are Psychic and -Hypnosis. Beware of Exeggutor's Hypnosis, which will put your Pokémon to sleep. -Use your highest-powered attack from your highest level Pokémon that isn't a -grass or psychic type move, like Body Slam or Strength. Fire moves work well -against this grass and psychic type. Water types like Feraligatr could run into -a bit more trouble due to the type weakness. - -Strategy VERSUS Arcanine: Arcanine is a fire type, so don't use grass or fire -type moves. Water moves like HM Surf work well, plus any high-powered move of -other types generally. - -With Blue defeated, you now have all 16 badges. Blue is completely surprised but -gives you the badge. He says he'll beat you back one day. Talk to him again and -he says you must not lose until he beats you. - -Head out of the gym and heal at the Pokémon Center. The game still isn't over! -There are no credits yet. There are still a few things you can do before you -clear Kanto. - -============================================================================== -============================================================================== -============================================================================== - -(c35) ---------------------------- diff --git a/docs/gold-walkthrough/section-29-routes-22-28.txt b/docs/gold-walkthrough/section-29-routes-22-28.txt deleted file mode 100644 index a511e3ea..00000000 --- a/docs/gold-walkthrough/section-29-routes-22-28.txt +++ /dev/null @@ -1,80 +0,0 @@ ----- 35 > Routes 22-28 ---- ---------------------------- - -Video Playthrough Link (Mount Silver & Boss Battle vs. Red): -https://www.youtube.com/watch?v=ubjgTa41WxU - -Now, fly back to Pallet Town. - -|-----------| -|Pallet Town| -|-----------| - -Again, it's time to enter Professor Oak's lab. Talk to him and he'll be -impressed and make arrangements for you to head to Mount Silver. Only the best -trainers are allowed in because of the ferocity of wild Pokémon. Now, fly back -to Viridian City, and then head west to Route 22. - -|--------| -|Route 22| -|--------| - -Pokémon on Route 22: - -#021 Spearow -#022 Rattata -#060 Poliwag (surfing) - -Items on Route 22: - -TM42 Dream Eater - -We're making our journey west now. If you cut the tree to the west of Viridian -City, you can talk to a guy that gives you TM42 Dream Eater. -There are no Pokémon trainers on this route, so just keep heading north, west, -then north. Head through that gate you went through earlier going to Indigo -Plateau, but this time, you will be heading west instead of north. Do so and -enter Route 28. - -|--------| -|Route 28| -|--------| - -Pokémon on Route 28: - -#077 Ponyta -#078 Rapidash -#114 Tangela -#215 Sneasel -#232 Donphan (Silver only) - -Items: - -TM47 Steel Wing - -As you head west towards Mount Silver, you can catch a few high-level Pokémon, -although they aren't as high level as the last few legendaries you just caught. -Consider catching a Tangela. It can learn Stun Spore which helps with Pokémon -catches, plus it can use HM02 Cut and HM05 Flash. - -There's also a Pokémon Center here that you should heal at before moving on. -It's the last one you can get to before Mount Silver. From there, you can cut -the trees east and go into a house to get TM47 Steel Wing from the lady there. - -|-------------------------| -|Final Dungeon Preparation| -|-------------------------| - -Before you enter Mount Silver, this would be a good opportunity to do any final -things before you challenge the final dungeons and boss. Make sure you've got at -least 10 Revives, 10 Hyper Potions, 5 Escape Ropes, 5 Max Repels, and 50 Ultra -Balls. You can fly to Mahogany Town's Pokemart to buy some if you need more. You -can fly back to the Pokémon Center on Route 28 just outside of Mount Silver when -you're ready. - -============================================================================== -============================================================================== -============================================================================== - -(c36) ---------------------- diff --git a/docs/gold-walkthrough/section-30-lapras.txt b/docs/gold-walkthrough/section-30-lapras.txt deleted file mode 100644 index 3ffdbf0e..00000000 --- a/docs/gold-walkthrough/section-30-lapras.txt +++ /dev/null @@ -1,72 +0,0 @@ ----- 36 > Lapras ---- ---------------------- - -|------| -|Lapras| -|------| - -We already have five of the nine special Pokémon of the game (two of five -legendaries): Sudowoodo, Snorlax, Gyarados (Shiny Red), Lugia, and Ho-oh. What -now? Thard_Verad comments, "Lapras only appears on Friday." If it's a Friday, -head to Violet City and then go south through Route 32 into Union Cave. Level 20 -Lapras becomes available on a Friday once you've gotten HM Surf. - -In Union Cave, use a Max Repel to reduce the number of wild encounters as you -navigate towards Lapras. Head along the path and past the first two trainers. -Head almost all the way through till you see the guy on the cliff still spinning -around, which is the third trainer that you spot. Down and to the left is a -lake. Surf past it and head down the ladder, then fight the guy next to the -lake. He should be very easy to beat. - -PoKeManiac Andrew -Level 24 Marowak (637 EXP) -Level 24 Marowak (637 EXP) -You get: 1440G - -With Andrew defeated, surf east and you'll fight another trainer. - -PoKeManiac Calvin -Level 26 Kangaskhan (975 EXP) -You get: 1560G - -After you beat him, he exclaims, "You demonstrated on me!" Head down the ladder, -then surf southward. Land on the right side to grab a Hyper Potion and fight a -trainer. - -Cooltrainer Nick -Level 26 Charmander (? EXP) -Level 26 Bulbasaur (355 EXP) -Level 26 Squirtle (? EXP) -You get: 1248G - -Then land on the left side. Head up to fight another spinning trainer. - -Cooltrainer Gwen -Level 26 Eevee (511 EXP) -Level 22 Vaporeon (924 EXP) -Level 22 Jolteon (928 EXP) -Level 22 Flareon (933 EXP) -You get: 1056G - -Head down the path a long way and you'll fight one more trainer. - -Cooltrainer Emma -Level 28 Poliwhirl (786 EXP) -You get: 1344G - -Then surf east from Emma and you'll encounter a Level 20 Lapras. Lapras isn't a -legendary. But treat it like it is one when you're trying to catch it by saving -before you interact with Lapras. If you knock it out on a critical hit, lose all -your Pokémon and black out, or run out of balls to throw at it, you would want -to return to your save and retry. Ultimately, you'll want to maximize your -chances by throwing many Ultra Balls at it. You'll want to do so when it is -lower on HP (yellow or red on the HP meter), and ideally when it is asleep or -paralyzed by a move like Stun Spore. You can then use Escape Rope to leave Union -Cave. - -============================================================================== -============================================================================== -============================================================================== - -(c37) ------------------------------------ diff --git a/docs/gold-walkthrough/section-31-mount-silver-and-red.txt b/docs/gold-walkthrough/section-31-mount-silver-and-red.txt deleted file mode 100644 index 85229a3d..00000000 --- a/docs/gold-walkthrough/section-31-mount-silver-and-red.txt +++ /dev/null @@ -1,153 +0,0 @@ ----- 37 > Mount Silver and Red ---- ------------------------------------ - -|------------| -|Mount Silver| -|------------| - -Pokémon in Mount Silver: - -#042 Golbat -#055 Golduck -#075 Graveler -#232 Donphan -#246 Larvitar - -Items in Mount Silver: - -Escape Rope -Full Restore -Protein - -Head west and up from there and you'll be on Mt. Silver. Bring your best team -with all the HMs (particularly Surf, Waterfall, and Flash). Bring 30 Ultra Balls -so you can catch the strongest wild Pokémon in any dungeon. Consider having a -few Max Repels ready in case you get frustrated with the numerous wild Pokémon -encounters here. I'd suggest trying to catch many of the Pokémon in here, -though, as they are high-level Pokémon and a few are great to catch. - -Larvitar would be a good Pokémon to catch because its most-evolved form, -Tyranitar, is an excellent Pokémon. Golduck may also be good if you want a -higher-level water Pokémon that can also learn HMs Surf, Strength, Waterfall, -and Whirlpool. - -When you get in, use HM Flash. Head up, then go left and drop down the ledge to -get the Escape Rope. Jump back down and go right instead of up. Go up the steps -to the cliff and grab the X Accuracy. - -Head back down the cliff, and go up the middle. This time, go right at the -intersection. Head a bit to the right, go up the steps, and then up the cliff. - -Go along the path, head on right, then up another cliff to the north. Continue -along the cliff route, and then when there's an intersection to the left and -right, take the left, drop down and get the Max Elixir. Drop down the ledges all -the way back to ground level, then again, and then go back up the cliff. - -Next, go, left, up, and right. If you go down, use Surf on the lake, and then -use Waterfall, you can go through an entrance to grab a Full Restore. If you -then return across the lake, you can go through the cave door, keep going up, -and you'll find a trainer. - -Your final lineup, if you've been leveling up your starter only, might look like -this: - -Level 82 Starter Evolution -Level 70 Ho-Oh/Lugia -Level 50 Snorlax -Level 48 Golduck -Level 40 Ho-Oh/Lugia -Level 39 Tangela - -Your final lineup, if balanced, might look like this: - -Level 60 Lugia -Level 54 Espeon -Level 53 Dragonair -Level 53 Sudowoodo -Level 53 Starter Evolution -Level 50 Gyarados - -You're about to face by far the strongest trainer in the game. Red is the final -boss. Make sure you have HMs Surf and Strength equipped on any high-level -Pokémon that can make use of them. Both HMs can be used an unlimited number of -times and even now, they likely do more damage than almost any move that a -Pokémon can learn during your game playthrough. You can go back to the Move -Deleter in Blackthorn City later in the game if you want to replace the moves -later. - -|-----------------------------| -|Red | -|Level 81 Pikachu (1422 EXP) | -|Level 73 Espeon (3081 EXP) | -|Level 77 Blastoise (3465 EXP)| -|Level 75 Snorlax (2475 EXP) | -|Level 77 Charizard (3448 EXP)| -|Level 77 Venusaur (3432 EXP) | -|You get: 7700G | -|-----------------------------| - -Red will have three Full Restores that he can use. You will likely need to use -several Hyper Potions and Revives to keep your Level 70+ Pokémon in the fight. - -Strategy VERSUS Pikachu: Pikachu is an electric type Pokémon that cannot damage -rock or ground type Pokémon. Pikachu is super effective against water type -Pokémon like Feraligatr. However, despite being the highest level Pokémon that -Red has, it is pretty frail. You can use your highest powered attack from your -starter evolution (even if it is a water move) or the highest powered attack -from Lugia or Ho-Oh, whichever Pokémon has the higher level. - -Strategy VERSUS Espeon: Espeon is a dark type that cannot be hit with ghost -moves. You can use your highest powered attack from your starter evolution or -the highest powered attack from Lugia or Ho-Oh, whichever Pokémon has the higher -level. - -Strategy VERSUS Blastoise: Blastoise is a water type Pokémon, and the most -evolved form of Squirtle. Blastoise is strong against fire type Pokémon like -Typhlosion and Ho-Oh. Blastoise is technically weak against grass type Pokémon, -but it also has Blizzard, a very strong ice type move, that is super effective -both against Meganium as well as flying types like Ho-Oh and Lugia. The best -moves to use against Blastoise are your highest-level grass moves (e.g. from -Meganium) or your highest-level electric moves that can be taught to a Pokémon -(like Ho-Oh). If you don't have those as Level 70+ Pokémon, use the highest -powered attack from your highest-level Pokémon that isn't Typhlosion or Ho-Oh. - -Strategy VERSUS Charizard: Charizard is a fire and flying type Pokémon, and the -most evolved form of Charmander. Charizard is strong against grass type Pokémon -like Meganium, so don't use Meganium. Charizard can still take damage from fire -type attacks from Typhlosion and Ho-Oh. Avoid using grass and fire type attacks -against Charizard. The best moves to use against Charizard are your -highest-level water type move (Hydro Pump or Surf) from your best Pokémon that -can use it (Feraligatr or Lugia), or your highest-level electric move that can -be taught to a Pokémon (like Ho-Oh). If you don't have those as Level 70+ -Pokémon, use the highest powered attack from your highest-level Pokémon that -isn't Meganium (e.g. Typhlosion or Ho-Oh). - -Strategy VERSUS Snorlax: Snorlax is Ash's strongest Pokémon, and takes the -longest to defeat. Snorlax is bulky and will keep using Rest to restore its -health. A rock, ground, or steel type Pokémon might take a bit more damage. Use -the highest powered attack from your starter evolution or the highest powered -attack from Lugia or Ho-Oh, whichever Pokémon has the higher level. - -Strategy VERSUS Venusaur: Venusaur is a grass and poison type Pokémon, and the -most evolved form of Bulbasaur. Venusaur is a slow and bulky Pokémon that can -use Synthesis to heal half its HP. Venusaur will be strong against water types -like Feraligatr, and will take damage well from grass attacks from Meganium. -Avoid using grass and water attacks against Venusaur. The best moves to use -against Venusaur will be your fire, flying, or psychic moves that you can have -Typhlosion, Ho-Oh, or Lugia utilize. - -Red is down. You're the best trainer in the Johto and Kanto worlds combined, at -this time. After defeating Red, he'll give you the money and then the silent -treatment. He flies away, and then the credits start rolling. After the credits -roll, save. - -The game is over, for the most part. You can still catch the Legendary Dogs at -this time if you haven't already. If you want to fight Red again, beat the Elite -Four again and then he'll be back at Mt. Silver, for experience or whatever. - -============================================================================== -============================================================================== -============================================================================== - -(c38) ------------------------------------------ diff --git a/docs/gold-walkthrough/section-32-raikou-entei-and-suicune.txt b/docs/gold-walkthrough/section-32-raikou-entei-and-suicune.txt deleted file mode 100644 index d4cd61a7..00000000 --- a/docs/gold-walkthrough/section-32-raikou-entei-and-suicune.txt +++ /dev/null @@ -1,61 +0,0 @@ ----- 38 > RAIKOU, ENTEI, AND SUICUNE ---- ------------------------------------------ - -They will only be in grasses, which means it'll never be in water, caves, trees, -Ilex Forest, or National Park. The exceptions are Routes 40 and 41. They change -route when you change route or enter/exit a city, cave, or building. Get on a -bike and start biking everywhere. Do this on every route you run across. Run -across all Johto. Check everywhere. Be a free spirit! Every time you enter a new -city or switch routes, the legendaries move one route, although they -occasionally will jump a few routes. - -There are advantages to training up a Scyther. It has False Swipe, which allows -you to knock a Pokémon's HP down to 1 HP without knocking it out. You can do so -by having your Elite Four Pokémon doing the fighting and sharing EXP at Mount -Silver. Train till Scyther is Level 39, then go to Route 42 with the Max Repel. - -Once you've had your original encounter with a roaming legendary, it becomes -possible to track where it is. After the initial encounter with a roaming -legendary Pokémon, they will show up on the map in Pokegear. Flying will reset -the roaming legendary's location, which is only really good if you are far from -the roaming legendary. - -It's better to keep switching between routes. BebeDuckie gives this advice: "You -go between the same city and route like a few steps left right left right in the -same spot, and eventually get the roaming Pokémon near you without having to fly -everywhere." - -In Johto, it is helpful to fly to Violet City, then stay on a bike and keep -switching between Routes 35, 36, and 37 so that the legendaries can quick reset -their locations, as quickly as every 10 seconds. You can get a lot of repeat -encounters quickly once you've located the legendary Pokémon before and keep -switching quickly back and forth between the same routes. - -Save every now and then when you get close to a roaming legendary. If you knock -it out, you can go back to the save file and try to catch it again. The roaming -legendaries will all run after one attack or one ball throw. This can be -frustrating, but go in with the expectation that you'll have to encounter it a -dozen or more times before you can catch it. The good news is that once you've -weakened a legendary once, it will remain at that HP the next time you encounter -it. -You could consider a Master Ball here, but keep in mind that there are three -roaming legendaries, so you can only use a Master Ball on one of them. Because -the catch rate is only about a few percent at low health, even with Ultra Balls, -this may involve many encounters or even resets. Ultimately, you'll want to -maximize your chances by throwing many Ultra Balls at it. You'll want to do so -when it is lower on HP (low yellow or ideally red on the HP meter), and ideally -when it is asleep or paralyzed although it's hard to get these moves in prior to -the roaming legendary running off. - -As long as you are persistent, you will eventually catch each roaming legendary. -If you knock it out a bunch of times or run out of Ultra Balls, you can go back -to your save and try again. After you've caught each roaming legendary, remember -to save! - - -============================================================================== -============================================================================== -============================================================================== - -(c39) -------------------------------------------- diff --git a/docs/rfcs/0003-multiplayer-session-layer.md b/docs/rfcs/0003-multiplayer-session-layer.md deleted file mode 100644 index 66af086a..00000000 --- a/docs/rfcs/0003-multiplayer-session-layer.md +++ /dev/null @@ -1,114 +0,0 @@ -# RFC 0003 — Add a reusable multiplayer session layer - -## Status - -Proposed. Engine: `Session.lua`, `Net.lua`, `LinkState.lua`, -`Tournament.lua`. Tests: `link_session.lua`. - -## Motivation - -Link play and tournaments currently own transport lifecycle details and -temporarily remove and reinsert packets in `Net.inbox` when a handshake or -battle starts. That makes packet ownership fragile and gives a future -shared-world mode no stable host/guest-aware boundary to reuse. - -The engine needs one small layer that preserves today's wire protocol while -owning received-packet order and terminal cleanup. Pokémon, battle, tournament, -save, and overworld rules remain outside that layer. - -## The decision it extends - -Extends the existing split between `Net` (backend setup, framing, and relay -controls), `Handshake`/`Protocol` (mode payloads), and the states that -interpret those payloads. It does not replace any of those components. - -## The exact API delta - -Backward-compatible and internal-only. - -### `Session.new(transport, options)` - -Wraps one successfully configured Net-compatible transport. `options.role` -is exactly `"host"` or `"guest"`; `options.kind` is a non-empty -local label such as `"link"` or `"tournament"`. Role and kind are -immutable session metadata selected locally and are never inferred from peer -packets. - -The facade forwards the narrow fields current consumers need: -`paired`, `code`, `address`, `target`, -`error`, and `closed`. -It forwards valid outbound tables unchanged through `send(message)`. - -### Receive and lifecycle methods - -- `update()` pumps the transport, validates decoded inbound values, and - appends accepted messages to a private FIFO. -- `pollOne()` removes the oldest queued message. -- `poll()` removes every queued message in order. -- `take(type)` removes the first queued message with that type without - disturbing any other message. -- `hasPending()` reports whether the FIFO is non-empty. -- `getRole()`, `getKind()`, `getStatus()`, and - `getFailure()` expose local metadata and lifecycle. -- `close()` closes the underlying transport once and is safe to repeat. - -Statuses are `connecting`, `paired`, `draining`, `closed`, -and `failed`. A transport close or failure becomes `draining` while -accepted packets remain queued. The terminal `closed`/`error` -compatibility projection appears only after that FIFO drains, so a last packet -travelling with a disconnect remains observable. - -An inbound value is structurally valid only when it is a table with a string -`type`. Invalid decoded values end the session with a protocol failure. -Unknown but structurally valid types remain queued for the owning mode; the -session does not contain a packet allowlist. - -## Authority direction - -A later `WorldSession` may compose this facade. In that mode the host -will own the world snapshot, map state, NPC state, event results, and shared -progression. A guest will bring a trainer identity plus their Pokémon party, -inventory, and other explicitly selected profile snapshot. - -Guest profile data and commands will be untrusted input. The host must validate -them and must authorize every world mutation before rebroadcasting the result. -The concrete snapshot schema, command vocabulary, conflict rules, and -persistence policy require a separate RFC and are not introduced here. - -## Compatibility and security - -No packet envelope, message name, payload shape, framing rule, relay protocol, -save schema, or engine protocol version changes. Existing valid outbound -messages encode exactly as before, and existing link and tournament screens -keep their current player-facing behavior. - -The layer does not authenticate players or encrypt traffic. Existing LAN and -relay access assumptions remain unchanged; knowledge of a join address or code -still grants the same access it grants today. Authentication, reconnect -identity, rate limits, and abuse controls remain future protocol decisions. - -## Migration note for players, mods, and peers - -**Nothing.** `LinkState` and `Tournament` adopt the facade -internally. Existing peers receive the same messages, mods gain no new API, and -players do not migrate saves or settings. - -## Parity tests - -- **ROM-free facade:** constructor validation, immutable role/kind, unchanged - send shape, FIFO ordering, typed retrieval, unknown typed packets, draining, - terminal failure latching, protected transport calls, and decoded-value - rejection. -- **Existing modes:** source guards prohibit direct inbox mutation; headless - module loads and the complete engine tier cover both migrated states. -- **ROM-backed link play:** run the existing link driver when generated ROM - data is available; the normal quick suite remains the required baseline. - -## Deprecation etiquette and non-goals - -Nothing deprecated. This RFC adds an internal facade and removes no transport -method. - -It does not add shared-world packets, co-op screens, a remote actor, save -transfer, server persistence, matchmaking, reconnect, or a protocol-version -bump. Those changes require the world-specific layer and its own review. diff --git a/docs/rfcs/0003-playthrough-storage.md b/docs/rfcs/0003-playthrough-storage.md deleted file mode 100644 index e9e4b5c2..00000000 --- a/docs/rfcs/0003-playthrough-storage.md +++ /dev/null @@ -1,147 +0,0 @@ -# RFC 0003 — Playthrough-scoped mod storage - -## Status - -Proposed. Engine: `SaveData.lua`, `SaveSerializer.lua`, `Storage.lua`, -`Loader.lua`. Tests: `playthrough_identity.lua`, `storage.lua`, the existing -save-slot and mod-save suites. - -## Motivation - -`mod.save` intentionally lives inside the normal progress record. That is the -right home for quest state, but not for independent tool data such as replay -captures, checkpoint histories, or recovery records: writing it would require a -normal Pokémon SAVE, and storing copies of progress beneath `save.modData` would -recursively embed the save that contains them. - -Mods also cannot safely infer which launcher slot or portable filesystem backs -the active playthrough. Direct filesystem access would expose private paths and -make isolation dependent on engine implementation details. - -## The decision it extends - -Extends the per-mod persistence contract documented in `docs/modding.md` and the -wiki's Save Model. `mod.save` and `mod.options` keep their existing behavior. - -## The exact API delta - -Backward-compatible, additive-only. `Loader:_api` binds a new `mod.storage` -facade to the calling mod id. Mods receive logical keys and either decoded table -values or exact opaque byte strings, never filesystem handles or physical paths. - -### Lazy opaque playthrough identity - -`SaveData.ensurePlaythroughId(save[, fs]) -> id | nil` allocates an opaque -32-hex-character identity without consuming gameplay RNG. It is called only when -`mod.storage` or `mod.checkpoints` first needs a scope; New Game, ordinary SAVE, -and ordinary load remain byte-compatible when no caller uses either API. - -The id is stored in `save.meta.playthroughId` after allocation. Until the next -ordinary SAVE writes it into progress, a mapping in `options.lua` keeps legacy -saves stable by game version and active launcher slot (or the legacy flat-save -scope). A newly created playthrough never adopts the previous playthrough's -mapping for that slot. - -`SaveData.persistenceFs([fs])` is engine-only routing used by the storage -implementation. It follows the same standard/portable backend as progress and -honors injected test filesystems; it is not exposed on the mod object. - -### `mod.storage:context(game)` - -Returns: - -```lua -{ engineVersion = "0.9.0", gameVersion = "red", playthroughId = "..." } -``` - -or `nil, code, message`. `engineVersion` is warning-grade compatibility metadata; -the context intentionally omits launcher slot ids and paths. - -### `mod.storage:write(game, key, value)` - -Accepts a data-only table and returns `true`, or -`false, code, message`. Keys are nonempty slash-separated segments containing -letters, digits, underscore, or dash. Empty segments, leading/trailing slash, -`.`/`..`, and other characters are rejected. - -The engine encodes deterministically, stages and decodes a `.tmp` witness, -preserves the previous valid generation, writes and decodes the main record, -then rolls the verified bytes to `.bak`. A failed stage or replacement leaves a -verified prior generation readable. - -### `mod.storage:read(game, key)` - -Returns a freshly decoded table, or `nil, code, message`. It tries main, staged, -then backup data. A valid staged/backup value is returned and promoted -best-effort; corrupt bytes are never executed. - -### `mod.storage:writeBytes(game, key, bytes)` - -Accepts a Lua string containing opaque bytes and returns `true`, or -`false, code, message`. Empty strings are valid. Payloads are limited to 512 -MiB per key. The engine writes the supplied bytes exactly as received, without -decoding, compression, checksums, or an engine-defined envelope. The consuming -mod owns semantic validation of its format. - -Byte records use private `.bin`, `.bin.tmp`, and `.bin.bak` witnesses. A staged -and replacement write is read back and compared byte-for-byte before it is -committed. A failed write leaves the previous verified generation readable. -Byte storage never passes its payload to the Lua serializer, loader, or module -resolver. - -Table and byte records share one logical key namespace and a key has one type. -Writing one type over the other returns `type_conflict`; callers must delete the -key before changing its type. `mod.storage:selected(game)` exposes the same -`readBytes` and `writeBytes` operations for the selected playthrough facade. - -### `mod.storage:list(game[, prefix])` - -Returns sorted logical keys beneath a valid prefix, an exact key when the prefix -names one, or `nil, code, message`. Physical witness filenames are hidden. - -### `mod.storage:delete(game, key)` - -Deletes only that key's main, backup, and staged witnesses. Returns `true`, or -`false, code, message`. - -### Scope and errors - -Physical records are scoped as: - -`persistence root / mod_storage / game version / playthrough id / mod id` - -Stable error codes are `not_in_playthrough`, `storage_unavailable`, -`invalid_key`, `encode_failed`, `invalid_bytes`, `size_limit`, `type_conflict`, -`type_mismatch`, `write_failed`, `verify_failed`, and `not_found`. Ordinary -data and I/O failures are return values, not callback-terminating errors. - -The restricted serializer's recursive writer runs outside LuaJIT traces. A -1,000-process GC stress regression found compiled recursion could intermittently -drop a newly inserted nested identity entry and produce undecodable bytes; save -encoding is infrequent and I/O-bound, so interpreter execution is the safe -boundary. - -## Migration note for existing mods - -**Nothing.** No API is removed, no manifest field changes, and no storage path or -playthrough id is created unless a mod invokes `mod.storage` or -`mod.checkpoints`. Existing save bytes remain unchanged on the no-caller path. -Existing files outside the scoped storage contract are not imported; a caller -must rebuild them through `writeBytes`. - -## Parity tests - -- **No-mod:** New Game plus ordinary save/load creates no identity or storage - file; the existing save-slot and mod-save suites remain green. -- **Engine identity:** lazy allocation, save/load preservation, stable legacy - mapping, fresh-playthrough replacement, and version/slot isolation. -- **Public Mod API:** two real API-2 entry chunks prove data-only roundtrip, - opaque byte roundtrip including NUL bytes, no execution, size/type rejection, - deterministic listing, key rejection, mod/game/playthrough isolation, - corrupt-main recovery, failure retention, selected-playthrough access, exact - delete, and no-mod no-write. - -## Deprecation etiquette - -Nothing deprecated. The additions are one bound public facade and engine-private -persistence/identity helpers. diff --git a/docs/rfcs/0004-runtime-checkpoints.md b/docs/rfcs/0004-runtime-checkpoints.md deleted file mode 100644 index 1df98749..00000000 --- a/docs/rfcs/0004-runtime-checkpoints.md +++ /dev/null @@ -1,138 +0,0 @@ -# RFC 0004 — Stable runtime checkpoints for mods - -## Status - -Proposed. Engine: `Checkpoint.lua`, `Game.lua`, `OverworldController.lua`, -`Loader.lua`. Tests: `checkpoints.lua`, existing world and engine suites. - -## Motivation - -Mods can observe world events and request semantic actions, but no supported API -can capture canonical progress at a proven-safe runtime boundary or reconstruct -the overworld without replaying map-entry scripts. Reaching into the state stack, -controller, ScriptRunner, or save restore internals would bind distributable mods -to private objects and can duplicate story side effects. - -The engine is the only component that can authoritatively decide whether the -runtime is settled and rebuild its controller objects. A generic checkpoint seam -lets tools store data-only records while keeping those responsibilities private. - -## The decision it extends - -Extends the public world/tool surfaces in `docs/modding.md`. It does not change -`mod.world`, normal CONTINUE, vanilla SAVE, or save lifecycle hooks/events. - -## The exact API delta - -Backward-compatible, additive-only. `Loader:_api` binds `mod.checkpoints`; mods -never receive `Game`, StateStack, controller, coroutine, renderer, or filesystem -internals inside a checkpoint. - -### `mod.checkpoints:inspect(game)` - -Returns a capability record. Stable overworld control returns: - -```lua -{ canCapture = true, canRestore = true, kind = "overworld" } -``` - -A refusal returns the same booleans as `false` plus `kind`, `reason`, and a -player-readable `message`. Format-1 supports only an overworld whose controller -is topmost, player movement has settled on a tile, and no transition, foreground -or parallel ScriptRunner, queued script, scripted move, engagement, emote, -teleport, field animation, or similar partial controller mutation is active. - -Refusal reasons are `not_in_playthrough`, `not_overworld`, `screen_busy`, -`transition_busy`, `script_busy`, `animation_busy`, and `movement_busy`. -Identity allocation is lazy and happens only after an active topmost overworld -has been established. - -### `mod.checkpoints:capture(game)` - -Returns a detached data-only format-1 checkpoint, or -`nil, code, message`: - -```lua -{ - format = 1, - kind = "overworld", - identity = { - engineVersion = "...", gameVersion = "red", playthroughId = "...", - }, - save = { -- canonical dynamic progress, excluding global options }, - runtime = { overworld = { - map = "PALLET_TOWN", x = 5, y = 6, - facing = "down", surfing = false, - } }, -} -``` - -`engineVersion` is metadata for caller compatibility warnings; the engine does -not reject patch/minor mismatches on restore. Capture deep-copies through the restricted serializer before and after -`OverworldController:captureSave` synchronizes live map, tile, facing, and surf -state. It excludes `save.options`, functions, userdata, threads, metatables as -behavior, controller instances, and static content registries. Failure code -`capture_failed` covers non-data progress and synchronization errors. - -### `mod.checkpoints:restore(game, checkpoint)` - -Returns `true`, or `false, code, message`. Before mutation it requires the current -runtime to be capturable and validates a detached copy of the complete record: -format, kind, internal identity consistency, current game/playthrough identity, -map availability, integral in-bounds tile, facing, surfing, and synchronized save -position. - -Validation codes are `invalid_checkpoint`, `invalid_content`, `unsupported_format`, -`unsupported_runtime_kind`, `wrong_game`, `wrong_playthrough`, `invalid_map`, and -`invalid_position`, in addition to the capability refusal reasons. - -The canonical save validator runs against the detached record. Unlike ordinary -CONTINUE, a checkpoint never accepts a quarantine, remap, reclaim, clamp, or -repair: any such content change returns `invalid_content` before live mutation. - -The engine captures an in-memory rollback checkpoint, preserves current global -options, then reconstructs semantic overworld state through -`Game:restoreCheckpointSave`. Checkpoint entry suppresses normal map exit/entry -events, `onEnter` scripts, forced-movement/current checks, and last-map rewrites; -it does not emit normal `save.loading`/`save.loaded` lifecycle events. After -reconstruction, the engine recaptures and byte-compares normalized data. A failed -apply rolls back and returns `restore_failed`; failure of that rollback returns -`rollback_failed`. Only after a successful comparison does the engine emit -`checkpoint.restored` with `{ game = game, kind = "overworld" }`. Validation -failure, failed apply, and successful rollback emit nothing. - -Durable recovery remains a caller responsibility: in-memory rollback handles a -runtime exception, not process termination. - -## Runtime boundary and future kinds - -This RFC's original Level A contract intentionally rejects battles, menus, -transitions, animations, and suspended/queued scripts. RFC 0005 subsequently -adds a separately inventoried `battle` kind with deterministic RNG and -differential reconstruction tests; it does not broaden script or arbitrary-frame -support implied here. - -## Migration note for existing mods - -**Nothing required.** No existing hook, save, controller, or world action changes -when `mod.checkpoints` is unused. The reconstruction path is called only by a -successful public restore after validation. Mods whose runtime caches derive from -rewound `game.save` or `mod.save` state may optionally subscribe to -`checkpoint.restored` and rebuild from their own public state. - -## Parity tests - -- **No-mod:** the complete ROM-free engine suite and existing world behavior stay - green; ordinary New Game/save/load allocates no checkpoint identity. -- **Public Mod API:** a real API-2 entry chunk proves stable inspection and every - unsafe refusal, detached data-only capture, exact map/tile/facing/surf sync, - `A -> mutate B -> restore A -> recapture A2` equality across representative - progress, settings preservation, compatibility rejection without mutation, - map-side-effect suppression, injected reconstruction rollback, mod-owned - metadata and `mod.save` rewind, independent `mod.storage`/options preservation, - and success-only runtime-cache reconciliation through `checkpoint.restored`. - -## Deprecation etiquette - -Nothing deprecated. This adds one public facade and a checkpoint-only semantic -reconstruction route. diff --git a/docs/rfcs/0005-battle-runtime-checkpoints.md b/docs/rfcs/0005-battle-runtime-checkpoints.md deleted file mode 100644 index d3459584..00000000 --- a/docs/rfcs/0005-battle-runtime-checkpoints.md +++ /dev/null @@ -1,158 +0,0 @@ -# RFC 0005 — Persistent battle safe-point checkpoints - -## Status - -Proposed. Extends RFC 0004. Engine: `BattleCheckpoint.lua`, `Checkpoint.lua`, -`Game.lua`, `BattleState.lua`, and `OverworldController.lua`. Tests: -`battle_checkpoint_*.lua`, `checkpoints.lua`, and the existing no-mod suites. - -## Motivation - -RFC 0004 lets a tool capture and reconstruct settled overworld progress without -private engine access. A battle is a different runtime: its queue can hold Lua -functions and UI factories, its controller contains renderer objects and live -references, completion is currently an `onFinish` closure, and scripted battles -resume a suspended `ScriptRunner` coroutine. Copying the controller would create -a record that is neither data-only nor process-independent. - -The engine can instead expose a narrow semantic safe point. Ordinary encounters -use fixed engine-owned completion descriptors. A scripted story encounter may -also participate when its active command row and remaining row-list are -detached data, its NPC can be rebound by stable object id, and its completion is -one of the engine-declared semantic forms. The suspended coroutine itself is -never captured. - -## API delta - -No new facade is added. The existing additive `mod.checkpoints` API gains a -second format-1 runtime kind. - -### Capability - -`mod.checkpoints:inspect(game)` returns this only when a supported single-player -wild or trainer battle is settled at the player command menu: - -```lua -{ canCapture = true, canRestore = true, kind = "battle" } -``` - -The action/message queue, waits, UI, animations, HP/status presentation, and -faint processing must be settled. The player must actually control the menu. -The battle must carry an engine-owned semantic continuation descriptor. An -ordinary encounter requires an idle overworld. A scripted story encounter may -have exactly its originating foreground runner suspended at the battle command; -queued/parallel scripts and scripted movement remain unsafe. - -Additional refusal codes are `battle_phase_busy`, `battle_origin_unsupported`, -`battle_variant_unsupported`, and `link_battle_unsupported`. Link, Safari, -ghost, old-man/demo, fishing, opaque callback continuations, non-data-only -scripts, and unsupported concurrent script work remain rejected. - -### Capture - -A battle checkpoint remains detached and data-only: - -```lua -{ - format = 1, - kind = "battle", - identity = { engineVersion = "...", gameVersion = "red", - playthroughId = "..." }, - save = { -- canonical dynamic progress, excluding global options }, - runtime = { - overworld = { map = "ROUTE_1", x = 7, y = 8, - facing = "left", surfing = false }, - battle = { -- normalized semantic model and continuation }, - }, - rng = { love = "..." }, -} -``` - -The model carries player/enemy roster indices, dynamic enemy Pokémon, turn and -escape state, HP/PP/status/stages/volatiles, participants, level-up tracking, -trainer AI state, battle ruleset identity, side/field extension data, and -normalized pointer relationships such as multi-turn move slots and Mimic -restoration entries. Definitions, sprites, canvases, queues, callbacks, and -controller objects are reconstructed or excluded. - -Callback-bearing battle extension tokens fail with `battle_extension_unsafe`; -invalid live reference relationships fail with `battle_state_invalid`. Nothing -is silently stripped. - -New overworld checkpoints also carry the LÖVE gameplay RNG state. Legacy -format-1 overworld checkpoints without `rng` remain loadable and leave the -current stream untouched. - -### Restore - -Battle restore validates the detached save, map, content references, ruleset, -roster indices, move references, continuation identity, and RNG before live -mutation. The engine then: - -1. reconstructs the saved overworld return point without entry side effects; -2. creates a fresh `BattleState` from current content registries; -3. applies the normalized battle model and rebuilds object-reference relations; -4. binds an engine-owned wild/trainer completion continuation; -5. installs the battle directly at the settled menu without replaying its intro; -6. restores the RNG after reconstruction has finished; and -7. recaptures and compares the complete checkpoint; and -8. emits `checkpoint.restored` with `{ game = game, kind = "battle" }` after the - comparison succeeds. - -The pre-operation checkpoint is the transaction rollback. A failed post-install -RNG restore is covered: both battle runtime and RNG are reconstructed back to -their original values. Validation failure, failed reconstruction, and successful -rollback emit no checkpoint lifecycle event. - -## Continuation decision - -Ordinary random wild battles resume through `OverworldState:afterBattle`. -Ordinary trainer battles use a descriptor containing map id, stable NPC id, -trainer class/party, and optional header event; a win reapplies the same defeated -flag, event, reward, and `afterBattle` path. Reconstructed overworld input and -NPC freeze state are normalized instead of reviving the old closure. - -For `start_battle`, `rival_battle`, and `static_battle`, the runner records the -detached row list and current command program counter plus stable source/NPC -identity where present. On restore, battle completion starts a fresh runner at -that command with a one-use semantic battle result. Replaying the current -command (rather than skipping to the next row) preserves wrapper behavior such -as rival-party consequences, static-object removal, `lastCheck`, and deferred -`afterBattle` evolution ordering. The reconstructed overworld starts with -normalized input/NPC freeze state, so an engine-marked `release_npc` callback -needs no closure revival. - -Arbitrary `onDone` callbacks, function-bearing rows, unknown commands, missing -NPC identities, old-man/demo flows, and concurrent scripts fail closed. This is -not a general ScriptRunner snapshot: no coroutine, Lua stack, local variable, -function, or runtime object enters the checkpoint. - -## Migration note - -**Existing mods require no changes.** The facade and format number are unchanged; -the new kind, RNG field, and success-only lifecycle event are additive. -Overworld-only callers may continue to filter `capability.kind`. Mods with derived -runtime caches may rebuild them from restored public state when the event fires. -No-mod behavior is unchanged when checkpoints are unused. - -## Verification - -- settled/unsafe boundary and every variant refusal; -- data-only wild and trainer capture, including callback-bearing extension - rejection; -- process-independent controller and continuation reconstruction; -- exact differential recapture for wild and trainer states; -- HP, PP, status/stages/volatiles, AI layer, participants, enemy roster, - multi-turn move references, and Mimic restore pointers; -- exact damage, critical, accuracy, random AI, escape, next encounter, and next - raw RNG result after reload; -- corrupt content/continuation rejection before mutation; -- injected post-install failure with full runtime and RNG rollback; -- mod-added Pokémon metadata and `mod.save` rewind while independent - `mod.storage` and options remain current; -- exactly one post-verification `checkpoint.restored` event and none on failure; -- legacy overworld checkpoint compatibility; -- complete ROM-free engine and public mod-API suites. -- scripted trainer and static/wild story continuation, including wrapper-row - replay, cold reconstruction, malformed row rejection, and opaque callback - refusal. diff --git a/docs/rfcs/0006-platform-lifecycle-hooks.md b/docs/rfcs/0006-platform-lifecycle-hooks.md deleted file mode 100644 index 9763fed1..00000000 --- a/docs/rfcs/0006-platform-lifecycle-hooks.md +++ /dev/null @@ -1,107 +0,0 @@ -# RFC 0006 — Generic process-lifecycle hooks for platform launcher integrations - -## Status - -Proposed. Engine: `PlatformHooks.lua` (new), `main.lua`, `Manifest.lua`, -`Loader.lua`. Tests: `tests/modkit/cases/platform_lifecycle_hooks.lua`, -`tests/mod_loader_tests.lua`, `tests/mod_manifest_tests.lua`. - -## Motivation - -A platform-specific launcher wrapper -- a native shell that embeds this -engine and owns its own UI around the game window (a mobile app shell, -say, presenting its own settings/import/save screens and only handing -control to the LÖVE window once play starts) needs three things no -current hook covers: - -1. Pause the simulation while its own UI is on top of the game window. -2. Live-reload options it wrote from outside any Lua UI. -3. Veto `main.lua`'s "closing the window returns to the Lua launcher" - behavior when the platform shell owns that job itself -- without this, - a shell that re-fronts its own launcher UI on quit gets looped straight - back into `HostShell.restart()`'s in-process reboot instead. - -Implementing this by hand-patching `main.lua`'s `love.update`/`love.quit` -directly ties every such integration to editing the one file every other -engine change also touches, guaranteeing merge conflicts for any second -platform integration (or any unrelated engine PR landing around the same -time). No existing hook covers "should the per-frame simulation step run" -or "should closing the window return to the Lua launcher." - -## The decision it extends - -No prior D-number. Extends the hook-contract section of `docs/modding.md` -alongside `input.step`, `render.hud`, `screen.render_visible`, etc. - -## The exact API delta - -Backward-compatible, additive-only. - -### `core.update` - -New hook, `(game, dt) -> nil` through the public wrapper signature -`(next, game, dt)`, called once per frame from `love.update` via -`src/core/PlatformHooks.lua`'s `PlatformHooks.update(game, dt)`. Vanilla -behavior (used when no mod claims the hook) is `game:update(dt)`, -unconditionally -- identical to `love.update`'s behavior before this hook -existed. A subscriber may skip calling `next(game, dt)` to pause the -simulation for that frame, or do additional per-frame work before/after -calling it regardless of whether it calls `next`. - -### `core.quit_to_launcher` - -New hook, `() -> boolean` through the public wrapper signature `(next)`, -called once from `love.quit()` via -`PlatformHooks.quitToLauncher(vanilla)`. `vanilla` is the pre-existing -non-platform-specific decision (`Game and not Importer and not -quitToLauncher and not scripted and not launchedIntoGame`). A subscriber -may return `false` outright to veto returning to the Lua launcher (without -ever calling `next`, so the vanilla condition is never evaluated), or call -`next()` and return its result to pass the vanilla decision through -unchanged. - -Neither hook is guarded by `Runtime.wantsHook` -- both fire unconditionally -every call, matching the existing `input.step` precedent -(`src/core/Game.lua`), since `Hooks:call` already fast-paths to a bare -`vanilla(...)` call when no mod has wrapped the name. - -### `Manifest.force_enable_env` - -New optional manifest field, a bare env-var name. `Loader:load` re-enables -a mod carrying this field whenever that variable is set to `"1"`, -regardless of a saved disable in `options.mods`. This exists for exactly -the mod class this RFC is for: a platform-bridge mod that ships only with -one build and cannot function disabled there, but must still behave like -every other mod (a manifest opt-in, not an engine special case) on every -build that doesn't set its variable. - -## Migration note for existing mods - -**Nothing.** With no subscriber, `love.update` still calls `Game:update(dt)` -unconditionally every frame and `love.quit()`'s restart-to-launcher -decision is exactly the pre-existing condition -- bit-identical to today's -behavior on every platform where no mod wraps either hook. A manifest with -no `force_enable_env` field behaves exactly as before. - -## Parity tests - -- **No-mod:** `core.update`'s vanilla runs exactly once per call with the - hook chain empty; `core.quit_to_launcher`'s vanilla return value passes - through unchanged. Both hooks are picked up automatically by the - catalog-driven no-mod gate (`tests/engine/gate_hooks.lua`, which scans - for `Runtime.call("...")` call sites), so neither needs a dedicated - no-mod test file. -- **Mod-API:** `tests/modkit/cases/platform_lifecycle_hooks.lua` proves, - through a fixture mod loaded via the public loader (not the engine's - internals), that a subscriber can skip the vanilla update call (pause), - run extra per-frame polling regardless of pause state, and veto the - quit-to-launcher decision without the vanilla condition ever running. -- `tests/mod_loader_tests.lua` and `tests/mod_manifest_tests.lua` cover - `force_enable_env`: a matching env var re-enables a mod saved as - disabled, and an unset one leaves the saved disable alone. - -## Deprecation etiquette - -Nothing deprecated. These are two additive hooks and one additive manifest -field; `main.lua`'s only footprint is one `require` and two call sites -into `src/core/PlatformHooks.lua`. diff --git a/docs/rfcs/0006-title-playthrough-checkpoint-resume.md b/docs/rfcs/0006-title-playthrough-checkpoint-resume.md deleted file mode 100644 index df26819a..00000000 --- a/docs/rfcs/0006-title-playthrough-checkpoint-resume.md +++ /dev/null @@ -1,92 +0,0 @@ -# RFC 0006 — Selected title playthrough storage and checkpoint resume - -## Status - -Proposed. Engine: `SaveData.lua`, `Storage.lua`, `Checkpoint.lua`, and -`Loader.lua`. Tests: `title_playthrough_context.lua`, existing storage, -checkpoint, title, save-slot, and no-mod parity suites. - -## Motivation - -A tool checkpoint may be the first durable record of a new playthrough. The -engine intentionally keeps normal Pokémon SAVE independent: before the first -normal write, identity is retained by the engine-owned selected-slot mapping, -while title starts with a fresh New Game skeleton. A durable checkpoint tool may -explicitly create one ordinary progress anchor after its first checkpoint has -committed; later tool writes must remain independent. Calling ordinary active -`mod.storage` there would allocate/adopt an identity, and live -`mod.checkpoints:restore` correctly refuses title because it has no gameplay -rollback state. Generic public capabilities are required; a tool must not use -private storage paths, slot ids, or simulate the player's SAVE menu flow. - -## Additive public API - -### `mod.storage:selected(game)` - -Available only while the engine is in a title session. Returns an opaque bound -facade or `nil, code, message`: - -```lua -local selected = mod.storage:selected(game) -local context = selected:context() -local history = selected:read("history/index") -``` - -The facade exposes `context()`, `read(key)`, `write(key, value)`, -`list(prefix)`, and `delete(key)`. It is bound internally to the launcher- -selected existing game-version/playthrough and the calling mod id. It neither -accepts an arbitrary playthrough id nor reveals a slot id, filesystem path, or -another mod namespace. Resolution is read-only; no selected mapping means -`no_selected_playthrough`, and opening a title browser never mints an identity. -Its detached context can include only `normalSavedAt` from a matching ordinary -save, so title tools can apply their own resume policy without receiving the -canonical normal-save record. - -### `mod.checkpoints:ensureNormalSave(game, checkpoint)` - -Available only at a live checkpoint-safe boundary. After a tool has durably -committed the supplied current checkpoint, it may request an ordinary progress -anchor for a playthrough that has never had one. The engine validates the -checkpoint, proves it exactly matches a fresh capture of the live runtime, and -uses the normal atomic save path including `save.write` lifecycle/veto hooks. - -The operation is idempotent. It returns `true, "already_exists"` without writing -when matching normal progress already exists, so later checkpoints never move -the vanilla CONTINUE target. A stale/non-current checkpoint, unsafe runtime, -write veto/failure, or failed readback returns a structured failure. A tool -should call it only after its own checkpoint and index are durable and must not -report that first checkpoint as successful if the required anchor fails. - -### `mod.checkpoints:resume(game, checkpoint)` - -Available only from title. It validates format, data-only structure, selected -game/playthrough identity, canonical save/content, overworld/battle runtime, and -RNG exactly as `restore` does. It then reconstructs semantic overworld or a -supported battle continuation, preserves current options, and differentially -recaptures before committing. On success it emits `checkpoint.restored` once. - -Title has no live runtime rollback. A reconstruction or verification failure -therefore rebuilds a clean title session from the pre-operation title save and -RNG; it emits no success event and never rewrites normal progress. Validation -failure leaves the existing title session untouched. Stable errors include -`not_at_title`, `no_selected_playthrough`, normal checkpoint validation codes, -`resume_failed`, and `title_recovery_failed`. - -## Isolation and migration - -Explicit NEW GAME retains its existing fresh-identity rule. It does not reuse a -previous selected mapping and cannot see old tool history. Existing mods change -nothing: no identity, storage, title reconstruction, or event is created unless -the new methods are called. `mod.storage` remains independent durable data and -does not rewind with a checkpoint; canonical `game.save` / `mod.save` does. - -## Verification - -The public SDK test starts a fresh playthrough, stores tool history, creates and -readback-verifies exactly one normal anchor, proves subsequent calls do not -rewrite it, simulates title/restart, reads the selected binding without -allocating title identity, resumes an overworld checkpoint, preserves options, -differentially recaptures, and confirms a later explicit NEW GAME receives -another identity. A separate two-process disk test proves cold-start routing and -reconstruction. Existing no-mod, storage, checkpoint, battle, and title suites -prove additive parity. diff --git a/docs/rfcs/0007-battle-menu-auxiliary-actions.md b/docs/rfcs/0007-battle-menu-auxiliary-actions.md deleted file mode 100644 index 422a7420..00000000 --- a/docs/rfcs/0007-battle-menu-auxiliary-actions.md +++ /dev/null @@ -1,46 +0,0 @@ -# RFC 0007: Battle menu auxiliary actions - -## Status - -Proposed. - -## Problem - -Tool mods can inspect/capture a persistent checkpoint only at a settled -ordinary wild/trainer player-decision boundary. Before this proposal, that -boundary had no public semantic input/action seam: `BattleState` consumed the -command loop directly. A mod could reach it only through private battle/input -internals, which would be unsafe and incompatible with controller/touch input. - -## Contract - -`mod.hooks:wrap("battle.menu_auxiliary", callback)` is called only when START -is pressed at the existing checkpoint-safe player-decision boundary. The -callback signature is: - -```lua -function callback(next, game, context) - -- context is { kind = "wild" } or { kind = "trainer" } - -- return true after claiming START, otherwise return next(game, context) -end -``` - -The context is data-only. No live battle controller, input object, serializer, -or restoration primitive is exposed. A `true` result consumes START for that -fixed step without selecting a battle command. With no installed handler, -START is inert exactly as before. Hook priorities and error isolation are the -existing generic wrapper semantics: a throwing handler is skipped and cannot -advance battle state. - -The engine reuses the same internal safety predicate as battle checkpoint -capture. Link, Safari, ghost/demo, unsupported origins, scripts, queues, -animations, messages, forced replacement/locked actions, and unsettled HP or -status presentation never invoke the hook. - -## Compatibility and verification - -The call is additive and no-op with no handler. ROM-free engine tests prove -wild/trainer delivery, cursor/turn preservation, and unsafe-phase refusal; -the mod-SDK fixture proves a loaded mod can consume the semantic action using -only its public hook facade. `gate_hooks` automatically includes the new call -site in no-mod parity coverage. diff --git a/docs/rfcs/0007-per-category-game-speed.md b/docs/rfcs/0007-per-category-game-speed.md deleted file mode 100644 index eb34b3a4..00000000 --- a/docs/rfcs/0007-per-category-game-speed.md +++ /dev/null @@ -1,212 +0,0 @@ -# RFC 0007 — Per-category GAME SPEED and the `core.logic_speed` hook - -## Status - -Proposed. Engine: `GameSpeed.lua`, `Game.lua`, `BattleState.lua`, -`OptionsMenu.lua`, `SaveData.lua`, `LauncherSettings.lua`. Tests: -`tests/engine/game_speed_categories_test.lua`, -`tests/engine/gate_hooks.lua` (structural, automatic), `tests/run_tests.lua` -(OptionsMenu row walk), `tests/mod_ui_tests.lua` (row id/order). - -## Motivation - -`GameSpeed` (`src/core/GameSpeed.lua`) is a single fast-forward multiplier -applied uniformly to the whole logic clock in `Game:logicSpeed()` / -`Game:update()` -- overworld walking, menu navigation and battle turns all -scale together. A player who wants 4X battles (grinding, a long gym fight) -but 1X overworld (so a scripted cutscene or NPC dialogue doesn't blur past) -has no way to get both; the one GAME SPEED row is a single ladder that -applies everywhere at once. - -This needs to be an engine change, not a mod: there is no per-frame seam a -mod can use to swap the multiplier mid-step, and no public event granular -enough to say "which category is active" (`screen.pushed`/`screen.popped` -and `battle.started`/`battle.ended` are the closest and are not enough -- -see Decisions below). The engine's own speed resolution has to become -category-aware. - -A category-aware speed resolution is also the general seam a -platform-launcher integration or automation tool needs to read or override -the effective multiplier for a given frame without caring which category -produced it -- this RFC's `core.logic_speed` hook is written for that case -alongside the player-facing Options rows. - -## The decision it extends - -No prior D-number. Extends `GameSpeed.lua`'s multiplier ladder (unchanged) -with per-category resolution. - -## The exact API delta - -Backward-compatible except for one save-data field rename, which ships with -an automatic migration (see below) -- nothing in the public mod API (hooks, -events, registries, `mod.*`) is renamed or removed. - -### `save.options`: `speed` -> `speedOverworld` / `speedBattle` / `speedMenu` - -`GameSpeed.CATEGORIES = { "overworld", "battle", "menu" }` is the new list -of categories, and `GameSpeed.optionKey(category)` maps a category to its -`save.options` field name (`"overworld"` -> `"speedOverworld"`, etc.). -`GameSpeed.LEVELS`, `.DEFAULT`, `.levelLabel`, `.clamp` and `.cycle` are -unchanged -- the ladder and its behavior are exactly what they were, just -applied three times instead of once. - -`SaveData.defaultOptions()` drops `speed = 1` and adds `speedOverworld = 1`, -`speedBattle = 1`, `speedMenu = 1`. `SaveData.mergeOptions()` migrates: a -loaded options table that still has `speed` and none of the three new -fields seeds all three from it, so an existing player's fast-forward -preference carries over instead of two of the three categories silently -resetting to 1X. `speed` is dropped on the way out (not carried forward), -so a re-save never re-triggers the migration. - -### `Game.speedCategoryInStack(stack)` - -New static helper, `(stack) -> "battle" | "overworld" | "menu"`. Walks the -whole state stack top-down -- the same idiom `Game.wideBattleInStack` and -`Game.fillScaleInStack` already use -- looking for `state.isBattle` (new -marker, `BattleState.isBattle = true`, covering every battle: wild, -trainer, link, safari, the old-man demo) or `state.isOverworld` (existing -marker, `OverworldController`'s `OverworldState.isOverworld = true`). The -first match wins; a state with neither marker (a menu, a text box, a -naming screen, a cutscene) is transparent to the walk and falls through to -whatever is under it. Nothing in the stack matching either falls back to -`"menu"`. - -### `Game:logicSpeed()` / `Game:_resolveLogicSpeed()` - -`Game:_resolveLogicSpeed()` is new: it resolves `Game.speedCategoryInStack` -against the live stack, maps the category to its `save.options` key via -`GameSpeed.optionKey`, and returns `GameSpeed.clamp` of that option (or -`GameSpeed.DEFAULT`). This is the exact category-resolution logic the new -hook wraps. - -`Game:logicSpeed()` keeps its existing early returns -- link play forces -`1`, a run-argument speed override wins over the saved option -- unchanged, -and in the same order, before ever calling the hook. Only once neither -applies does it call the `core.logic_speed` hook. - -### `Game:_cycleSpeed(dir)` - -The keyboard hotkey and the gamepad shoulders/triggers that used to cycle -the single `speed` option now cycle whichever category -`Game.speedCategoryInStack` says is active: pressing the hotkey during a -battle speeds up just the battle, on the overworld just the walk, in a menu -just the menu. This is the natural per-category answer for a control that -used to have one option to reach and now has three -- see Decisions below -for why this reading was chosen over, say, always cycling `overworld`. - -### `core.logic_speed` - -New hook, `(game) -> number` through the public wrapper signature -`(next, game)`, called once per `Game:logicSpeed()` (i.e. once per frame). -Vanilla behavior (used when no mod claims the hook) is -`Game:_resolveLogicSpeed()` -- exactly the category resolution above, -nothing else. A subscriber may call `next(game)` and return its result to -pass the vanilla multiplier through, or return a different number outright -to override it for that frame (e.g. a bot mod forcing `1` during one route -segment regardless of what category or option is active). - -This intentionally sits *after* the link and speed-override checks in -`Game:logicSpeed()`, not around them: link play staying locked to 1X "no -matter what either player set this to" is exactly the invariant that would -break if a mod's hook could override it, and the run-argument override -exists so a bot/screenshot run's speed does not depend on a mod any more -than on the player's saved option. Both stay unconditional early returns a -mod never sees. - -Not guarded by `Runtime.wantsHook`: `Hooks:call` already fast-paths to a -bare `vanilla(...)` call when no mod has wrapped the name, and this hook -fires every frame regardless. - -## Decisions on the issue's open questions - -**1. Overlays on top of another category's state (a party menu, a choice -box, a naming screen opened mid-battle or mid-overworld).** Resolved by -making the category a property of stack *position*, not of the overlay's -own type: an overlay with no `isBattle`/`isOverworld` marker is transparent -to `Game.speedCategoryInStack`'s walk and inherits whatever is under it. A -party swap opened mid-battle reads as `"battle"`; a bag opened while -walking reads as `"overworld"`. This was chosen over giving every UI state -its own fixed category (which would make a fast-forwarded battle visibly -stutter back to 1X every time its party menu opens) because it matches -what the player is actually doing moment to moment, and it reuses a -pattern the codebase already leans on for exactly this "menus opened over -X should behave like X" class of problem (`Game.fillScaleInStack`, -`Game.wideBattleInStack`). - -**2. Cutscenes/scripts.** No fourth category. A scripted sequence runs -through the owning state's own machinery -- the overworld's script runner -or a battle's message queue -- rather than pushing a state of its own, so -it is already covered by decision 1: it inherits whatever category the -state driving it resolves to. A cutscene state that genuinely has nothing -under it (a pre-game intro) falls to `"menu"`, the default for anything -that is not battle or overworld gameplay -- consistent with those being -pre-game presentation, not something a player is likely to want scaled -differently from menu navigation. - -**3. Category granularity (splitting "menu" further).** Deferred. Start -with the three named here; `GameSpeed.CATEGORIES` and `GameSpeed.optionKey` -are written so adding a fourth later (a Pokédex/Bag category, say) is one -entry plus one new `save.options` field, not a resolution-logic rewrite. -No current request motivates it. - -**4. The GAME SPEED hotkey/shoulder buttons, once "the" speed is three -things.** `Game:_cycleSpeed` now cycles whichever category is currently -active (`Game.speedCategoryInStack`), rather than, say, always cycling -`overworld` or requiring a modifier key to pick a category. A single -physical control that means "speed up whatever I'm looking at right now" -is the reading that needs no new UI and matches what a player pressing it -mid-battle almost certainly wants. - -## Migration note for existing mods - -**Nothing**, for the mod API surface: `content.X:register/override/get`, -`events:on`, `hooks:wrap`, `mod.log`, `mod:read`, manifest v1 fields are -untouched, and `GameSpeed.LEVELS`/`.DEFAULT`/`.levelLabel`/`.clamp`/`.cycle` -keep their exact signatures and behavior. - -**One save-data field**, for anything that read `save.options.speed` -directly (not a formal registry/hook surface, but worth naming): it is -superseded by `speedOverworld`/`speedBattle`/`speedMenu`, migrated -automatically on load (see above) so a save from before this RFC keeps its -player's chosen speed. A mod reading `save.options.speed` after this change -sees `nil` (the key is dropped on migration, not kept as a stale alias) and -should read the per-category fields, or hook `core.logic_speed` to observe -the resolved multiplier directly regardless of which category produced it. - -## Parity tests - -- **No-mod:** `core.logic_speed` needs no dedicated no-mod test file -- - `tests/engine/gate_hooks.lua` walks the live hook catalog (which scans - `src` for `Runtime.call("...")` call sites), so the new - `Runtime.call("core.logic_speed", ...)` site is picked up and gated - automatically: vanilla runs exactly once with an empty hook chain, an - unsubscribed-but-live bus passes values and multiple returns through - unchanged, and `Runtime.wantsHook` reads `false`. -- **Mod-API:** `tests/engine/game_speed_categories_test.lua` exercises the - hook through the public API (`Hooks.new()` + `bus:wrap("core.logic_speed", - ...)` + `Runtime.call`, the same idiom other hooks' tests use) -- a - subscriber can read the vanilla category resolution via `next(game)` and - can override it outright -- plus direct coverage of - `Game.speedCategoryInStack` (battle-on-top, overworld-on-top, an overlay - inheriting each, an empty/unmatched stack falling to `"menu"`) and - `Game:logicSpeed()`'s precedence (link forces 1X over all three - categories and over a hook override; the run-argument override wins over - the category resolution). -- `tests/run_tests.lua`'s OptionsMenu walk exercises the three new rows - (OVERWORLD SPEED / BATTLE SPEED / MENU SPEED) cycling and wrapping - independently, in place of the old single GAME SPEED row. -- `tests/mod_ui_tests.lua`'s row-id/order check and hardcoded row-index - activations (MODS, CONTROLS) are updated for the two extra rows. -- A link-play driver should set all three per-category speeds high before - asserting `game:logicSpeed()` reads `1` during a real link session, - proving the lock wins over every category at once, not just whichever - one happens to be active. - -## Deprecation etiquette - -Nothing deprecated in the mod-facing hook/event/registry catalog -- this -adds one hook, additive. The `save.options.speed` field is superseded with -an automatic migration rather than a deprecation notice, since it was never -a registered mod-API surface (no schema entry, no registry) -- the same -treatment any other `save.options` field would get if it needed reshaping. diff --git a/docs/rfcs/0008-device-power-info.md b/docs/rfcs/0008-device-power-info.md deleted file mode 100644 index cafe668f..00000000 --- a/docs/rfcs/0008-device-power-info.md +++ /dev/null @@ -1,49 +0,0 @@ -# RFC 0008 — Read-only device power information for sandboxed mods - -## Status - -Proposed. Engine: `Loader.lua`, `Sandbox.lua`. Test: -`tests/modkit/cases/device_power_info.lua`. - -## Motivation - -A handheld UI mod can show the player's battery state and warn before power -loss. The sandbox correctly removes `love.system` because that module also -launches URLs and exposes other host operations, but it leaves no scoped way -to read the harmless power values that LÖVE already provides. - -## The decision it extends - -Extends the mod sandbox in `src/mods/Sandbox.lua`: blocked host modules stay -blocked while legitimate operations receive narrow engine-owned facades. - -## The exact API delta - -Add `mod.device:powerInfo() -> state, percent`. - -The engine calls `love.system.getPowerInfo()` outside the mod sandbox and -returns only its first two values. `state` is one of LÖVE's standard power -states. `percent` is `0` through `100` or `nil`. When the platform has no -power-information backend, the result is `"unknown", nil`. - -No permission grants access to `love.system`; URL launching, clipboard access, -OS identification, and the module table itself remain unavailable. - -## Migration note for existing mods - -Mods that used `love.system.getPowerInfo()` replace that call with -`mod.device:powerInfo()`. No other mod changes. - -## Parity tests - -- **No mod:** loading no mods does not call the platform power backend. -- **Mod API:** a fixture mod loaded through the public loader receives state - and percentage through `mod.device`, while the existing sandbox suite keeps - proving that direct `love.system` access is refused. -- **Unavailable backend:** the public facade returns `"unknown", nil` rather - than inventing battery data or failing mod load. - -## Deprecation etiquette - -Nothing deprecated. The facade is additive; the sandbox's `love.system` block -remains in force. diff --git a/docs/rfcs/0009-step-bridge-permission.md b/docs/rfcs/0009-step-bridge-permission.md deleted file mode 100644 index b0462c31..00000000 --- a/docs/rfcs/0009-step-bridge-permission.md +++ /dev/null @@ -1,68 +0,0 @@ -# RFC 0009 — Permission-gated step bridge for sandboxed mods - -## Status - -Proposed. Engine: `Steps.lua` (new), `Loader.lua`, `Manifest.lua`, -`Sandbox.lua`. Test: `tests/modkit/cases/steps_bridge.lua`. Issue: #1186. - -## Motivation - -The iOS and Android builds count the player's real-world steps natively -(#452, #489), exposed to Lua as `love.system.syncHealthSteps()` and -delivered as `steps_pending.json` in the save-directory root. The sandbox -correctly blocks both — `love.system` also launches URLs, and the file API -names paths — but that leaves the bridge with no consumer: the mod that -step counting was built for (Pokéwalker, steps→EXP) can no longer be -written. - -## The decision it extends - -Extends the mod sandbox in `src/mods/Sandbox.lua` (blocked host modules -stay blocked; legitimate operations receive narrow engine-owned facades) -and the permission model `network` established: a `manifest.json` -permission the player sees in the mod manager that genuinely gates a -capability. - -## The exact API delta - -A new manifest permission token, `steps`, and a `mod.steps` facade: - -- `mod.steps:available() -> boolean` — whether this build carries the - native bridge. Answers `false` without the permission, so a probe stays - quiet. -- `mod.steps:sync() -> boolean` — asks the platform to refresh its count - (async; the OS consent sheet still appears on first use, exactly as - before the sandbox). `false` when there is no bridge. -- `mod.steps:poll() -> { steps = n, from = iso?, to = iso? } | nil` — the - next delivery for this mod, engine-consumed from the pending file. Each - permissioned mod receives its own copy of a delivery. - -Without the permission, `sync` and `poll` raise an error naming the -missing permission, the way the network gate does. The engine owns the -pending file: mods never learn its name or location, and only the three -contract fields travel. No new event, hook, or registry names. - -## Migration note for existing mods - -Mods that called `love.system.syncHealthSteps()` and read -`steps_pending.json` themselves add `"steps"` to `permissions` and switch -to `mod.steps:sync()` / `mod.steps:poll()`. No other mod changes. - -## Parity tests - -- **No mod:** with nothing installed the bridge is never called and a - pending file on disk is left untouched. -- **Mod API:** a fixture mod with the permission syncs and receives a - delivery through the public loader; two permissioned mods both receive - the same walk; a second poll returns nil. -- **No permission:** `available()` is false and the acting calls name the - missing permission; the sandbox suite keeps proving direct - `love.system` access is refused. -- **Malformed delivery:** a bad or empty pending file is dropped whole - rather than crashing a poll (the native anchor only advances on a - successful sync, so nothing is lost). - -## Deprecation etiquette - -Nothing deprecated. The facade is additive; the sandbox's `love.system` -and `love.filesystem` blocks remain in force. diff --git a/docs/rfcs/0010-trainer-battle-party-scope.md b/docs/rfcs/0010-trainer-battle-party-scope.md deleted file mode 100644 index 9d6ffda4..00000000 --- a/docs/rfcs/0010-trainer-battle-party-scope.md +++ /dev/null @@ -1,92 +0,0 @@ -# RFC 0010: Deferred trainer preparation and battle-local party scope - -## Status - -Proposed. - -## Motivation - -Challenge and tournament mods sometimes need a player to choose an eligible -subset of the save party before a trainer battle. The current public surface -can replace the opponent through `trainer.party` and observe -`world.trainer_engaged`, but it cannot pause the engagement before battle -construction or keep unselected save-party members out of initial send, -switch, replacement, exhaustion, experience, and party-menu traversal. - -Temporarily rewriting `game.save.party` is not a safe substitute: it changes -authoritative save state, composes poorly with checkpoints and other mods, and -can strand excluded Pokémon if a callback or process fails. - -## Decision and plan extended - -This implements **D-AT-001: battle-local Gym registration without save-party -mutation**, the consuming design decision tracked as capability `AT-SP-001` in -the Adaptive Trainers implementation plan. The plan file is -[`docs/superpowers/plans/2026-08-14-adaptive-trainers.md`](https://github.com/MaxTomahawk/gen1recomp-adaptive-trainers/blob/main/docs/superpowers/plans/2026-08-14-adaptive-trainers.md), -Task 5. The engine delta also extends the additive, guarded public-hook -decision used by RFC 0007 and the screen facade documented in -`docs/modding.md`; it deliberately contains none of the consuming mod's Gym -or party-size policy. - -## Exact API delta - -Add the guarded hook: - -```lua -mod.hooks:wrap("trainer.before_battle", function(next, game, context, continue) - -- context = { trainerClass, partyIndex, mapId, npcId } - -- Return true only when the battle has been deferred. - -- continue({ cancel = true }) returns without constructing a battle. - -- Call continue() for the full save party, or: - -- continue({ playerPartyIndices = { 2, 4, 5 } }) -end) -``` - -The hook runs after the trainer's challenge text and immediately before the -trainer battle is constructed. A mod may push a registered screen with -`mod.ui.push`, return `true`, and retain `continue` for its confirm/cancel -callback. `continue` is one-shot and returns `false` after the first call. -Returning anything other than `true` without calling it continues immediately -with vanilla scope. With no subscriber, no context or continuation is built. - -`playerPartyIndices` is an ordered, one-based list into `game.save.party`. -Valid unique indices create `battle.playerParty` as a battle-local view of the -same Pokémon records; the save party itself is never reordered or replaced. -Malformed or empty scopes degrade to the full party. The view governs initial -send, all battle party menus and targets, voluntary and forced replacement, -exhaustion/blackout checks, participant and EXP.ALL traversal, party counts, -and party-ball presentation. Checkpoints preserve the index list and rebuild -the same view before restoring battlers. - -`{ cancel = true }` ends a deferred encounter through its normal completion -callback without constructing a battle or writing trainer-defeated state. A -cancelled sight encounter is suppressed while the player remains on the same -cell, preventing immediate reacquisition; moving or directly talking permits a -new challenge. Cancellation is also one-shot; if supplied alongside a party -index list, cancellation wins. - -The API sets no maximum, chooses no members, identifies no boss, and contains -no scaling or challenge policy. - -## Migration and compatibility - -Existing mods change nothing. `BattleState.newTrainer(game, class, index)` -keeps its current behavior; the optional fourth argument is additive. Existing -battle checkpoints without a party scope restore against the full save party. -Wild, Safari, link, and no-mod battles are unchanged. - -## Verification - -- The catalog-driven hook gate proves empty-chain parity and the guarded hot - path proves no-mod engagement starts exactly once without allocation. -- A sandboxed fixture mod defers through its public hook facade, inspects the - data-only context, and resumes once with ordered indices. -- Engine tests cover initial send, party menus, replacement/exhaustion, - EXP traversal, invalid-scope fallback, and save-party identity. -- Battle-checkpoint tests prove scoped capture/restore and old-checkpoint - compatibility. - -## Deprecation etiquette - -Nothing is deprecated. The hook and optional constructor argument are -additive. diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 38c1bdd7..a6253993 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -364,21 +364,17 @@ local function grayImage(img) return getImage(meta.path) or img end --- The blacked-out battle screen. HandlePlayerBlackOut (core.asm:1151) runs --- SET_PAL_BATTLE_BLACK, i.e. SetPal_BattleBlack sends PalPacket_Black -- --- PAL_BLACK in all four slots of BlkPacket_Battle (engine/gfx/palettes.asm: --- 22-25). The mon pics are drawn OVER the zone pass with their palette --- already baked in, so darkening them means re-baking through PAL_BLACK the --- way fadeImage re-bakes through a BGP permutation (#292). Reads the palette --- out of the active pack, exactly like sgbBattlePals, so the zone pass and --- the pics can never disagree. trueColor art has no DMG shades to remap. +-- SET_PAL_BATTLE_BLACK re-bake of a pic (engine/battle/core.asm:1151, +-- engine/gfx/palettes.asm:22-25); resolved like sgbBattlePals' blackout (#292). local function blackImage(data, img) local meta = imageMeta[img] if not meta or meta.trueColor then return img end local PaletteFX = require("src.render.PaletteFX") local pack = PaletteFX.pack(data) - local colors = pack and pack.palettes and pack.palettes.BLACK - if not colors then return img end + local pals = pack and pack.palettes + if not (pals and pals.BLACK) then return img end + local colors = PaletteFX.usesYellowCgb() and pals.BLACK + or PaletteFX.pal(data, "BLACK") or pals.BLACK local name = PaletteFX.usesGbcPack() and "redpp:BLACK" or "BLACK" return getImage(meta.path, { name = name, colors = colors }) or img end @@ -2608,6 +2604,9 @@ function BattleState:residualFor(b, opp) if self.result then return end if self.player ~= b and self.enemy ~= b then return end if b.mon.hp <= 0 or opp.mon.hp <= 0 then return end + -- engine/battle/core.asm:435-473 + if b.residualDone then return end + b.residualDone = true local msgs = Status.residual(b, opp, self) for _, m in ipairs(msgs) do self:sayNext(prefixEnemy(m, b)) end if b.leechSeeded and b.mon.hp > 0 then @@ -2664,7 +2663,8 @@ function BattleState:endOfTurn() for _, pair in ipairs({ { self.player, self.enemy, "player", enemyAlive }, { self.enemy, self.player, "enemy", playerAlive } }) do local b, opp, side, oppAlive = pair[1], pair[2], pair[3], pair[4] - if sweep and b.mon.hp > 0 and oppAlive then + if sweep and not b.residualDone and b.mon.hp > 0 and oppAlive then + b.residualDone = true local msgs = Status.residual(b, opp, self) for _, m in ipairs(msgs) do self:sayNext(prefixEnemy(m, b)) end if #msgs > 0 then self:drainNext() end -- poison/burn/seed HP moved @@ -2678,6 +2678,7 @@ function BattleState:endOfTurn() -- the Haze move-forfeit only covers the turn Haze was used; if the -- cured mon had already moved, drop the flag before next turn b.skipMove = nil + b.residualDone = nil -- CheckNumAttacksLeft (core.asm:683-697): a trapping counter that -- hit 0 this turn releases its bit only now, at the end of the turn if b.trappingTurns and b.trappingTurns <= 0 then @@ -3892,9 +3893,9 @@ function BattleState:onFaint(battler) battler.fainted = true local Sound = require("src.core.Sound") if battler.isPlayer then - -- RemoveFaintedPlayerMon (core.asm:1040-1042): the player mon's - -- faint plays its ordinary species cry -- no Faint_Fall - self.faintCry = Sound.playCry(self.data, battler.mon.species) + -- RemoveFaintedPlayerMon: the species cry (core.asm:1040-1042), + -- PikachuCry4 on Yellow (engine/battle/core.asm:1058) + self.faintCry = Sound.playCry(self.data, battler.mon.species, 4) elseif self.kind ~= "wild" then -- FaintEnemyPokemon (core.asm:732-771): the enemy faint plays no -- species cry; trainer battles get SFX_FAINT_FALL, then SFX_FAINT_THUD @@ -5296,24 +5297,28 @@ function BattleState:sgbBattlePals() local pack = PaletteFX.pack(self.data) local pals = pack and pack.palettes if not pals then return nil end - -- HandlePlayerBlackOut (core.asm:1151) runs SET_PAL_BATTLE_BLACK: - -- SetPal_BattleBlack sends PalPacket_Black, PAL_BLACK in all four slots of - -- BlkPacket_Battle (engine/gfx/palettes.asm:22-25), so every zone of the - -- battle screen -- both HP bars and both mon regions -- goes dark behind - -- the blackout text. picImage re-bakes the pics through the same palette, - -- since those draw over the zone pass rather than through it (#292). + -- HandlePlayerBlackOut (core.asm:1151) runs SET_PAL_BATTLE_BLACK + -- (engine/gfx/palettes.asm:22-25); picImage re-bakes through it (#292). if self.blackedOut and pals.BLACK then - local b = pals.BLACK + local b = PaletteFX.usesYellowCgb() and pals.BLACK + or PaletteFX.pal(self.data, "BLACK") or pals.BLACK return { [0] = b, [1] = b, [2] = b, [3] = b } end + -- home/palettes.asm:38 local function bar(b) - if not b then return pals.GREENBAR end + if not b then + return PaletteFX.pal(self.data, "GREENBAR") or pals.GREENBAR + end local hp = b.shownHP or b.mon.hp - return pals[PaletteFX.barPalName(hp, b.mon.stats.hp, b.shownPx)] + return PaletteFX.pal(self.data, + PaletteFX.barPalName(hp, b.mon.stats.hp, b.shownPx)) or pals.GREENBAR end local function mon(b, placeholder) - if placeholder or not b then return pals.MEWMON or pals.GREENBAR end + if placeholder or not b then + if PaletteFX.usesYellowCgb() then return pals.MEWMON or pals.GREENBAR end + return PaletteFX.pal(self.data, "MEWMON") or pals.MEWMON or pals.GREENBAR + end return PaletteFX.monPal(self.data, b.mon.species) or pals.MEWMON end local out = { @@ -5322,23 +5327,6 @@ function BattleState:sgbBattlePals() [2] = mon(self.player, self.showPlayerBack or self.safari or self.demo), [3] = mon(self.enemy, self.showEnemyTrainer), } - -- OG RED: the Game Boy Color drew the whole battle from one BG palette -- - -- white paper, black ink -- so every zone shares the same background and - -- outline; only the two mid shades differ per element (green HP bar, red - -- mon pic). The bar/base zones otherwise carry the SGB off-white - -- (255,239,255) as color 0 while the mon zones (monPal -> GBC_BG) carry a - -- true white, which is what drew a white box around each pic on the pink - -- field. Snap every zone's color 0/3 to the global GBC white/black; the - -- mid shades (and the green bar the user prefers) stay untouched. - -- Boot-ROM OG only (Red/Blue): snap zone paper to the global GBC white/black. - -- OG YELLOW keeps each CGBBasePalettes endpoint (already near-white / near-black). - if PaletteFX.mode == "ogred" and not require("src.core.GameVersion").isYellow() then - local white, black = PaletteFX.GBC_BG[1], PaletteFX.GBC_BG[4] - for i = 0, 3 do - local c = out[i] - out[i] = { white, c[2], c[3], black } - end - end return out end @@ -5432,23 +5420,29 @@ function BattleState:drawZonePass(src, sx, sy) love.graphics.setShader() end --- colors for one anim-layer OAM sprite at screen pixel (px, py): the --- zone palette under that pixel's 8x8 attribute cell (the SGB colors --- the composited picture per cell, so AnimPlayer samples once per cell --- the tile overlaps), through the OBJ palette the routine ran with --- (SetAnimationPalette: wAnimPalette = $f0 on SGB, rOBP1 = $6c, --- ambient rOBP0 = $e4) +-- SetAnimationPalette (engine/battle/animations.asm:551): wAnimPalette = $f0 +-- on SGB, $e4 otherwise; rOBP1 = $6c either way local OBJ_SHADES = { f0 = { 0, 3, 3 }, -- color 1 -> shade 0, colors 2/3 -> shade 3 - f0x = { 3, 0, 3 }, -- $f0 xor %00111100 = $cc: the Master/Ultra ball - -- toss flicker (DoBallTossSpecialEffects) + f0x = { 3, 0, 3 }, -- $f0 xor %00111100 = $cc (DoBallTossSpecialEffects, + -- engine/battle/animations.asm:685) e4 = { 1, 2, 3 }, -- identity + e4x = { 2, 1, 3 }, -- $e4 xor %00111100 = $d8 obp1 = { 3, 2, 1 }, -- $6c } function BattleState:animSpriteColors(s, px, py) - local P = self:zoneColorsAt(px or (s.x - 8 + 4), py or (s.y - 16 + 4)) + local PaletteFX = require("src.render.PaletteFX") + local key = s.obp or "f0" + local P + -- engine/battle/animations.asm:551 (.notSGB) + if PaletteFX.usesSpriteObp() then + P = PaletteFX.ogObj() + if key == "f0" then key = "e4" elseif key == "f0x" then key = "e4x" end + else + P = self:zoneColorsAt(px or (s.x - 8 + 4), py or (s.y - 16 + 4)) + end if not P then return nil end - local m = OBJ_SHADES[s.obp or "f0"] or OBJ_SHADES.f0 + local m = OBJ_SHADES[key] or OBJ_SHADES.f0 local function c(shade) local col = P[shade + 1] return { col[1] / 255, col[2] / 255, col[3] / 255 } diff --git a/src/battle/gen2/AnimRunner.lua b/src/battle/gen2/AnimRunner.lua index 3c92bb8b..87bed2fc 100644 --- a/src/battle/gen2/AnimRunner.lua +++ b/src/battle/gen2/AnimRunner.lua @@ -279,7 +279,7 @@ end C.dropsub = function(self) local side = self.env.battleTurn == 0 and "player" or "enemy" - self.picOverride[side] = nil + self.picOverride[side] = false end -- BattleAnimCmd_MinimizeOpp / GetMinimizePic: despite the name it shrinks the diff --git a/src/battle/gen2/Battle.lua b/src/battle/gen2/Battle.lua index 6602517d..4f5014ad 100644 --- a/src/battle/gen2/Battle.lua +++ b/src/battle/gen2/Battle.lua @@ -1517,10 +1517,10 @@ function Battle:useMove(attacker, defender, moveId) -- read and cleared by the very next move aimed at it, and while it is up the -- accuracy roll does not happen at all. local locked = self:consumeLockOn(defender) - -- SUBSTATUS_X_ACCURACY (an X ACCURACY) grants the same roll bypass, - -- CheckHit's `.XAccuracy` arm, but is not consumed: it lasts until the - -- switch drops the volatile. + -- CheckHit's .XAccuracy and EFFECT_ALWAYS_HIT arms + -- (effect_commands.asm:1572-1579). local sureHit = locked or self:volatile(attacker).xAccuracy == true + or def.effect == "EFFECT_ALWAYS_HIT" -- .LockOn runs ahead of .FlyDigMoves and returns a HIT unless the target is -- flying and the move is one of the three (effect_commands.asm:1563-1567, @@ -1723,9 +1723,14 @@ function Battle:useMove(attacker, defender, moveId) text = ("Hit %d time(s)!"):format(landed) }) end - -- Recoil is a quarter of what was dealt; drain heals half of it. Dream - -- Eater's sleep requirement is checkhit's, not this block's, so by the - -- time a drain is paid out the target is known to have been asleep. + -- move_effects/pay_day.asm:13 + if def.effect == "EFFECT_PAY_DAY" and dealt > 0 then + self.payDay = (self.payDay or 0) + 2 * (attacker.level or 1) + self:emit({ kind = "message", + text = Strings("Coins scattered\neverywhere!") }) + end + + -- Recoil is a quarter of what was dealt; drain heals half of it. if def.effect == "EFFECT_RECOIL_HIT" and dealt > 0 then local recoil = Effects.recoilDamage(dealt) attacker.hp = math.max(0, (attacker.hp or 0) - recoil) @@ -3012,6 +3017,14 @@ function Battle:resolveFaints() text = (self.trainer.name or "TRAINER") .. " was defeated!" }) self:awardPrizeMoney() end + -- CheckPayDay, on the win arm only (engine/battle/core.asm:7971-7976, + -- :8014-8042). + local coins = Prize.payDay(self.save, self.payDay, self.amuletCoin) + if coins then + self:emit({ kind = "money", text = Prize.payDayMessage(coins, + self.save.player and self.save.player.name) }) + end + self.payDay = nil self:endBattle("win") return true end diff --git a/src/battle/gen2/Prize.lua b/src/battle/gen2/Prize.lua index 4e492bd9..54fef6ae 100644 --- a/src/battle/gen2/Prize.lua +++ b/src/battle/gen2/Prize.lua @@ -66,6 +66,8 @@ local SENT_SOME = Strings.source("%s got %s%d for winning! Sent some to MOM!") -- see because BankOfMom only ever writes MOM_SAVING_SOME_MONEY_F. local SENT_HALF = Strings.source("Sent half to MOM!") local SENT_ALL = Strings.source("Sent all to MOM!") +-- BattleText_PlayerPickedUpPayDayMoney (data/text/battle.asm:3-8). +local PICKED_UP = Strings.source("%s picked up %s%d!") -- charmap.asm: the currency glyph, the same one Chrome.money floats in front -- of a six-digit field. @@ -220,4 +222,17 @@ function Prize.message(award, playerName) return Strings(GOT_MONEY, name, YEN, total) end +-- CheckPayDay (engine/battle/core.asm:8014-8042): the Amulet Coin doubles the +-- accumulated total once, and the wallet is written directly, no Mom split. +function Prize.payDay(save, amount, amuletCoin) + if not (save and save.player) or (amount or 0) <= 0 then return nil end + if amuletCoin then amount = amount * 2 end + setPlayerMoney(save, addToAccount(playerMoney(save), amount)) + return amount +end + +function Prize.payDayMessage(amount, playerName) + return Strings(PICKED_UP, playerName or "PLAYER", YEN, amount or 0) +end + return Prize diff --git a/src/core/Game2.lua b/src/core/Game2.lua index 8533b903..6365cb37 100644 --- a/src/core/Game2.lua +++ b/src/core/Game2.lua @@ -730,7 +730,7 @@ function Game2:usePartyItem(itemId) local row = ItemEffects.RESTORE_PP[itemId] or {} if row.each or mon.isEgg then self.stack:pop() - finish(ItemEffects.usePpItem(itemId, mon), mon) + finish(ItemEffects.usePpItem(itemId, mon, nil, self.data), mon) return end Screens.push(self, "Gen2MoveDeleter", { @@ -740,7 +740,7 @@ function Game2:usePartyItem(itemId) onChoose = function(slot) self.stack:pop() -- the move list self.stack:pop() -- the party list - finish(ItemEffects.usePpItem(itemId, mon, slot), mon) + finish(ItemEffects.usePpItem(itemId, mon, slot, self.data), mon) end, }) end, @@ -888,6 +888,12 @@ function Game2:load() self.data.items = loadGenerated("data/generated/items.lua") or {} self.data.moves = loadGenerated("data/generated/moves.lua") or {} self.data.type_chart = loadGenerated("data/generated/type_chart.lua") or {} + -- data/types/type_matchups.asm:112-116: the rows after the `db -2` marker + -- apply by default; Foresight is what cuts the table short at it. + local chart = self.data.type_chart + for _, row in ipairs(chart.foresightMatchups or {}) do + chart.matchups[#chart.matchups + 1] = row + end -- The `held_items` registry's merge target: ItemAttributes' last two columns -- as their own table, so a mod can give an item a held behaviour without -- owning the whole item record. Built BEFORE mods:load so the registry @@ -1949,6 +1955,7 @@ function Game2:applyOptions() touchControls = options.touchControls, haptics = options.haptics, }) + require("src.core.VideoMode").applyOptions(options) local GBCFX = require("src.render.GBCFX") if GBCFX.applyOptions(options) and self.save then -- applyOptions returns true when it had to clear an unsupported level. diff --git a/src/core/Music.lua b/src/core/Music.lua index 2c5f7b1f..01ffc756 100644 --- a/src/core/Music.lua +++ b/src/core/Music.lua @@ -130,12 +130,23 @@ local SPECIAL = { evolution = "Music_SafariZone", } +-- SpecialMapMusic (pokegold home/audio.asm:397) +local SPECIAL_GEN2 = { + surf = "Music_Surf", + bike = "Music_Bicycle", + evolution = "Music_Evolution", +} + -- the label a scene role resolves to; call sites keep their own presence -- guard on the resolved label function Music.special(data, key) local special = data and data.audio and data.audio.special local label = special and special[key] if label ~= nil then return label end + if data and data.audio and data.audio.generation == 2 + and SPECIAL_GEN2[key] ~= nil then + return SPECIAL_GEN2[key] + end return SPECIAL[key] end @@ -192,6 +203,12 @@ local function startSong(data, def, wantLoop) return nil, nil, nil, "no chip program and no file" end +-- Music_MeetRival_Ch{1,2,3}_AlternateStart (audio/alternate_tempo.asm:7) +local RIVAL_ALT_START = { + redblue = { 0x71a2, 0x721d, 0x72b5 }, + yellow = { 0x7075, 0x70f0, 0x7188 }, +} + -- the single choke point every song choice passes through, so one hook -- covers map themes, battle themes, jingles and scene music local function selectSong(song, ctx) @@ -219,8 +236,17 @@ function Music.play(data, song, loop, ctx) local start = ctx and ctx.start or nil -- a hook may silence the cue outright, or swap in a label the dedupe -- below has to compare against - if not song or (song == state.current and tempo == state.tempo - and start == state.start) then return end + if not song then return end + if song == state.current and tempo == state.tempo + and start == state.start then + -- ..(home/audio.asm ln 65) + if state.fade and state.fade.pending then + state.fade = nil + applyVolume(state.source) + applyVolume(state.loopSource) + end + return + end local def = songDef(data, song) if not def or state.failed[song] then return end @@ -247,10 +273,12 @@ function Music.play(data, song, loop, ctx) and def.bank == 2 and def.address == 17050 then local started = {} for key, value in pairs(def) do started[key] = value end + local alt = require("src.core.GameVersion").isYellow() + and RIVAL_ALT_START.yellow or RIVAL_ALT_START.redblue started.startChannels = { - { number = 1, address = 0x71a2 }, - { number = 2, address = 0x721d }, - { number = 3, address = 0x72b5 }, + { number = 1, address = alt[1] }, + { number = 2, address = alt[2] }, + { number = 3, address = alt[3] }, } def = started end @@ -340,9 +368,12 @@ end Music.MAP_FADE = 10 --- the song a map should currently play, honoring the bike/surf overrides +-- the song a map should currently play, honoring the bike/surf overrides; +-- Gen 2 has no outdoor gate (pokegold home/audio.asm:437) local function effectiveMapSong(data, song) - if not song or not outdoorSongs(data)[song] then return song end + if not song then return song end + local gen2 = data and data.audio and data.audio.generation == 2 + if not gen2 and not outdoorSongs(data)[song] then return song end if state.onBike then local bike = Music.special(data, "bike") if bike and songDef(data, bike) then return bike end diff --git a/src/core/gen2/ItemEffects.lua b/src/core/gen2/ItemEffects.lua index c57010dc..241eaaed 100644 --- a/src/core/gen2/ItemEffects.lua +++ b/src/core/gen2/ItemEffects.lua @@ -58,6 +58,12 @@ ItemEffects.RESTORE_PP = { MAX_ELIXER = { amount = "all", each = true }, } +-- engine/items/item_effects.asm:1245 StatExpItemPointerOffsets. +ItemEffects.VITAMIN = { + HP_UP = "hp", PROTEIN = "attack", IRON = "defense", + CARBOS = "speed", CALCIUM = "special", +} + -- EnergypowderEnergyRootCommon / HealPowderEffect: the herb items charge -- happiness for tasting bitter on top of their heal. local BITTER = { @@ -70,6 +76,9 @@ ItemEffects.TEXT_NO_EFFECT = "It won't have any\neffect." ItemEffects.TEXT_CANT_USE_ON_EGG = "That can't be used\non an EGG." -- _PPRestoredText (data/text/common_3.asm). ItemEffects.TEXT_PP_RESTORED = "PP was restored." +-- _PPIsMaxedOutText / _PPsIncreasedText (data/text/common_3.asm). +ItemEffects.TEXT_PP_MAXED = "%s's PP\nis maxed out." +ItemEffects.TEXT_PP_INCREASED = "%s's PP\nincreased." -- PrintPartyMenuActionText's .MenuActionTexts (engine/pokemon/party_menu.asm), -- keyed by the class GetItemHealingAction resolves. Each is the two rows the @@ -235,6 +244,33 @@ local function rareCandy(mon, data) } end +-- engine/items/item_effects.asm:1216 StatStrings. +local VITAMIN_LABEL = { + hp = "HEALTH", attack = "ATTACK", defense = "DEFENSE", + speed = "SPEED", special = "SPECIAL", +} + +-- engine/items/item_effects.asm:1149 VitaminEffect. +local function vitamin(itemId, mon, data) + local stat = ItemEffects.VITAMIN[itemId] + mon.statExp = mon.statExp or Mon.newStatExp() + local cur = mon.statExp[stat] or 0 + if cur >= 25600 then + return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + end + mon.statExp[stat] = math.min(Mon.MAX_STAT_EXP, cur + 2560) + local def = data and data.pokemon and data.pokemon[mon.species] + if def and def.baseStats then + mon.stats = Mon.stats(def.baseStats, mon.dvs, mon.level, mon.statExp) + mon.maxHp = mon.stats.hp + end + Happiness.change(mon, "USEDITEM") + return { + used = true, + text = ("%s's\n%s rose."):format(monName(mon), VITAMIN_LABEL[stat]), + } +end + -- --------------------------------------------------------- held attributes -- -- The `held_items` registry (src/mods/Schemas.lua), which is the last two @@ -341,11 +377,8 @@ function ItemEffects.recordFor(itemId, data) return (merged and merged[itemId]) or ItemEffects.RECORDS[itemId] end --- Which family a PACK item runs on a party mon, or nil for anything whose --- ITEMMENU_PARTY behaviour is not ported (vitamins, PP UP, evolution stones). --- FULL_RESTORE classifies as "heal"; its full-HP status arm lives inside the --- record's own `use` the way FullRestoreEffect keeps both halves in one --- routine. +-- Which family a PACK item runs on a party mon, or nil for an id with no +-- item_effects record (engine/items/pack.asm UseItem, ITEMMENU_PARTY arm). function ItemEffects.partyAction(itemId, data) local record = ItemEffects.recordFor(itemId, data) return record and record.action or nil @@ -446,6 +479,36 @@ for itemId, row in pairs(ItemEffects.RESTORE_PP) do end) end +-- engine/items/item_effects.asm:2320 RestorePPEffect's PP_UP arm. +record("PP_UP", "pp", function(ctx) + local move = (ctx.mon.moves or {})[ctx.slot] + if type(move) ~= "table" or not move.id then + return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + end + local row = ((ctx.data and ctx.data.moves) or {})[move.id] + local name = (row and row.name) or move.id + -- constants/pokemon_data_constants.asm:216 PP_UP_MASK. + if move.id == "SKETCH" or (move.ppUps or 0) >= 3 then + return { used = false, text = ItemEffects.TEXT_PP_MAXED:format(name) } + end + local base = (row and row.pp) or move.maxPp + if not base then + return { used = false, text = ItemEffects.TEXT_PP_MAXED:format(name) } + end + -- engine/items/item_effects.asm:2736 ComputeMaxPP. + local bonus = math.min(math.floor(base / 5), 7) + move.ppUps = (move.ppUps or 0) + 1 + move.maxPp = base + move.ppUps * bonus + move.pp = (move.pp or 0) + bonus + return { used = true, text = ItemEffects.TEXT_PP_INCREASED:format(name) } +end) + +for itemId in pairs(ItemEffects.VITAMIN) do + record(itemId, "vitamin", function(ctx) + return vitamin(ctx.item, ctx.mon, ctx.data) + end) +end + record("RARE_CANDY", "candy", function(ctx) return rareCandy(ctx.mon, ctx.data) end) diff --git a/src/core/gen2/Save.lua b/src/core/gen2/Save.lua index 3d754198..cd270b09 100644 --- a/src/core/gen2/Save.lua +++ b/src/core/gen2/Save.lua @@ -272,6 +272,7 @@ Save.DEFAULT_OPTIONS = { -- Game Boy. The Gen 1 save's equivalent key is `colors` (SGB packs), which -- means something different, hence the different name. color = "gbc", + videoMode = "windowed", musicVol = 7, -- 0-7, like the GB's NR50 master volume sfxVol = 7, -- 0-7 musicFilter = 0, -- low-pass steps, 0 = off diff --git a/src/import/LauncherSettings.lua b/src/import/LauncherSettings.lua index 0fe7c4de..78eead2d 100644 --- a/src/import/LauncherSettings.lua +++ b/src/import/LauncherSettings.lua @@ -560,6 +560,16 @@ local function gen2Rows(opts, hooks) end) end + local okVm, VideoMode = pcall(require, "src.core.VideoMode") + if okVm then + add(Strings("VIDEO MODE"), + function() return VideoMode.modeLabel(opts.videoMode) end, + function(dir) + opts.videoMode = VideoMode.cycle(opts.videoMode, dir) + return true + end) + end + addTouchRows(rows, add, opts, hooks) return rows diff --git a/src/render/PaletteFX.lua b/src/render/PaletteFX.lua index 90fb6580..461a29c8 100644 --- a/src/render/PaletteFX.lua +++ b/src/render/PaletteFX.lua @@ -119,13 +119,9 @@ PaletteFX.GBC_OBJ_BLUE = { { 255, 255, 255 }, { 255, 132, 132 }, { 148, 58, 58 }, { 0, 0, 0 }, } --- The active game's OG boot-ROM background palette: blue for a Blue --- playthrough, red for Red. White (index 1) and black (index 4) are --- identical across Red/Blue, so callers that only touch the endpoints --- (e.g. BattleState's zone white/black snap) need no version branch there. --- Yellow is CGB-enhanced (pokeyellow CGBBasePalettes): named zones go through --- pal() / usesYellowCgb(), and ogBg() falls back to CGBBase PAL_ROUTE for any --- remaining whole-screen callers -- never Blue's GBC_BG_BLUE. +-- The active game's OG boot-ROM background palette: blue for Blue, red for Red. +-- Yellow is CGB-enhanced (pokeyellow CGBBasePalettes), so ogBg() falls back to +-- CGBBase PAL_ROUTE there, never Blue's GBC_BG_BLUE. function PaletteFX.ogBg() if GameVersion.isBlue() then return PaletteFX.GBC_BG_BLUE end if GameVersion.isYellow() then @@ -354,23 +350,14 @@ function PaletteFX.usesSpriteObp(mode) return mode == "ogred" and not GameVersion.isYellow() end --- ------- post-zone sprite redraw (OG RED) --- --- In OG RED the world canvas still runs through the whole-screen zone --- shade-remap shader, which would corrupt an OBP-baked sprite's true-color --- pixels. (SGB used to come through here too; it no longer bakes an object --- palette at all, so its characters are colorized by the zone like the ground --- they stand on and never queue a replay -- see usesSpriteObp, #301.) So SpriteRenderer draws the baked sprite into the canvas (its --- pixels come out zone-tinted there) AND records the draw here; --- Renderer:endFrame replays the list on top of the finished zone pass, --- scaled into screen space -- the GBC's OBJ-over-BG compositing, one draw --- late. Entries carrying `colors` are re-colorized draws (the tall-grass --- feet overdraw, which must keep hiding sprite feet) issued through the --- color-0-keyed shade-remap shader. World pass only; cleared per frame. +-- ------- post-zone sprite redraw (OG RED): OBP-baked draws recorded here and +-- replayed by Renderer:endFrame on top of the zone pass (usesSpriteObp, #301) local spriteRedraws = {} +local uiSpriteRedraws = {} function PaletteFX.clearSpriteRedraws() for i = #spriteRedraws, 1, -1 do spriteRedraws[i] = nil end + for i = #uiSpriteRedraws, 1, -1 do uiSpriteRedraws[i] = nil end end function PaletteFX.markSpriteRedraw(image, quad, x, y, sx, colors, keyed) @@ -392,6 +379,16 @@ function PaletteFX.spriteRedraws() return spriteRedraws end +function PaletteFX.markUiSpriteRedraw(image, quad, x, y) + if currentPass ~= "ui" then return end + uiSpriteRedraws[#uiSpriteRedraws + 1] = + { image = image, quad = quad, x = x + markOffsetX, y = y } +end + +function PaletteFX.uiSpriteRedraws() + return uiSpriteRedraws +end + -- Active named-palette table for COLORS: RED++ uses data/palettes_gbc.lua, -- everything else uses the ROM-imported data.palettes. function PaletteFX.pack(data) diff --git a/src/render/Renderer.lua b/src/render/Renderer.lua index 54b5518c..7d5553a1 100644 --- a/src/render/Renderer.lua +++ b/src/render/Renderer.lua @@ -1017,6 +1017,20 @@ function Renderer:endFrame(zones, worldZones) p.dx - p.a.x * Ux, p.dy - p.a.y * Uy, p.dx, p.dy, p.dw, p.dh) end end + local uiRedraws = PaletteFX.uiSpriteRedraws() + if uiRedraws[1] then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setScissor(uox, uoy, uvpw, uvph) + for _, r in ipairs(uiRedraws) do + if r.quad then + love.graphics.draw(r.image, r.quad, uox + r.x * Ux, uoy + r.y * Uy, + 0, Ux, Uy) + else + love.graphics.draw(r.image, uox + r.x * Ux, uoy + r.y * Uy, 0, Ux, Uy) + end + end + love.graphics.setScissor() + end -- The battle wipe covers the whole surface, letterbox included, so it goes -- over the finished composite rather than under the UI blit. On hardware diff --git a/src/render/TextBox.lua b/src/render/TextBox.lua index 3843cbc1..26cf2420 100644 --- a/src/render/TextBox.lua +++ b/src/render/TextBox.lua @@ -51,6 +51,7 @@ function TextBox.new(game, text, onDone, opts) self.choice = opts and opts.choice self.defaultNo = opts and opts.defaultNo self.choiceNoSound = opts and opts.noSound + self.money = opts and opts.money self.auto = opts and opts.auto self.stay = opts and opts.stay -- opts.instant: put the LAST page up already typed, with no typewriter and @@ -457,6 +458,14 @@ function TextBox:draw() pen = pen + Font.advanceOf(code) end end + if self.money then + -- money box (engine/menus/text_box.asm:130): DisplayMoneyBox at + -- hlcoord 11,0, the amount right-aligned on its middle row + Font.drawBox(11, 0, 9, 3) + love.graphics.setColor(0, 0, 0, 1) + local money = ("¥%d"):format(self.money() or 0) + Font.draw(money, 152 - Font.width(money), 8) + end if (self.waiting or (self.done and not self.choice and not self.auto and not self.stay)) and self.blink < 30 then diff --git a/src/script/gen2/CallAsm.lua b/src/script/gen2/CallAsm.lua index eb9924f1..d2f9f7fd 100644 --- a/src/script/gen2/CallAsm.lua +++ b/src/script/gen2/CallAsm.lua @@ -565,10 +565,11 @@ end -- ---- engine/events/poisonstep.asm ----------------------------------------- --- .PlayPoisonSFX: SFX_POISON, then LoadPoisonBGPals for two frames. The port --- plays the sound; the two-frame red flash is the renderer's business. +-- .PlayPoisonSFX: SFX_POISON, then LoadPoisonBGPals for four frames. +-- engine/events/poisonstep.asm:101 function H.PlayPoisonSFX(ctx) call(ctx, "playSfxNamed", SFX_POISON[1], SFX_POISON[2]) + call(ctx, "poisonBGFlash") return nil end diff --git a/src/ui/SummaryMenu.lua b/src/ui/SummaryMenu.lua index 6ff9f8c8..30d0de4f 100644 --- a/src/ui/SummaryMenu.lua +++ b/src/ui/SummaryMenu.lua @@ -134,13 +134,15 @@ function SummaryMenu:draw() Font.draw(("%03d"):format(def.dex or 0), 24, 56) if self.page == 1 then - -- HP bar (11,3) + numbers row 4, STATUS/ (9,6), the DrawLineBox - -- bracket around the name/HP block, and PrintLevel at (14,2). The - -- level belongs to page 1 ONLY: StatusScreen2 opens with ClearScreenArea - -- over (9,2) 5x10 (status_screen.asm:303-305), which wipes it. #280 + -- level is page 1 only: StatusScreen2 opens with ClearScreenArea over + -- (9,2) 5x10 (status_screen.asm:303-305). #280 printLevel(14, 2, mon.level) drawLineBox(19, 1, 6, 10) - HudTiles.drawHPBar(data, 11, 3, mon, 1) -- wHPBarType 1 + -- engine/pokemon/status_screen.asm:120-125 + local PaletteFX = require("src.render.PaletteFX") + local barZoned = PaletteFX.shader() ~= nil + and PaletteFX.pal(data, "GREENBAR") ~= nil + HudTiles.drawHPBar(data, 11, 3, mon, 1, barZoned) -- wHPBarType 1 Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 96, 32) Font.draw(Strings("STATUS/"), 72, 48) Font.draw(mon.status or "OK", 128, 48) diff --git a/src/ui/TitleState.lua b/src/ui/TitleState.lua index 18b8e059..2159dc4a 100644 --- a/src/ui/TitleState.lua +++ b/src/ui/TitleState.lua @@ -171,6 +171,27 @@ local function markVisibleTrueColor(x, y, w, h, cover) if ix2 < right then P.markTrueColor(ix2, iy1, right - ix2, iy2 - iy1) end end +-- ..(engine/movie/title.asm ln 321) +local function replayObjSprite(game, image, quad, x, y) + local P = require("src.render.PaletteFX") + if not P.usesSpriteObp() then return end + local top = game.stack and game.stack:top() + local box = top and top.titleUiBox + if box then + local w, h + if quad then + w, h = select(3, quad:getViewport()) + else + w, h = image:getDimensions() + end + if x < (box[3] + 1) * 8 and x + w > box[1] * 8 + and y < (box[4] + 1) * 8 and y + h > box[2] * 8 then + return + end + end + P.markUiSpriteRedraw(image, quad, x, y) +end + function TitleState.new(game, opts) opts = opts or {} local self = setmetatable({}, TitleState) @@ -662,12 +683,11 @@ function TitleState:draw() local x = 40 + math.floor((56 - w) / 2) + self.monOffset local y = 136 - h love.graphics.draw(sprite, x, y) - -- a full-color mon keeps its own palette through the SGB pass, minus - -- the strip Red's OAM covers (#350). Yellow never reaches here: its - -- layout has no cycling mon and no Red art (title_yellow.asm). + -- SGB: the mon keeps its palette minus the strip Red's OAM covers + -- (#350); Yellow never reaches here (title_yellow.asm) if spriteTrueColor then local cover - if playerImage then + if playerImage and not require("src.render.PaletteFX").usesSpriteObp() then local pw, ph = playerImage:getDimensions() cover = { 82, 80, pw, ph } end @@ -678,10 +698,13 @@ function TitleState:draw() if self.playerQuads then for _, part in ipairs(self.playerQuads) do love.graphics.draw(playerImage, part[1], 82 + part[2], 80 + part[3]) + replayObjSprite(self.game, playerImage, part[1], 82 + part[2], 80 + part[3]) end love.graphics.draw(playerImage, self.ballQuad, 82, self.ballY) + replayObjSprite(self.game, playerImage, self.ballQuad, 82, self.ballY) elseif playerImage then love.graphics.draw(playerImage, 82, 80) + replayObjSprite(self.game, playerImage, nil, 82, 80) end end self:drawCopyright(136 + (preRibbon and 0 or scrollY)) diff --git a/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index 1e118fcd..ecaf09a2 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -31,6 +31,7 @@ local ItemEffects = require("src.core.gen2.ItemEffects") local Mon = require("src.battle.gen2.Mon") local Palettes = require("src.world.gen2.Palettes") local Pokerus = require("src.core.gen2.Pokerus") +local Prize = require("src.battle.gen2.Prize") local Runtime = require("src.mods.Runtime") local Screens = require("src.ui.Screens") local Sound = require("src.core.Sound") @@ -621,6 +622,19 @@ function BattleState:drawPic(mon, back) and not (self.vanishAnim and self.vanishAnim == self.anim) then return end + -- GetSubstitutePic (engine/battle_anims/anim_commands.asm:905-960): the + -- doll sits in the mon's own pic box and takes its palette. + local doll, dollQuad + if not (trainerBack or enemyTrainer) then + local over = anim and anim.pic + local up + if over ~= nil then + up = over == "substitute" + else + up = mon and mon.volatile and (mon.volatile.substitute or 0) > 0 + end + if up then doll, dollQuad = self:substituteDoll(back) end + end local G = love.graphics local w, h = image:getDimensions() local px, py @@ -660,6 +674,10 @@ function BattleState:drawPic(mon, back) px = px + math.floor(w * (1 - scale) / 2) py = py + math.floor(h * (1 - scale)) end + if doll then + px = (back and 32 or 112) + ((anim and anim.slide) or 0) + py = back and 80 or 40 + end G.setColor(1, 1, 1, 1) -- No mon on this side at all in the catching tutorial, where the box holds -- the DUDE's back-pic and nothing else for the whole battle. @@ -682,6 +700,10 @@ function BattleState:drawPic(mon, back) -- what is still inside the box rather than drawn over the HUD below it. local sunk = self:faintSink(side) local function body() + if doll then + G.draw(doll, dollQuad, px, py) + return + end if sunk > 0 then local visible = h - math.floor(sunk / scale) if visible <= 0 then return end @@ -701,6 +723,24 @@ function BattleState:drawPic(mon, back) end end +-- MonsterSpriteGFX (gfx/sprites.asm:82): the facing-DOWN 16x16 frame for the +-- enemy's frontpic, facing-UP for the player's backpic. +function BattleState:substituteDoll(back) + if self.subDoll == nil then + local ok, image = pcall(Assets.image, "assets/generated/sprites/monster.png") + if ok and image then + local w, h = image:getDimensions() + self.subDoll = { image = image, + down = love.graphics.newQuad(0, 0, 16, 16, w, h), + up = love.graphics.newQuad(0, 16, 16, 16, w, h) } + else + self.subDoll = false + end + end + if not self.subDoll then return nil end + return self.subDoll.image, back and self.subDoll.up or self.subDoll.down +end + -- The top `visible` rows of a pic, for the faint slide. One quad, re-aimed, -- the way BattleAnimView keeps one blit quad rather than a new one per frame. function BattleState:cropQuad(image, visible) @@ -1072,16 +1112,8 @@ function BattleState:stepAnim(input) end end --- NOTE on picHidden and the animation runtime. An animation that ENDS with a --- pic box cleared could latch it here, and the tilemap argument says it should: --- BattleAnimRestoreHuds redraws the two HUDs and nothing else. It deliberately --- does not, because BATTLE_BG_EFFECT_REMOVE_MON and _RETURN_MON are also used --- by moves whose user is still standing there afterwards -- SUBSTITUTE (the --- doll takes the box over, and nothing in this port draws one yet), SKY_ATTACK, --- BEAT_UP, BATON_PASS -- and a blanket latch would make those mons invisible --- for the rest of the fight. The two moments the cart really does leave the --- box empty for good are latched explicitly instead: a catch (pushCaught) and --- a faint (MonFaintedAnimation, in update). +-- REMOVE_MON / RETURN_MON also serve SUBSTITUTE, SKY_ATTACK, BEAT_UP and +-- BATON_PASS, so picHidden is only latched by a catch and a faint. -- Whatever Call_PlayBattleAnim was standing in front of: a send-out's cry and -- HUD update run the moment its animation is done, cut short or not. @@ -1101,6 +1133,7 @@ function BattleState:animPicState(side) size = bg.picSize[side], slide = bg.slide[side] or 0, shade = bg.monShade[side], + pic = self.anim.picOverride[side], } end @@ -1190,22 +1223,14 @@ function BattleState:advanceQueue() if event.kind == "level" and event.index then self.evolvable[event.index] = true -- GiveExperiencePoints' `.skip_active_mon_update` guard - -- (engine/battle/core.asm:6999-7003): only the mon that is OUT copies its - -- recalculated HP, max HP and level into the battle struct, and only then - -- does `callfar UpdatePlayerHUD` (:7034) redraw the bar. That is a - -- REDRAW, not AnimateHPBar, so the shown HP snaps instead of chasing -- - -- without it the bar kept the pre-level-up HP against the new maximum - -- until the next damage or heal event moved it. + -- (engine/battle/core.asm:6999-7003): the OUT mon's shown HP snaps. local battle = self.battle local mon = battle and battle.party and battle.party[event.index] -- pokegold engine/battle/core.asm:7057-7069: every mon that leveled -- gets the stats box, not just the mon currently on the field. self.pendingStatsMon = mon - -- engine/battle/core.asm:7044 + -- engine/battle/core.asm:7284 if mon and mon == battle.player then - event.text = nil - event.sfx = nil - event.waitSfx = nil if self.shownHp then self.shownHp.player = mon.hp or 0 if self.hpAnim and self.hpAnim.side == "player" then @@ -1213,9 +1238,6 @@ function BattleState:advanceQueue() end end -- `ld [wBattleMonLevel], a` in the same guarded block (:7018-7020). - -- AnimateExpBar has already walked the number up one level at a time by - -- the time this runs, so this only catches a level gained with no exp - -- crawl behind it. self.shownLevel = mon.level or self.shownLevel end end @@ -2464,7 +2486,10 @@ function BattleState:pushCaught(enemy, itemId) self:push({ kind = "dex-entry", species = enemy.species }) end if self.contest then - return self:contestCatch(enemy) + -- A contest catch still exits through the `.run` arm with result WIN + -- (engine/battle/core.asm:4780-4783), so CheckPayDay runs for it too. + self:contestCatch(enemy) + return self:pushPayDay() end save.party = save.party or {} local toPc = #save.party >= Boxes.PARTY_SIZE @@ -2520,6 +2545,19 @@ function BattleState:pushCaught(enemy, itemId) self:push({ kind = "message", text = self:name(enemy) .. " was sent to BILL's PC." }) end + self:pushPayDay() +end + +-- CheckPayDay runs on a capture too: `and $f` keeps the win arm +-- (engine/battle/core.asm:7971-7976, :8014-8042). +function BattleState:pushPayDay() + local save = self.save + local coins = Prize.payDay(save, self.battle.payDay, self.battle.amuletCoin) + self.battle.payDay = nil + if coins then + self:push({ kind = "message", + text = Prize.payDayMessage(coins, save.player and save.player.name) }) + end end -- CheckWhetherToAskSwitch: a started battle, more than one mon, no link, the @@ -3029,7 +3067,7 @@ function BattleState:applyPartyItem(itemId, action, mon, slot) local before = (mon and mon.hp) or 0 local result if action == "pp" then - result = ItemEffects.usePpItem(itemId, mon, slot) + result = ItemEffects.usePpItem(itemId, mon, slot, data) else result = ItemEffects.useOnMon(itemId, mon, data) end diff --git a/src/ui/gen2/OptionsMenu.lua b/src/ui/gen2/OptionsMenu.lua index c6cf7dce..0b200f81 100644 --- a/src/ui/gen2/OptionsMenu.lua +++ b/src/ui/gen2/OptionsMenu.lua @@ -172,6 +172,17 @@ local ROWS = { text = function(options) return require("src.render.GBCFX").levelLabel(options.gbcfx or 0) end }, + { label = "VIDEO MODE", key = "videoMode", port = true, + cycle = function(options, delta) + local VideoMode = require("src.core.VideoMode") + options.videoMode = VideoMode.cycle(options.videoMode, delta) + VideoMode.apply(options.videoMode) + end, + text = function(options) + local VideoMode = require("src.core.VideoMode") + return VideoMode.normalize(options.videoMode) == "borderless" + and "FULL" or "WINDOWED" + end }, { id = "touchControls", label = "TOUCH PAD", port = true, text = function(options) local tc = options.touchControls diff --git a/src/ui/gen2/PartyMenu.lua b/src/ui/gen2/PartyMenu.lua index f765fd09..13a508d3 100644 --- a/src/ui/gen2/PartyMenu.lua +++ b/src/ui/gen2/PartyMenu.lua @@ -283,6 +283,14 @@ function PartyMenu:finishSwitch() if self.save and self.save.party == party then Mail.swapSlots(self.save, from, to) end + -- engine/pokemon/switchpartymons.asm:38 + local data = self.game and self.game.data + local ok, Sound = pcall(require, "src.core.Sound") + if not (ok and data and Sound and Sound.play) then return end + local sfx = data.audio and data.audio.sfx + if sfx and sfx[Sound.resolve(data, "Sfx_SwitchPokemon")] then + pcall(Sound.play, data, "Sfx_SwitchPokemon") + end end -- The reopened list: InitPartyMenuNoCancel caps the cursor at the last mon, diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index 7008e360..be17de27 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -1969,8 +1969,11 @@ function OverworldState:tryBookshelf(fx, fy) return true end if entry.screen then - -- Blue's house shelf opens the TOWN MAP (TownMapText) - pcall(Screens.push, Game, entry.screen) + -- engine/events/hidden_events/town_map.asm:1 + Game.stack:push(TextBox.new(Game, + t[entry.text] or t._TownMapText + or romText(Game.data, "_TownMapText", "A TOWN MAP."), + function() pcall(Screens.push, Game, entry.screen) end)) return true end local kind = entry.kind @@ -2136,14 +2139,17 @@ function OverworldState:tryHiddenObject(fx, fy) for _, h in ipairs(extras.pcTiles[self.map.id] or {}) do if h.x == fx and h.y == fy and (not h.facing or h.facing == facing) then if self.map.id == "REDS_HOUSE_2F" then - -- The player's bedroom PC is the one location in Red/Blue whose PC - -- callback is OpenRedsPC (engine/events/hidden_objects/players_pc.asm), - -- which runs the PlayerPC predef directly -- item storage, no - -- SOMEONE'S/BILL'S PC main menu (DisplayPCMainMenu). Every other - -- pcTile is a Pokémon Center-style PC that shows the multi-PC menu. (#228) + -- OpenRedsPC (engine/events/hidden_objects/players_pc.asm) runs the + -- PlayerPC predef directly, no DisplayPCMainMenu (#228) require("src.core.Sound").play(Game.data, "Turn_On_PC") - -- direct access: ExitPlayerPC rings SFX_TURN_OFF_PC (players_pc.asm, #960) - Screens.push(Game, "PlayerPC", { direct = true }) + -- engine/menus/players_pc.asm:16 + Game.stack:push(TextBox.new(Game, + (Game.data.text or {})._TurnedOnPC2Text + or romText(Game.data, "_TurnedOnPC2Text", "{PLAYER} turned on\nthe PC."), + function() + -- direct access: ExitPlayerPC rings SFX_TURN_OFF_PC (players_pc.asm, #960) + Screens.push(Game, "PlayerPC", { direct = true }) + end, { instant = true })) else self:openPC() end @@ -2892,13 +2898,16 @@ function OverworldState:openPC(onDone) done() end table.insert(items, { label = Strings("LOG OFF"), onSelect = logOff }) - -- pokered sets BIT_NO_MENU_BUTTON_SOUND for the whole PC session - -- (engine/overworld/pokecenter_pc.asm / player_pc.asm); DisplayPCMainMenu - -- calls TextBoxBorder with c=14 (interior width, +2 for the border), so - -- tw here (total width) is 16 - Game.stack:push(Menu.new(Game, items, + -- BIT_NO_MENU_BUTTON_SOUND for the whole PC session; DisplayPCMainMenu's + -- TextBoxBorder c=14 interior -> tw 16 (engine/overworld/pokecenter_pc.asm) + local menu = Menu.new(Game, items, { tx = 0, ty = 0, tw = 16, th = #items * 2 + 2, onCancel = logOff, - noSound = true })) + noSound = true }) + -- engine/menus/pc.asm:5 + Game.stack:push(TextBox.new(Game, + (Game.data.text or {})._TurnedOnPC1Text + or romText(Game.data, "_TurnedOnPC1Text", "{PLAYER} turned on\nthe PC."), + function() Game.stack:push(menu) end)) end -- The PROF. OAK's PC session (engine/menus/oaks_pc.asm OpenOaksPC): the diff --git a/src/world/gen2/StepEvents.lua b/src/world/gen2/StepEvents.lua index 765d36be..228b8213 100644 --- a/src/world/gen2/StepEvents.lua +++ b/src/world/gen2/StepEvents.lua @@ -78,7 +78,7 @@ function StepEvents.poisonStep(party) return { kind = "poisonFaint", fainted = fainted, hurt = hurt, blocks = true } end if #hurt > 0 then - -- .PlayPoisonSFX and the two-frame red flash, then `xor a`: no carry, so + -- .PlayPoisonSFX and the four-frame BG flash, then `xor a`: no carry, so -- the step still counts and the wild roll still happens. return { kind = "poisonHurt", hurt = hurt, blocks = false } end diff --git a/src/world/gen2/World.lua b/src/world/gen2/World.lua index f2ca4150..fd1fc787 100644 --- a/src/world/gen2/World.lua +++ b/src/world/gen2/World.lua @@ -2123,6 +2123,11 @@ function World:updateShake() shake.phase = (shake.left % 2 == 0) and shake.amplitude or -shake.amplitude end +-- engine/events/poisonstep_pals.asm:9 +function World:poisonBGFlash() + self.poisonFlash = 4 +end + -- Script_warp / Script_warpfacing: a raw destination CELL, distinct from the -- warp_events World:takeWarp follows. `facing` is nil for `warp` and a -- Movement direction for `warpfacing` (PLAYERSPRITESETUP_CUSTOM_FACING). A @@ -2400,7 +2405,9 @@ end function World:playMapMusic() local data = self.game and self.game.data if data and data.audio and data.audio.runtime and self.map then - Music.playMap(data, self.map.id) + -- SpecialMapMusic (home/audio.asm:397) + Music.playMap(data, self.map.id, nil, + FieldMoves.isSurfing(self.playerState)) end end @@ -3246,7 +3253,9 @@ function World:surfStartStep(mon) self:applyPlayerState(FieldMoves.surfType(mon)) local audio = self.game and self.game.data and self.game.data.audio if audio and audio.runtime and self.map then - Music.playMap(self.game.data, self.map.id) + -- SpecialMapMusic (home/audio.asm:397) + Music.playMap(self.game.data, self.map.id, nil, + FieldMoves.isSurfing(self.playerState)) end if p.scriptStep then p:scriptStep(p.facing) end self.fieldMove = { phase = "step" } @@ -5363,7 +5372,9 @@ function World:runSurf(result) self:applyPlayerState(result.state) local audio = self.game and self.game.data and self.game.data.audio if audio and audio.runtime and self.map then - Music.playMap(self.game.data, self.map.id) + -- SpecialMapMusic (home/audio.asm:397) + Music.playMap(self.game.data, self.map.id, nil, + FieldMoves.isSurfing(self.playerState)) end if self.player and self.player.scriptStep then self.player:scriptStep(self.player.facing) @@ -8524,13 +8535,12 @@ function World:setMap(mapId, cx, cy, facing, opts) -- rebuildPeople may have pooled fresh NPCs; give them their colors too. self:applyPalettes() local audio = self.game and self.game.data and self.game.data.audio - -- PlayMapMusicBike (home/audio.asm), which is the mapsetup every load uses: - -- a player still on the bike keeps the bike theme across the warp instead of - -- hearing the new map's song. Music.play dedupes the same label, so this is - -- safe on seamless edge crossings. + -- PlayMapMusicBike / SpecialMapMusic (home/audio.asm:335, :397): a biking + -- player keeps the bike theme; Music.play dedupes seamless edge crossings. if audio and audio.runtime then if not (FieldMoves.isBiking(self.playerState) and self:playBikeMusic()) then - Music.playMap(self.game.data, mapId) + Music.playMap(self.game.data, mapId, nil, + FieldMoves.isSurfing(self.playerState)) end end -- Fires with the map fully built and BEFORE the map's own scene script, so a @@ -8912,14 +8922,13 @@ function World:movePlayer(dir) if result == "moved" and Permissions.surfable(map:cellCollision(p.targetX, p.targetY)) == "land" then - -- .ExitWater: GetOutOfWater writes PLAYER_NORMAL and runs - -- UpdatePlayerSprite BEFORE .DoStep, so the player is already off the - -- Lapras for the step that puts them on the beach, and PlayMapMusic then - -- swaps the surfing theme back for the map's own. + -- .ExitWater: GetOutOfWater writes PLAYER_NORMAL before .DoStep, then + -- PlayMapMusic swaps the surf theme back (home/audio.asm:308) self:applyPlayerState(FieldMoves.PLAYER_NORMAL) local audio = self.game and self.game.data and self.game.data.audio if audio and audio.runtime then - Music.playMap(self.game.data, map.id) + Music.playMap(self.game.data, map.id, nil, + FieldMoves.isSurfing(self.playerState)) end end end @@ -9159,7 +9168,7 @@ function World:countStep() elseif event.kind == "poisonFaint" then self:poisonFaintScript(event) elseif event.kind == "poisonHurt" then - -- .PlayPoisonSFX alone: the sound and the two-frame red flash, no script. + -- .PlayPoisonSFX alone: the sound and the four-frame BG flash, no script. CallAsm.run(self, "PlayPoisonSFX") elseif event.kind == "repel" then self:repelWoreOff() @@ -10031,6 +10040,18 @@ function World:draw() G.setColor(1, 1, 1, 1) end + -- engine/events/poisonstep_pals.asm:9-42 + if self.poisonFlash and self.poisonFlash > 0 then + self.poisonFlash = self.poisonFlash - 1 + if GbcPalette.mode == "gbc" then + G.setColor(28 / 31, 21 / 31, 1, 0.55) + else + G.setColor(0, 0, 0, 0.45) + end + G.rectangle("fill", 0, 0, w, h) + G.setColor(1, 1, 1, 1) + end + -- FadeOutToWhite / FadeOutToBlack, held until a FadeInFrom* clears it. On -- the cart the pair brackets a scripted cutscene's set change (the Elite Four -- doors, the Radio Tower takeover, Lugia's chamber); the port has no diff --git a/tests/drivers/title_obj_palette_bug1214_test.lua b/tests/drivers/title_obj_palette_bug1214_test.lua new file mode 100644 index 00000000..271eb822 --- /dev/null +++ b/tests/drivers/title_obj_palette_bug1214_test.lua @@ -0,0 +1,323 @@ +-- ..(engine/movie/title.asm ln 321 DrawPlayerCharacter, ball at ln 99) +return function(game) + local U = dofile("tests/drivers/util.lua") + local P = require("src.render.PaletteFX") + local GameVersion = require("src.core.GameVersion") + local TitleState = require("src.ui.TitleState") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local REST_OBJ_MIN, REST_OBJ_MAX = 340, 520 + local MENU_OBJ_SLACK = 8 + -- ..(engine/movie/title.asm ln 321): 40x56 at (82,80), plus a margin + local BAND = { 74, 74, 132, 144 } + + local fails = 0 + local function check(label, ok, detail) + U.log(ok and "PASS" or "FAIL", label, detail or "") + if not ok then fails = fails + 1 end + return ok + end + + local rendered = 0 + local hostDraw = love.draw + love.draw = function(...) + local r = hostDraw(...) + rendered = rendered + 1 + return r + end + + local function waitRenderedFrames(frames) + local target = rendered + (frames or 2) + for _ = 1, 2400 do + if rendered >= target then return true end + U.wait(1) + end + return false + end + + local function waitFor(pred, limit) + for _ = 1, limit or 1800 do + if pred() then return true end + U.wait(1) + end + return false + end + + local function redrawCount(frames) + local m = 0 + for _ = 1, frames or 4 do + waitRenderedFrames(1) + local n = #P.uiSpriteRedraws() + if n > m then m = n end + end + return m + end + + local objRamp + local function isObj(R, G, B) + for _, c in ipairs(objRamp) do + if math.abs(R - c[1]) <= 12 and math.abs(G - c[2]) <= 12 + and math.abs(B - c[3]) <= 12 then + return true + end + end + return false + end + + local function letterbox(img) + local W, H = img:getWidth(), img:getHeight() + local x1, y1, x2, y2 = W, H, -1, -1 + for y = 0, H - 1 do + for x = 0, W - 1 do + local r, g, b = img:getPixel(x, y) + if r > 0.02 or g > 0.02 or b > 0.02 then + if x < x1 then x1 = x end + if x > x2 then x2 = x end + if y < y1 then y1 = y end + if y > y2 then y2 = y end + end + end + end + local bw, bh = x2 - x1 + 1, y2 - y1 + 1 + if x2 >= x1 and math.abs(bw / 160 - bh / 144) < 0.05 then + return x1, y1, bh / 144 + end + local s = math.min(W / 160, H / 144) + return math.floor((W - 160 * s) / 2), math.floor((H - 144 * s) / 2), s + end + + local function census(path, bx1, by1, bx2, by2) + local f = io.open(path, "rb") + if not f then return nil end + local bytes = f:read("*a") + f:close() + local ok, img = pcall(function() + return love.image.newImageData( + love.filesystem.newFileData(bytes, "shot.png")) + end) + if not ok or not img then return nil end + local W, H = img:getWidth(), img:getHeight() + local ox, oy, scale = letterbox(img) + local x1 = ox + math.floor((bx1 or 0) * scale) + local y1 = oy + math.floor((by1 or 0) * scale) + local x2 = ox + math.ceil((bx2 or 160) * scale) - 1 + local y2 = oy + math.ceil((by2 or 144) * scale) - 1 + local c = { grey = 0, obj = 0, ink = 0, scale = scale, w = W, h = H } + for y = math.max(0, y1), math.min(H - 1, y2) do + for x = math.max(0, x1), math.min(W - 1, x2) do + local r, g, b = img:getPixel(x, y) + local R = math.floor(r * 255 + 0.5) + local G = math.floor(g * 255 + 0.5) + local B = math.floor(b * 255 + 0.5) + if R == G and G == B and R > 60 and R < 240 then c.grey = c.grey + 1 end + if R < 30 and G < 30 and B < 30 then c.ink = c.ink + 1 end + if isObj(R, G, B) then c.obj = c.obj + 1 end + end + end + local d = scale * scale + c.rawGrey, c.rawObj, c.rawInk = c.grey, c.obj, c.ink + c.grey = math.floor(c.grey / d + 0.5) + c.obj = math.floor(c.obj / d + 0.5) + c.ink = math.floor(c.ink / d + 0.5) + return c + end + + local function report(tag, c) + U.log(string.format( + "%s: obj=%d grey=%d ink=%d (raw obj=%d grey=%d at %dx%d, scale %.2f)", + tag, c.obj, c.grey, c.ink, c.rawObj, c.rawGrey, c.w, c.h, c.scale)) + end + + local version = GameVersion.get() + + U.wait(5) + U.tap(game, "start") + local title + check("reached the title screen", waitFor(function() + title = game.stack:top() + return getmetatable(title) == TitleState + end, 300)) + if getmetatable(title) ~= TitleState then + U.log("BUG #1214 DRIVER ABORTED: never reached TitleState") + love.event.quit(1) + return + end + + game.save.options.colors = "ogred" + P.applyOptions(game.save.options) + check("COLORS is ogred", P.mode == "ogred", P.mode) + check("usesSpriteObp() true, so the OBJ ramp is baked", + P.usesSpriteObp() == true) + objRamp = { P.ogObj()[2], P.ogObj()[3] } + U.log("version:", tostring(version), "boot-ROM OBJ ramp:", + string.format("(%d,%d,%d)/(%d,%d,%d)", + objRamp[1][1], objRamp[1][2], objRamp[1][3], + objRamp[2][1], objRamp[2][2], objRamp[2][3])) + + check("title reached the loop phase", + waitFor(function() return title.phase == "loop" end)) + check("mon cycle reached the hold beat", + waitFor(function() return title.scrollPhase == "hold" end)) + waitRenderedFrames(3) + + local restObj + do + check("nothing is occluding the title", game.stack:top() == title + and title.titleUiBox == nil) + check("at rest the four player draws are recorded for replay", + redrawCount(6) == 4, "#" .. #P.uiSpriteRedraws()) + local shot = SHOT_DIR .. "/bug1214_1_ogred_rest.png" + if U.shot(game, shot) then + local c = census(shot) + local band = census(shot, BAND[1], BAND[2], BAND[3], BAND[4]) + report("REST screen", c) + report("REST player band", band) + restObj = c.obj + check("#1214 REST: player wears the boot-ROM OBJ ramp (expect ~427" + .. " canvas px, 12141 raw on a 1024x768 window)", + c.obj >= REST_OBJ_MIN and c.obj <= REST_OBJ_MAX, c.obj) + check("#1214 REST: no DMG grey in the player band (r==g==b, 60<r<240)", + band.grey == 0, band.grey) + end + end + + do + check("caught the mon sliding through the player bbox", + waitFor(function() + return title.scrollPhase == "in" and title.monOffset + and title.monOffset >= 10 and title.monOffset <= 60 + end)) + local before = title.monOffset + local shot = SHOT_DIR .. "/bug1214_2_ogred_midscroll.png" + if U.shot(game, shot) then + local c = census(shot) + local band = census(shot, BAND[1], BAND[2], BAND[3], BAND[4]) + U.log("mid-scroll monOffset:", tostring(before), "->", + tostring(title.monOffset)) + report("MIDSCROLL screen", c) + report("MIDSCROLL player band", band) + check("#1214 MIDSCROLL: no grey leaking through the transparent" + .. " player sprite (was ~6135 raw px before the replay)", + band.grey == 0, band.grey) + check("#1214 MIDSCROLL: player still wears the OBJ ramp", + c.obj >= REST_OBJ_MIN and c.obj <= REST_OBJ_MAX, c.obj) + end + end + + local saveName = require("src.core.SaveData").saveFilename(version) + do + check("mon cycle back on the hold beat", + waitFor(function() return title.scrollPhase == "hold" end)) + waitRenderedFrames(3) + pcall(function() love.filesystem.write(saveName, "return {}") end) + title:openMenu() + waitRenderedFrames(3) + local menu = game.stack:top() + local box = menu and menu.titleUiBox + U.log("menu titleUiBox:", box and table.concat(box, ",") or "nil") + check("main menu lists CONTINUE, so the box bottom is y 80", + box ~= nil and box[4] == 9, + box and tostring(box[4]) or "nil") + check("#1214 MENU: a box ending at y 80 does not suppress the player," + .. " who starts at y 80", + redrawCount(6) == 4, "#" .. #P.uiSpriteRedraws()) + local shot = SHOT_DIR .. "/bug1214_3_ogred_menu.png" + if U.shot(game, shot) then + local c = census(shot) + local band = census(shot, BAND[1], BAND[2], BAND[3], BAND[4]) + report("MENU screen", c) + report("MENU player band", band) + check("#1214 MENU: OBJ ramp count unchanged from rest", + restObj ~= nil and math.abs(c.obj - restObj) <= MENU_OBJ_SLACK, + string.format("%d vs rest %d", c.obj, restObj or -1)) + check("#1214 MENU: no DMG grey in the player band", + band.grey == 0, band.grey) + end + game.stack:pop() + waitRenderedFrames(2) + end + + do + local ContinueInfo + for i = 1, 40 do + local name, val = debug.getupvalue(TitleState.openMenu, i) + if not name then break end + if name == "ContinueInfo" then ContinueInfo = val break end + end + check("ContinueInfo class reachable", ContinueInfo ~= nil) + local info + if ContinueInfo then + local ok, st = pcall(ContinueInfo.new, title, + { player = { name = "RED" }, playTime = 0, pokedex = { owned = {} } }) + check("ContinueInfo.new", ok, not ok and tostring(st) or "") + if ok then info = st end + end + if info then + check("CONTINUE window box is (4,7)-(19,16)", + info.titleUiBox and info.titleUiBox[1] == 4 + and info.titleUiBox[2] == 7 and info.titleUiBox[3] == 19 + and info.titleUiBox[4] == 16) + game.stack:push(info) + waitRenderedFrames(3) + check("#1214 CONTINUE: every player draw overlapping the window is" + .. " suppressed", redrawCount(6) == 0, + "#" .. #P.uiSpriteRedraws()) + local shot = SHOT_DIR .. "/bug1214_4_ogred_continue.png" + if U.shot(game, shot) then + local c = census(shot, 33, 57, 159, 135) + report("CONTINUE box interior", c) + check("#1214 CONTINUE: no OBJ ramp bleeding over the window", + c.obj == 0, c.obj) + local row = census(shot, 33, 103, 159, 113) + report("CONTINUE POKeDEX row", row) + check("#1214 CONTINUE: the POKeDEX row is legible ink, not sprite", + row.ink > 30 and row.obj == 0, + string.format("ink=%d obj=%d", row.ink, row.obj)) + end + game.stack:pop() + waitRenderedFrames(2) + end + end + pcall(function() love.filesystem.remove(saveName) end) + + do + for _, mode in ipairs({ "gbc", "og", "og_inv", "gbc_inv", "classic" }) do + P.applyOptions({ colors = mode }) + waitRenderedFrames(2) + check(mode .. " records no UI sprite redraws", redrawCount(4) == 0) + end + P.applyOptions({ colors = "gbc" }) + check("mon cycle back on the hold beat under gbc", + waitFor(function() return title.scrollPhase == "hold" end)) + waitRenderedFrames(3) + local shot = SHOT_DIR .. "/bug1214_5_gbc.png" + if U.shot(game, shot) then + local c = census(shot) + report("GBC", c) + check("#1214 GBC: the SGB title never wears the boot-ROM OBJ ramp", + c.obj == 0, c.obj) + end + end + + game.save.options.colors = "ogred" + P.applyOptions(game.save.options) + waitRenderedFrames(3) + + if fails > 0 then + U.log("################################################################") + U.log("#1214 FAILED: " .. fails .. " check(s) above, shots in " .. SHOT_DIR) + U.log("################################################################") + else + U.log("#1214 PASS: all checks, shots in " .. SHOT_DIR) + end + U.log("On screen now: the OG RED title in COLORS = OG. The player and the") + U.log("pokeball in his hand must be the boot ROM's OBJ ramp (green on Red,") + U.log("pink on Blue) while the logo, ribbon, cycling mon and copyright line") + U.log("stay on the BG ramp. Before #1214 he was BG dark red, and the first") + U.log("attempt at the fix left grey rectangles around him as mons slid past.") + U.log("START opens the menu, whose box ends one pixel above his head.") + + while true do + coroutine.yield() + end +end diff --git a/tests/engine/gen2_always_hit_bug1272.lua b/tests/engine/gen2_always_hit_bug1272.lua new file mode 100644 index 00000000..40535bcb --- /dev/null +++ b/tests/engine/gen2_always_hit_bug1272.lua @@ -0,0 +1,155 @@ +-- engine/battle/effect_commands.asm:1553-1614 BattleCommand_CheckHit + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local Battle = require("src.battle.gen2.Battle") +local Effects = require("src.battle.gen2.Effects") +local Mon = require("src.battle.gen2.Mon") + +local TYPES = { + NORMAL = { id = "NORMAL", index = 0, category = "physical" }, + FIGHTING = { id = "FIGHTING", index = 1, category = "physical" }, +} + +local MOVES = { + TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL", + accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" }, + SWIFT = { id = "SWIFT", name = "SWIFT", power = 60, type = "NORMAL", + accuracy = 100, pp = 20, effect = "EFFECT_ALWAYS_HIT" }, + VITAL_THROW = { id = "VITAL_THROW", name = "VITAL THROW", power = 70, + type = "FIGHTING", accuracy = 100, pp = 10, + effect = "EFFECT_ALWAYS_HIT" }, +} + +local POKEMON = { + growthRates = { + GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0, + linear = 0, constant = 0 }, + }, + MACHOP = { + id = "MACHOP", index = 66, name = "MACHOP", + baseStats = { hp = 70, attack = 80, defense = 50, speed = 35, + specialAttack = 35, specialDefense = 35 }, + types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63, + levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {}, + }, +} + +local DATA = { + pokemon = POKEMON, + moves = MOVES, + type_chart = { types = TYPES, matchups = {} }, + items = { + BRIGHTPOWDER = { id = "BRIGHTPOWDER", name = "BRIGHTPOWDER", + heldEffect = "HELD_BRIGHTPOWDER", heldParameter = 51 }, + }, +} + +local perfect = { attack = 15, defense = 15, speed = 15, special = 15 } +perfect.hp = Mon.hpDV(perfect) + +local function alwaysHighRoll(n) + return 99 % math.max(1, n or 1) +end + +local function newBattle() + local player = Mon.new(DATA, "MACHOP", 15, { dvs = perfect }) + player.moves = { + { id = "TACKLE", pp = 35, maxPp = 35 }, + { id = "SWIFT", pp = 20, maxPp = 20 }, + { id = "VITAL_THROW", pp = 10, maxPp = 10 }, + } + local wild = Mon.new(DATA, "MACHOP", 15, { dvs = perfect }) + wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } + return Battle.new({ data = DATA, party = { player }, wild = wild, + random = alwaysHighRoll }), player, wild +end + +local function moveEvent(events) + for _, event in ipairs(events or {}) do + if event.kind == "move" then return event end + end + return nil +end + +local function findText(events, text) + for _, event in ipairs(events or {}) do + if event.kind == "message" and event.text == text then return true end + end + return false +end + +local function use(battle, player, wild, moveId) + wild.hp = wild.maxHp + battle:useMove(player, wild, moveId) + local events = battle:takeEvents() + return (moveEvent(events) or {}).missed == true, events +end + +do + local battle, player, wild = newBattle() + battle.stages.enemy.evasion = Effects.MAX_STAGE + T.check(use(battle, player, wild, "TACKLE"), + "at +6 evasion a 100-accuracy move misses on a high roll") + T.check(not use(battle, player, wild, "SWIFT"), + "SWIFT ignores the evasion stage") + T.check(wild.hp < wild.maxHp, "and the hit lands damage") + T.check(not use(battle, player, wild, "VITAL_THROW"), + "VITAL THROW carries the same effect") +end + +do + local battle, player, wild = newBattle() + battle.stages.player.accuracy = -Effects.MAX_STAGE + T.check(use(battle, player, wild, "TACKLE"), + "at -6 accuracy a 100-accuracy move misses on a high roll") + T.check(not use(battle, player, wild, "SWIFT"), + "SWIFT ignores the accuracy stage") +end + +do + local battle, player, wild = newBattle() + wild.item = "BRIGHTPOWDER" + T.check(use(battle, player, wild, "TACKLE"), + "BRIGHTPOWDER alone is enough to miss on a 99 roll") + T.check(not use(battle, player, wild, "SWIFT"), + "the `ret z` sits ahead of .BrightPowder too") +end + +do + local battle, player, wild = newBattle() + battle:volatile(wild).vanished = true + battle:volatile(wild).chargeMove = "FLY" + local missed, events = use(battle, player, wild, "SWIFT") + T.check(missed, "a FLY target still dodges SWIFT") + T.check(findText(events, "MACHOP's attack missed!"), "with CheckHit's .Miss") + T.eq(wild.hp, wild.maxHp, "and takes nothing") +end + +do + local battle, player, wild = newBattle() + battle:volatile(wild).vanished = true + battle:volatile(wild).chargeMove = "DIG" + T.check(use(battle, player, wild, "SWIFT"), "a DIG target dodges it too") +end + +do + local battle, player, wild = newBattle() + battle:volatile(wild).protect = true + local missed, events = use(battle, player, wild, "SWIFT") + T.check(missed, "PROTECT still turns SWIFT aside") + T.check(findText(events, "MACHOP protected itself!"), "with .Protect's line") + T.eq(wild.hp, wild.maxHp, "and takes nothing") +end + +do + local battle, player, wild = newBattle() + T.check(not use(battle, player, wild, "SWIFT"), + "with no stage moved at all SWIFT still hits") + T.check(not use(battle, player, wild, "TACKLE"), + "and a plain move only fails the 99 roll once a stage has moved") +end + +T.finish("gen2 always-hit bug 1272") diff --git a/tests/engine/gen2_pay_day_bug1212.lua b/tests/engine/gen2_pay_day_bug1212.lua new file mode 100644 index 00000000..dd5364bf --- /dev/null +++ b/tests/engine/gen2_pay_day_bug1212.lua @@ -0,0 +1,268 @@ +-- engine/battle/move_effects/pay_day.asm:13, engine/battle/core.asm:8014-8042 + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local Battle = require("src.battle.gen2.Battle") +local BattleState = require("src.ui.gen2.BattleState") +local Effects = require("src.battle.gen2.Effects") +local Mon = require("src.battle.gen2.Mon") +local Prize = require("src.battle.gen2.Prize") + +local LEVEL = 15 + +local TYPES = { + NORMAL = { id = "NORMAL", index = 0, category = "physical" }, +} + +local MOVES = { + TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL", + accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" }, + PAY_DAY = { id = "PAY_DAY", name = "PAY DAY", power = 40, type = "NORMAL", + accuracy = 100, pp = 20, effect = "EFFECT_PAY_DAY" }, +} + +local POKEMON = { + growthRates = { + GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0, + linear = 0, constant = 0 }, + }, + MEOWTH = { + id = "MEOWTH", index = 52, name = "MEOWTH", + baseStats = { hp = 40, attack = 45, defense = 35, speed = 90, + specialAttack = 40, specialDefense = 40 }, + types = { "NORMAL", "NORMAL" }, catchRate = 255, baseExp = 69, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 127, + levelMoves = { { level = 1, move = "PAY_DAY" } }, evolutions = {}, + }, + RATTATA = { + id = "RATTATA", index = 19, name = "RATTATA", + baseStats = { hp = 30, attack = 56, defense = 35, speed = 72, + specialAttack = 25, specialDefense = 35 }, + types = { "NORMAL", "NORMAL" }, catchRate = 255, baseExp = 51, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 127, + levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {}, + }, +} + +local DATA = { + pokemon = POKEMON, + moves = MOVES, + type_chart = { types = TYPES, matchups = {} }, + items = {}, +} + +local perfect = { attack = 15, defense = 15, speed = 15, special = 15 } +perfect.hp = Mon.hpDV(perfect) + +local function highRoll(n) return 99 % math.max(1, n or 1) end + +local function newSave() + return { player = { name = "GOLD", money = 1000, id = 4242 }, + mom = { savedMoney = 500 }, party = {} } +end + +local function newBattle(save) + local player = Mon.new(DATA, "MEOWTH", LEVEL, { dvs = perfect }) + player.moves = { + { id = "PAY_DAY", pp = 20, maxPp = 20 }, + { id = "TACKLE", pp = 35, maxPp = 35 }, + } + local wild = Mon.new(DATA, "RATTATA", LEVEL, { dvs = perfect }) + wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } + wild.maxHp = 999 + wild.hp = 999 + local battle = Battle.new({ data = DATA, party = { player }, wild = wild, + random = highRoll, save = save }) + return battle, player, wild +end + +local function eventWithText(events, text) + for _, event in ipairs(events or {}) do + if event.text == text then return event end + end + return nil +end + +local function findText(events, text) + return eventWithText(events, text) ~= nil +end + +do + local battle, player, wild = newBattle(newSave()) + battle:useMove(player, wild, "PAY_DAY") + T.eq(battle.payDay, 2 * LEVEL, "one connected hit is 2 x the user's level") + T.check(findText(battle:takeEvents(), "Coins scattered\neverywhere!"), + "CoinsScatteredText rides the hit") + battle:useMove(player, wild, "PAY_DAY") + T.eq(battle.payDay, 4 * LEVEL, "a second hit adds the same again") + battle:useMove(player, wild, "TACKLE") + T.eq(battle.payDay, 4 * LEVEL, "another move adds nothing") +end + +do + local battle, player, wild = newBattle(newSave()) + battle.stages.enemy.evasion = Effects.MAX_STAGE + battle:useMove(player, wild, "PAY_DAY") + T.eq(battle.payDay, nil, "a miss accumulates nothing") +end + +do + local battle, player, wild = newBattle(newSave()) + battle.amuletCoin = true + battle:useMove(player, wild, "PAY_DAY") + battle:useMove(player, wild, "PAY_DAY") + T.eq(battle.payDay, 4 * LEVEL, + "the Amulet Coin does not double the running counter") +end + +do + local battle, player, wild = newBattle(newSave()) + local enemy = battle.enemy + enemy.level = 40 + battle:useMove(enemy, player, "PAY_DAY") + T.eq(battle.payDay, 80, "an enemy's PAY DAY pays the player, at ITS level") + T.check(wild == battle.enemy, "the enemy is the wild mon") +end + +do + local save = newSave() + T.eq(Prize.payDay(save, 30, false), 30, "the payout returns what it paid") + T.eq(save.player.money, 1030, "and writes the wallet directly") + T.eq(save.mom.savedMoney, 500, "with no Mom split") +end + +do + local save = newSave() + T.eq(Prize.payDay(save, 30, true), 60, "the Amulet Coin doubles the total") + T.eq(save.player.money, 1060, "once, at payout") +end + +do + local save = newSave() + T.eq(Prize.payDay(save, 0, true), nil, "an empty counter pays nothing") + T.eq(Prize.payDay(save, nil, false), nil, "and neither does an unset one") + T.eq(save.player.money, 1000, "the wallet is untouched") + T.eq(Prize.payDay(nil, 30, false), nil, "a save-less battle pays nothing") +end + +do + local save = newSave() + save.player.money = Prize.MAX_MONEY - 10 + Prize.payDay(save, 500, true) + T.eq(save.player.money, Prize.MAX_MONEY, "AddBattleMoneyToAccount clamps") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + wild.hp = 0 + T.check(battle:resolveFaints(), "the enemy faint ends the battle") + T.eq(battle.outcome, "win", "on the win arm") + T.eq(save.player.money, 1000 + 2 * LEVEL, "which is where CheckPayDay runs") + T.eq(battle.payDay, nil, "and the counter is cleared") + local paid = eventWithText(battle:takeEvents(), "GOLD picked up ¥30!") + T.check(paid, "BattleText_PlayerPickedUpPayDayMoney") + T.eq(paid and paid.kind, "money", "on the same event kind the prize uses") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle.amuletCoin = true + battle:useMove(player, wild, "PAY_DAY") + wild.hp = 0 + battle:resolveFaints() + T.eq(save.player.money, 1000 + 4 * LEVEL, + "the Amulet Coin doubles once on the way out") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + player.hp = 0 + T.check(battle:resolveFaints(), "a whiteout ends the battle") + T.eq(battle.outcome, "lose", "on the lose arm") + T.eq(save.player.money, 1000, "which pays nothing") + T.eq(battle.payDay, 2 * LEVEL, "the counter is dropped, not banked") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + battle:endBattle("run") + T.eq(save.player.money, 1000, "running away pays nothing") +end + +local function newScreen(save, battle, opts) + opts = opts or {} + local pushed = {} + local screen = setmetatable({ + save = save, battle = battle, picHidden = {}, + contest = opts.contest, tutorial = opts.tutorial, + contestCaught = false, + push = function(self, event) pushed[#pushed + 1] = event end, + name = function(_, mon) return mon.species end, + hasPokedex = function() return false end, + currentBox = function() return 1 end, + contestCatch = function(self) self.contestCaught = true end, + }, { __index = BattleState }) + return screen, pushed +end + +local function caughtEnemy(battle) + local enemy = battle.enemy + enemy.hp = 1 + return enemy +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + local screen, pushed = newScreen(save, battle) + screen:pushCaught(caughtEnemy(battle), "POKE_BALL") + T.eq(save.player.money, 1000 + 2 * LEVEL, "a capture pays out") + T.eq(battle.payDay, nil, "and clears the counter") + T.check(findText(pushed, "GOLD picked up ¥30!"), "with the payout line") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + local screen = newScreen(save, battle, { contest = true }) + screen:pushCaught(caughtEnemy(battle), "PARK_BALL") + T.check(screen.contestCaught, "the contest arm still holds the mon") + T.eq(save.player.money, 1000 + 2 * LEVEL, "and a contest catch pays too") + T.eq(battle.payDay, nil, "clearing the counter with it") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + local screen = newScreen(save, battle, { tutorial = true }) + screen:pushCaught(caughtEnemy(battle), "POKE_BALL") + T.eq(save.player.money, 1000, "the catching tutorial pays nothing") + T.eq(battle.payDay, 2 * LEVEL, "and leaves the counter alone") +end + +do + local save = newSave() + local battle, player, wild = newBattle(save) + battle:useMove(player, wild, "PAY_DAY") + wild.hp = 0 + battle:resolveFaints() + local screen = newScreen(save, battle, { contest = true }) + screen:pushPayDay() + T.eq(save.player.money, 1000 + 2 * LEVEL, + "a contest KO that already paid does not pay a second time") +end + +T.finish("gen2 pay day bug 1212") diff --git a/tests/engine/gen2_substitute_doll_bug1271.lua b/tests/engine/gen2_substitute_doll_bug1271.lua new file mode 100644 index 00000000..eeb96c04 --- /dev/null +++ b/tests/engine/gen2_substitute_doll_bug1271.lua @@ -0,0 +1,147 @@ +-- engine/battle_anims/anim_commands.asm:905-960 GetSubstitutePic + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local AnimRunner = require("src.battle.gen2.AnimRunner") +local Assets = require("src.render.Assets") +local BattleState = require("src.ui.gen2.BattleState") + +local MONSTER = { getDimensions = function() return 16, 96 end } +Assets.image = function(path) + if path == "assets/generated/sprites/monster.png" then return MONSTER end + return nil +end + +local drawn +love.graphics.draw = function(image, a, b, c) + if type(a) == "table" then + drawn[#drawn + 1] = { image = image, quad = a, x = b, y = c } + else + drawn[#drawn + 1] = { image = image, x = a, y = b } + end +end + +do + local runner = { env = { battleTurn = 0 }, picOverride = {} } + AnimRunner.COMMANDS.raisesub(runner) + T.eq(runner.picOverride.player, "substitute", + "anim_raisesub puts the doll up on the acting side") + AnimRunner.COMMANDS.dropsub(runner) + T.eq(runner.picOverride.player, false, + "anim_dropsub writes false, not nil") + T.check(runner.picOverride.player ~= nil, + "so LowerSub reads as an override and not as `no animation running`") +end + +do + local runner = { env = { battleTurn = 1 }, picOverride = {} } + AnimRunner.COMMANDS.raisesub(runner) + T.eq(runner.picOverride.enemy, "substitute", "and the enemy side too") + T.eq(runner.picOverride.player, nil, "without touching the other one") +end + +local function newAnim(overrides) + return { + bg = { hidden = {}, picSize = {}, slide = {}, monShade = {} }, + picOverride = overrides or {}, + } +end + +local function newScreen(anim) + local image = { getDimensions = function() return 56, 56 end } + local back = { getDimensions = function() return 48, 48 end } + return setmetatable({ + picHidden = {}, anim = anim, + pic = function(_, _, isBack) return isBack and back or image end, + picScale = function() return 1 end, + faintSink = function() return 0 end, + }, { __index = BattleState }), image, back +end + +do + local screen = newScreen(newAnim({ enemy = "substitute" })) + T.eq(screen:animPicState("enemy").pic, "substitute", + "animPicState surfaces the runner's override") +end + +do + local screen, image = newScreen(newAnim({ enemy = "substitute" })) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = {} }, false) + T.eq(#drawn, 1, "one blit") + T.eq(drawn[1].image, MONSTER, "the doll, not the frontpic") + T.check(drawn[1].image ~= image, "the mon's own pic is not drawn") + T.eq(drawn[1].x, 112, "enemy doll x, GetSubstitutePic's cols 2-3") + T.eq(drawn[1].y, 40, "enemy doll y, rows 5-6 of the 7-tall box") + local quad = drawn[1].quad or {} + T.eq(quad.x, 0, "cut from the facing-DOWN frame") + T.eq(quad.y, 0, "at the top of monster.png") + T.eq(quad.w, 16, "16 wide") + T.eq(quad.h, 16, "16 tall") +end + +do + local screen = newScreen(newAnim({ player = "substitute" })) + drawn = {} + screen:drawPic({ species = "TYPHLOSION", volatile = {} }, true) + T.eq(#drawn, 1, "one blit") + T.eq(drawn[1].x, 32, "player doll x, cols 2-3 of the 6-wide box") + T.eq(drawn[1].y, 80, "player doll y, rows 4-5 of the 6-tall box") + T.eq((drawn[1].quad or {}).y, 16, "cut from the facing-UP frame") +end + +do + local anim = newAnim({ enemy = "substitute" }) + anim.bg.slide.enemy = 8 + local screen = newScreen(anim) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = {} }, false) + T.eq(drawn[1].x, 120, "REMOVE_MON / ENTER_MON still slide the doll") +end + +do + local screen, image = newScreen(nil) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = { substitute = 22 } }, false) + T.eq(drawn[1].image, MONSTER, "with no animation running the volatile wins") + T.check(drawn[1].image ~= image, "and the frontpic stays down") +end + +do + local screen, image = newScreen(nil) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = {} }, false) + T.eq(drawn[1].image, image, "no substitute, no doll") + T.eq(drawn[1].x, 96, "the frontpic sits in its own box") + T.eq(drawn[1].y, 0, "at hlcoord 12, 0") +end + +do + local screen, image = newScreen(newAnim({ enemy = false })) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = { substitute = 22 } }, false) + T.eq(drawn[1].image, image, + "a dropsub override beats the still-standing substitute volatile") + T.eq(drawn[1].x, 96, "and the mon is drawn in its own box") +end + +do + local screen = newScreen(newAnim({ enemy = nil })) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = { substitute = 22 } }, false) + T.eq(drawn[1].image, MONSTER, + "an animation that never touched the pic leaves the doll up") +end + +do + local screen, image = newScreen(newAnim({ enemy = "transform" })) + drawn = {} + screen:drawPic({ species = "GASTLY", volatile = { substitute = 22 } }, false) + T.eq(drawn[1].image, image, + "and any other override is not the doll either") +end + +T.finish("gen2 substitute doll bug 1271") diff --git a/tests/engine/gen2_type_chart_merge_bug1268.lua b/tests/engine/gen2_type_chart_merge_bug1268.lua new file mode 100644 index 00000000..4677786a --- /dev/null +++ b/tests/engine/gen2_type_chart_merge_bug1268.lua @@ -0,0 +1,215 @@ +-- data/types/type_matchups.asm:112-116, engine/battle/effect_commands.asm:1305-1313 + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local Game2 = require("src.core.Game2") +local Damage = require("src.battle.gen2.Damage") + +local TYPES = { + "NORMAL", "FIGHTING", "FLYING", "POISON", "GROUND", "ROCK", "BIRD", "BUG", + "GHOST", "STEEL", "CURSE_TYPE", "FIRE", "WATER", "GRASS", "ELECTRIC", + "PSYCHIC_TYPE", "ICE", "DRAGON", "DARK", +} + +local DEFAULT_ROWS = [[ +NORMAL ROCK 5 +NORMAL STEEL 5 +FIRE FIRE 5 +FIRE WATER 5 +FIRE GRASS 20 +FIRE ICE 20 +FIRE BUG 20 +FIRE ROCK 5 +FIRE DRAGON 5 +FIRE STEEL 20 +WATER FIRE 20 +WATER WATER 5 +WATER GRASS 5 +WATER GROUND 20 +WATER ROCK 20 +WATER DRAGON 5 +ELECTRIC WATER 20 +ELECTRIC ELECTRIC 5 +ELECTRIC GRASS 5 +ELECTRIC GROUND 0 +ELECTRIC FLYING 20 +ELECTRIC DRAGON 5 +GRASS FIRE 5 +GRASS WATER 20 +GRASS GRASS 5 +GRASS POISON 5 +GRASS GROUND 20 +GRASS FLYING 5 +GRASS BUG 5 +GRASS ROCK 20 +GRASS DRAGON 5 +GRASS STEEL 5 +ICE WATER 5 +ICE GRASS 20 +ICE ICE 5 +ICE GROUND 20 +ICE FLYING 20 +ICE DRAGON 20 +ICE STEEL 5 +ICE FIRE 5 +FIGHTING NORMAL 20 +FIGHTING ICE 20 +FIGHTING POISON 5 +FIGHTING FLYING 5 +FIGHTING PSYCHIC_TYPE 5 +FIGHTING BUG 5 +FIGHTING ROCK 20 +FIGHTING DARK 20 +FIGHTING STEEL 20 +POISON GRASS 20 +POISON POISON 5 +POISON GROUND 5 +POISON ROCK 5 +POISON GHOST 5 +POISON STEEL 0 +GROUND FIRE 20 +GROUND ELECTRIC 20 +GROUND GRASS 5 +GROUND POISON 20 +GROUND FLYING 0 +GROUND BUG 5 +GROUND ROCK 20 +GROUND STEEL 20 +FLYING ELECTRIC 5 +FLYING GRASS 20 +FLYING FIGHTING 20 +FLYING BUG 20 +FLYING ROCK 5 +FLYING STEEL 5 +PSYCHIC_TYPE FIGHTING 20 +PSYCHIC_TYPE POISON 20 +PSYCHIC_TYPE PSYCHIC_TYPE 5 +PSYCHIC_TYPE DARK 0 +PSYCHIC_TYPE STEEL 5 +BUG FIRE 5 +BUG GRASS 20 +BUG FIGHTING 5 +BUG POISON 5 +BUG FLYING 5 +BUG PSYCHIC_TYPE 20 +BUG GHOST 5 +BUG DARK 20 +BUG STEEL 5 +ROCK FIRE 20 +ROCK ICE 20 +ROCK FIGHTING 5 +ROCK GROUND 5 +ROCK FLYING 20 +ROCK BUG 20 +ROCK STEEL 5 +GHOST NORMAL 0 +GHOST PSYCHIC_TYPE 20 +GHOST DARK 5 +GHOST STEEL 5 +GHOST GHOST 20 +DRAGON DRAGON 20 +DRAGON STEEL 5 +DARK FIGHTING 5 +DARK PSYCHIC_TYPE 20 +DARK GHOST 20 +DARK DARK 5 +DARK STEEL 5 +STEEL FIRE 5 +STEEL WATER 5 +STEEL ELECTRIC 5 +STEEL ICE 20 +STEEL ROCK 20 +STEEL STEEL 5 +]] + +local FORESIGHT_ROWS = [[ +NORMAL GHOST 0 +FIGHTING GHOST 0 +]] + +local function parse(text) + local rows = {} + for attacker, defender, multiplier in + text:gmatch("(%u[%u_]*)%s+(%u[%u_]*)%s+(%d+)") do + rows[#rows + 1] = { attacker = attacker, defender = defender, + multiplier = tonumber(multiplier) } + end + return rows +end + +local function serialize(rows) + local parts = {} + for _, row in ipairs(rows) do + parts[#parts + 1] = ("{attacker=%q,defender=%q,multiplier=%d}") + :format(row.attacker, row.defender, row.multiplier) + end + return "{" .. table.concat(parts, ",") .. "}" +end + +local DEFAULT = parse(DEFAULT_ROWS) +local FORESIGHT = parse(FORESIGHT_ROWS) + +T.eq(#DEFAULT, 108, "the fixture carries the cart's 108 default rows") +T.eq(#FORESIGHT, 2, "and the two rows the `db -2` marker precedes") + +love.filesystem.write("data/generated/type_chart.lua", + ("return { generation = 2, matchups = %s, foresightMatchups = %s }") + :format(serialize(DEFAULT), serialize(FORESIGHT))) + +local function bootChart() + local game = setmetatable({ data = {} }, Game2) + game.applyOptions = function() end + pcall(Game2.load, game) + return game.data.type_chart +end + +local function resolve(matchups) + local out = {} + for _, attacker in ipairs(TYPES) do + for _, defender in ipairs(TYPES) do + out[attacker .. ">" .. defender] = + Damage.typeMultiplier(attacker, { defender }, matchups) + end + end + return out +end + +local chart = bootChart() +T.check(chart and chart.matchups, "Gold's chart reaches self.data.type_chart") + +local before = resolve(DEFAULT) +local after = resolve(chart.matchups) + +T.eq(before["NORMAL>GHOST"], 10, + "unmerged, a NORMAL move is neutral against a GHOST") +T.eq(before["FIGHTING>GHOST"], 10, + "unmerged, a FIGHTING move is neutral against a GHOST") + +T.eq(after["NORMAL>GHOST"], 0, "merged, NORMAL does nothing to GHOST") +T.eq(after["FIGHTING>GHOST"], 0, "merged, FIGHTING does nothing to GHOST") +T.eq(after["GHOST>NORMAL"], 0, "and GHOST still does nothing to NORMAL") + +T.eq(#chart.matchups, #DEFAULT + #FORESIGHT, + "the merge appends exactly the foresight rows") +T.eq(#(chart.foresightMatchups or {}), #FORESIGHT, + "and leaves foresightMatchups in place for Fingerprint") + +local moved = {} +for pair, value in pairs(after) do + if before[pair] ~= value then moved[#moved + 1] = pair end +end +table.sort(moved) +T.eq(#moved, 2, "exactly two of the 19x19 pairs move") +T.eq(moved[1], "FIGHTING>GHOST", "the first is FIGHTING against GHOST") +T.eq(moved[2], "NORMAL>GHOST", "the second is NORMAL against GHOST") + +local twice = bootChart() +T.eq(#twice.matchups, #DEFAULT + #FORESIGHT, + "a second load appends the rows once, not twice") +T.eq(Damage.typeMultiplier("NORMAL", { "GHOST", "POISON" }, twice.matchups), 0, + "a dual GHOST/POISON target is still immune to NORMAL") + +T.finish("gen2 type chart foresight merge bug 1268") diff --git a/tests/engine/museum_money_box_bug1335.lua b/tests/engine/museum_money_box_bug1335.lua new file mode 100644 index 00000000..8dfa1260 --- /dev/null +++ b/tests/engine/museum_money_box_bug1335.lua @@ -0,0 +1,81 @@ +-- The Pewter museum ticket clerk's money box (#1335): the closure re-reads +-- the balance, so the thank-you box shows 50 less than the ask did, and +-- the decline boxes keep the box up too (scripts/Museum1F.asm:71-112). +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +package.loaded["src.render.TextBox"] = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} + +local M = assert(loadfile("data/scripts/story2.lua"))() +local clerk = M.MUSEUM_1F.talk.TEXT_MUSEUM1F_SCIENTIST1 + +local pushed +local function mkGame(cash) + pushed = {} + return { + data = { text = {} }, + save = { money = cash, flags = {} }, + stack = { push = function(_, box) pushed[#pushed + 1] = box end }, + } +end + +-- YES with enough money: the prompt carries the money closure and reads +-- the pre-payment balance; the thank-you box re-reads the reduced one +local g = mkGame(3000) +local doneFired = false +clerk(g, nil, nil, function() doneFired = true end) +local ask = pushed[1] +T.check(ask.opts ~= nil and ask.opts.money ~= nil and ask.opts.choice ~= nil, + "the ask box carries both a money closure and the YES/NO choice") +T.eq(ask.opts.money(), 3000, "the ask box reads 3000 before paying") +ask.opts.choice(true) +local thanks = pushed[2] +T.check(tostring(thanks.text):find("Thank you", 1, true) ~= nil, + "YES opens the thank-you box") +T.check(thanks.opts ~= nil and thanks.opts.money ~= nil, + "the thank-you box carries a money closure") +T.eq(thanks.opts.money(), 2950, + "the thank-you box re-reads the balance, 50 less than the ask") +T.check(g.save.flags.EVENT_BOUGHT_MUSEUM_TICKET, "the ticket flag is set") +thanks.onDone() +T.check(doneFired, "the done callback still chains through") + +-- YES but short on cash: the decline box also carries the money opt now +-- (Part 1: scripts/Museum1F.asm keeps MONEY_BOX up on the whole exchange) +g = mkGame(20) +clerk(g, nil, nil, function() end) +pushed[1].opts.choice(true) +local broke = pushed[2] +T.check(tostring(broke.text):find("enough money", 1, true) ~= nil, + "short on cash opens the not-enough-money box") +T.check(broke.opts ~= nil and broke.opts.money ~= nil, + "the not-enough-money box carries the money opt") +T.eq(broke.opts.money(), 20, "and its closure reads the untouched balance") +T.eq(g.save.money, 20, "money is not spent on the broke path") + +-- NO: the come-again box keeps the money opt too +g = mkGame(3000) +clerk(g, nil, nil, function() end) +pushed[1].opts.choice(false) +local decline = pushed[2] +T.check(tostring(decline.text):find("Come again", 1, true) ~= nil, + "declining opens the come-again box") +T.check(decline.opts ~= nil and decline.opts.money ~= nil, + "the come-again box carries the money opt") +T.eq(decline.opts.money(), 3000, "and its closure reads the unspent balance") + +-- already ticketed: take-your-time only, no money box (a ticket holder +-- is already inside, past the rope) +g = mkGame(3000) +g.save.flags.EVENT_BOUGHT_MUSEUM_TICKET = true +clerk(g, nil, nil, function() end) +T.check(tostring(pushed[1].text):find("Take your time", 1, true) ~= nil, + "an existing ticket holder gets the take-your-time line") +T.check(pushed[1].opts == nil, "and no money box on that branch") + +T.finish("museum_money_box_bug1335") diff --git a/tests/engine/oaks_lab_posters_email_bug1331_1333.lua b/tests/engine/oaks_lab_posters_email_bug1331_1333.lua new file mode 100644 index 00000000..725c2d9f --- /dev/null +++ b/tests/engine/oaks_lab_posters_email_bug1331_1333.lua @@ -0,0 +1,83 @@ +-- Oak's lab poster/e-mail hidden events (#1331, #1333): the extractor's +-- hidden_event whitelist drops both, so the posters and PC e-mail were +-- dead A presses until the flavor script's onInteract hook. +-- engine/events/hidden_events/oaks_lab_posters.asm:1 +-- engine/events/hidden_events/oaks_lab_email.asm:1 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +package.loaded["src.render.TextBox"] = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} + +local M = assert(loadfile("data/scripts/flavor/oaks_lab.lua"))() +local hook = M.OAKS_LAB.onInteract +T.check(type(hook) == "function", "OAKS_LAB.onInteract exists") + +local pushed +local function mkGame(owned) + pushed = {} + return { + data = { text = { + _PushStartText = "PUSH", + _SaveOptionText = "SAVE", + _StrengthsAndWeaknessesText = "TYPES", + _OakLabEmailText = "EMAIL", + } }, + save = { pokedex = { owned = owned } }, + stack = { push = function(_, box) pushed[#pushed + 1] = box end }, + } +end +local owUp = { player = { facing = "up" } } +local owDown = { player = { facing = "down" } } + +-- left poster: unconditional, any facing (#1331) +local g = mkGame({}) +T.eq(hook(g, owDown, 4, 0), true, "left poster consumes the press") +T.eq(#pushed, 1, "left poster pushes one box") +T.eq(pushed[1].text, "PUSH", "left poster prints PushStartText") + +-- right poster, fewer than 2 species owned: SaveOptionText (#1331) +g = mkGame({ [1] = true }) +T.eq(hook(g, owDown, 5, 0), true, "right poster consumes the press (<2 owned)") +T.eq(pushed[1].text, "SAVE", "right poster prints SaveOptionText below 2 owned") + +-- right poster, 2+ species owned: StrengthsAndWeaknessesText (#1331) +g = mkGame({ [1] = true, [4] = true }) +T.eq(hook(g, owDown, 5, 0), true, "right poster consumes the press (2+ owned)") +T.eq(pushed[1].text, "TYPES", + "right poster prints StrengthsAndWeaknessesText at 2+ owned") + +-- right poster, exactly at the boundary (still <2, one species owned twice +-- under different keys does not double-count) +g = mkGame({ [1] = true }) +T.eq(hook(g, owDown, 5, 0), true, "right poster boundary case consumes") +T.eq(pushed[1].text, "SAVE", "one owned species stays on the SAVE line") + +-- e-mail tiles, facing up: OakLabEmailText (#1333) +g = mkGame({}) +T.eq(hook(g, owUp, 0, 1), true, "e-mail tile x=0 consumes facing up") +T.eq(pushed[1].text, "EMAIL", "e-mail tile x=0 prints OakLabEmailText") +g = mkGame({}) +T.eq(hook(g, owUp, 1, 1), true, "e-mail tile x=1 consumes facing up") +T.eq(pushed[1].text, "EMAIL", "e-mail tile x=1 prints OakLabEmailText") + +-- e-mail tiles, any other facing: explicit FALSE so the press falls +-- through to tryBookshelf, and nothing is pushed (#1333) +g = mkGame({}) +T.eq(hook(g, owDown, 0, 1), false, "e-mail tile x=0 returns false facing down") +T.eq(#pushed, 0, "wrong facing prints nothing") +g = mkGame({}) +T.eq(hook(g, { player = { facing = "left" } }, 1, 1), false, + "e-mail tile x=1 returns false facing left") +T.eq(#pushed, 0, "wrong facing prints nothing") + +-- unrelated cell: falls through, nothing printed +g = mkGame({}) +T.eq(hook(g, owUp, 3, 3), false, "an unrelated cell returns false") +T.eq(#pushed, 0, "an unrelated cell prints nothing") + +T.finish("oaks_lab_posters_email_bug1331_1333") diff --git a/tests/engine/oaks_lab_starter_energetic_bug1334.lua b/tests/engine/oaks_lab_starter_energetic_bug1334.lua new file mode 100644 index 00000000..2512d890 --- /dev/null +++ b/tests/engine/oaks_lab_starter_energetic_bug1334.lua @@ -0,0 +1,68 @@ +-- "This POKéMON is really energetic!" prints before the received-mon box +-- (#1334). starterBall's numeric jump targets moved down one row for the +-- new line, so this asserts the targets by ROW CONTENT, not by index, and +-- would catch a stale target the next time a row is inserted above them. +-- scripts/OaksLab.asm:919 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local M = assert(loadfile("data/scripts/oaks_lab.lua"))() +local rows = M.talk.TEXT_OAKSLAB_CHARMANDER_POKE_BALL +T.check(type(rows) == "table", "starterBall rows loaded") + +-- the energetic line lands between the ask and the sound/received pair +local askRow +for i, row in ipairs(rows) do + if row[1] == "ask" then askRow = i end +end +T.check(askRow ~= nil, "found the ask row") +T.eq(rows[askRow + 1][1], "jump_if_false", "the ask is followed by its jump") +T.same(rows[askRow + 2], { "show_text", "_OaksLabMonEnergeticText" }, + "the energetic line is the row right after the NO jump") +T.eq(rows[askRow + 3][1], "text_sound", + "the sound cue stays on the RECEIVED line, not the energetic one") +T.eq(rows[askRow + 4][1], "show_text", + "and the received-mon text follows the sound cue") +T.eq(rows[askRow + 4][2], "_OaksLabReceivedMonText", + "specifically the received-mon text") + +-- jump_if_true (row 2): the EVENT_GOT_STARTER short-circuit must land on +-- the leftover-ball beat, "face_object 5 down" -- asserted by content +local gotStarterJump +for i, row in ipairs(rows) do + if row[1] == "check_flag" and row[2] == "EVENT_GOT_STARTER" then + gotStarterJump = rows[i + 1] + break + end +end +T.check(gotStarterJump ~= nil and gotStarterJump[1] == "jump_if_true", + "found the EVENT_GOT_STARTER jump_if_true") +local target1 = gotStarterJump[2] +T.check(type(target1) == "number", "the target is a numeric row index") +T.same(rows[target1], { "face_object", 5, "down" }, + "jump_if_true lands on the leftover-ball face_object row") + +-- jump_if_false (row 4): the EVENT_FOLLOWED_OAK_INTO_LAB gate must land on +-- the pre-pick line, "show_text _OaksLabThoseArePokeBallsText" +local followedOakJump +for i, row in ipairs(rows) do + if row[1] == "check_flag" and row[2] == "EVENT_FOLLOWED_OAK_INTO_LAB" then + followedOakJump = rows[i + 1] + break + end +end +T.check(followedOakJump ~= nil and followedOakJump[1] == "jump_if_false", + "found the EVENT_FOLLOWED_OAK_INTO_LAB jump_if_false") +local target2 = followedOakJump[2] +T.check(type(target2) == "number", "the target is a numeric row index") +T.same(rows[target2], { "show_text", "_OaksLabThoseArePokeBallsText" }, + "jump_if_false lands on the pre-pick ThoseArePokeBalls row") + +-- the leftover-ball beat this jump lands on must never fall through into +-- the pre-pick line beneath it (#601 remnant); confirm a terminating jump +-- sits between them +T.eq(rows[target1 + 2][1], "jump", + "the leftover-ball beat ends on its own jump before the pre-pick row") + +T.finish("oaks_lab_starter_energetic_bug1334") diff --git a/tests/engine/oaks_pc_flow.lua b/tests/engine/oaks_pc_flow.lua index c786b9f4..b736b213 100644 --- a/tests/engine/oaks_pc_flow.lua +++ b/tests/engine/oaks_pc_flow.lua @@ -76,6 +76,8 @@ for _, name in ipairs({ "openOaksPC", "dexRating" }) do ("Game upvalue on %s"):format(name)) end T.check(setUpvalue(OW.openPC, "Game", fakeGame), "Game upvalue on openPC") +T.check(setUpvalue(OW.openPC, "TextBox", textBoxStub), + "TextBox upvalue on openPC") local fakeSelf = setmetatable({}, { __index = OW }) @@ -111,8 +113,14 @@ end reset() local done = false fakeSelf:openPC(function() done = true end) +-- engine/menus/pc.asm:5 +local pcOn = lastPush() +T.eq(pcOn.kind, "text", "openPC opens with the turned-on text") +T.check(tostring(pcOn.text):find("turned on", 1, true) ~= nil, + "the opening box is TurnedOnPC1Text") +pcOn.onDone() local menu = lastPush() -T.eq(menu.kind, "menu", "openPC pushes the PC menu") +T.eq(menu.kind, "menu", "the PC menu follows the box") local oak for _, item in ipairs(menu.items) do if item.label == "PROF.OAK's PC" then oak = item end @@ -121,16 +129,16 @@ T.check(oak ~= nil, "PROF.OAK's PC is offered once the Pokédex is had") oak.onSelect() -- drop the menu's Turn_On_PC and the row's Enter_PC plays = {} -T.eq(pushed[2].kind, "text", "selection opens the access text") -T.check(tostring(pushed[2].text):find("Accessed", 1, true) ~= nil, +T.eq(pushed[3].kind, "text", "selection opens the access text") +T.check(tostring(pushed[3].text):find("Accessed", 1, true) ~= nil, "first session box is the access text") runChain() T.check(done, "session completes") -T.check(pushed[3].opts ~= nil and pushed[3].opts.choice ~= nil, +T.check(pushed[4].opts ~= nil and pushed[4].opts.choice ~= nil, "the rated question carries the YES/NO choice") -T.check(tostring(pushed[3].text):find("rated", 1, true) ~= nil, +T.check(tostring(pushed[4].text):find("rated", 1, true) ~= nil, "second session box asks for the rating") -local ratingBox = pushed[4] +local ratingBox = pushed[5] T.check(ratingBox.opts and ratingBox.opts.auto ~= nil and ratingBox.opts.auto.wait ~= nil, "rating box sounds the jingle then waits for a button") @@ -142,7 +150,7 @@ T.eq(#plays, 0, "no jingle while the evaluation is printing") ratingBox.opts.auto.sound() T.eq(#plays, 1, "jingle fires once the rating text is printed") T.eq(plays[1], "Pokedex_Rating", "jingle is the Pokedex_Rating fanfare") -T.check(tostring(pushed[5].text):find("Closed link", 1, true) ~= nil, +T.check(tostring(pushed[6].text):find("Closed link", 1, true) ~= nil, "the closing link prints at the end of the session") -- === declining the rating skips the evaluation but still closes the link diff --git a/tests/engine/residual_latch_bug1181.lua b/tests/engine/residual_latch_bug1181.lua new file mode 100644 index 00000000..dfbb6baa --- /dev/null +++ b/tests/engine/residual_latch_bug1181.lua @@ -0,0 +1,174 @@ +-- HandlePoisonBurnLeechSeed runs once per side per round; a mod that +-- reads battle.ruleset.residualAfterMove twice (executeAction:3487 and +-- endOfTurn:2657) could otherwise fire both arms. #1181's b.residualDone +-- latch (residualFor ~2612, endOfTurn ~2670, cleared ~2685) closes that. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +local Font = require("src.render.Font") +Font.load(Data) +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local TypeChart = require("src.battle.TypeChart") +TypeChart.load(Data) + +local function newBattle(opts) + opts = opts or {} + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 30), Pokemon.new(Data, "FIXMON_A", 28) } + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + local battle = BattleState.newWild(game, "FIXMON_C", 30) + battle.rng = function() return 0 end + battle.enemyAction = function() return { id = "FIX_SCRATCH", pp = 35 } end + if opts.playerFaster then + battle.player.curStats.speed = 200 + battle.enemy.curStats.speed = 1 + else + battle.enemy.curStats.speed = 200 + battle.player.curStats.speed = 1 + end + return battle +end + +local function drain(battle) + local rows = {} + for _ = 1, 800 do + local item = table.remove(battle.queue, 1) + if not item then return rows end + if item.text then rows[#rows + 1] = { text = item.text } end + if item.fn then + battle.nextInsert = 0 + item.fn() + end + end + error("the turn queue never drained") +end + +local function countTicks(rows) + local player, enemy = 0, 0 + for _, r in ipairs(rows) do + if r.text and r.text:find("hurt by poison", 1, true) then + if r.text:find("Enemy", 1, true) then enemy = enemy + 1 + else player = player + 1 end + end + end + return player, enemy +end + +local function assertCleared(b, label) + T.check(b.player.residualDone == nil, label .. ": player residualDone cleared") + T.check(b.enemy.residualDone == nil, label .. ": enemy residualDone cleared") +end + +do + local b = newBattle({ playerFaster = true }) + b.player.mon.status = "PSN" + b:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, "faster attacker: poison ticks exactly once") + T.eq(et, 0, "faster attacker: no enemy tick") + assertCleared(b, "faster attacker") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, "slower attacker: poison ticks exactly once") + T.eq(et, 0, "slower attacker: no enemy tick") + assertCleared(b, "slower attacker") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b:itemUsed({}) + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, "item round: poison ticks exactly once") + T.eq(et, 0, "item round: no enemy tick") + assertCleared(b, "item round") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b.catchAttempt = function() return false, 0 end + local ballId + for id in pairs(Data.items or {}) do + if id:find("BALL") then ballId = id break end + end + if not ballId then + Data.items = Data.items or {} + Data.items.POKE_BALL = { name = "POKE BALL" } + ballId = "POKE_BALL" + end + b:throwBall(ballId) + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, "ball round: poison ticks exactly once") + T.eq(et, 0, "ball round: no enemy tick") + assertCleared(b, "ball round") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b.runRoll = function() return false end + b:tryRun() + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, "failed run: poison ticks exactly once") + T.eq(et, 0, "failed run: no enemy tick") + assertCleared(b, "failed run") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b:resolveSwitch(b.game.save.party[2]) + local pt, et = countTicks(drain(b)) + T.eq(pt, 0, "switch round: a fresh battler ticks zero") + T.eq(et, 0, "switch round: still no enemy tick") + assertCleared(b, "switch round") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b.enemy.mon.status = "PSN" + local php0, ehp0 = b.player.mon.hp, b.enemy.mon.hp + b:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local rows = drain(b) + local pt, et = countTicks(rows) + T.eq(pt, 1, "both sides poisoned: player side ticks once") + T.eq(et, 1, "both sides poisoned: enemy side ticks once too") + T.check(b.player.mon.hp < php0, "both sides poisoned: player actually lost HP") + T.check(b.enemy.mon.hp < ehp0, "both sides poisoned: enemy actually lost HP") + assertCleared(b, "both sides poisoned") +end + +do + local b = newBattle({ playerFaster = false }) + b.player.mon.status = "PSN" + b.player.mon.hp = 999 + b.player.mon.stats.hp = 999 + local lastLoss + for round = 1, 5 do + local hp0 = b.player.mon.hp + b:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local pt, et = countTicks(drain(b)) + T.eq(pt, 1, ("round %d of 5: poison ticks exactly once"):format(round)) + T.eq(et, 0, ("round %d of 5: no enemy tick"):format(round)) + assertCleared(b, ("round %d of 5"):format(round)) + local loss = hp0 - b.player.mon.hp + T.check(loss > 0, ("round %d of 5: HP actually dropped"):format(round)) + if lastLoss then + T.eq(loss, lastLoss, ("round %d of 5: same tick size as the round before"):format(round)) + end + lastLoss = loss + end +end + +T.finish("residual latch (#1181)") diff --git a/tests/engine/town_map_bookshelf_bug1330.lua b/tests/engine/town_map_bookshelf_bug1330.lua new file mode 100644 index 00000000..211042fe --- /dev/null +++ b/tests/engine/town_map_bookshelf_bug1330.lua @@ -0,0 +1,68 @@ +-- The wall TOWN MAP prints TownMapText before the map screen opens (#1330). +-- engine/events/hidden_events/town_map.asm:1 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +local screenPushes = {} +package.loaded["src.ui.Screens"] = { + push = function(_, id, opts) + screenPushes[#screenPushes + 1] = { id = id, opts = opts } + end, +} + +local OW = require("src.world.OverworldController") + +local function setUpvalue(fn, name, val) + local i = 1 + while true do + local n = debug.getupvalue(fn, i) + if not n then return false end + if n == name then debug.setupvalue(fn, i, val); return true end + i = i + 1 + end +end + +local pushed +local textBoxStub = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} +local fakeGame = { + data = { text = { _TownMapText = "A TOWN MAP." } }, + stack = { push = function(_, box) pushed = box end }, +} +T.check(setUpvalue(OW.tryBookshelf, "TextBox", textBoxStub), + "TextBox upvalue on tryBookshelf") +T.check(setUpvalue(OW.tryBookshelf, "Game", fakeGame), + "Game upvalue on tryBookshelf") + +local map = { + def = { tileset = "HOUSE" }, + inBounds = function() return true end, + cellTile = function() return 0x3D end, +} +local fakeSelf = setmetatable({ player = { facing = "up" }, map = map }, + { __index = OW }) + +pushed, screenPushes = nil, {} +local result = fakeSelf:tryBookshelf(5, 2) +T.eq(result, true, "tryBookshelf consumes the wall TOWN MAP tile") +T.check(pushed ~= nil, "a text box was pushed") +T.eq(pushed.text, "A TOWN MAP.", "the box carries TownMapText") +T.eq(#screenPushes, 0, "the TownMap screen has not opened yet") +T.check(type(pushed.onDone) == "function", "the box has an onDone") + +pushed.onDone() +T.eq(#screenPushes, 1, "onDone is what opens the screen") +T.eq(screenPushes[1].id, "TownMap", "the pushed screen id is TownMap") + +-- a screen id a mod removed must not crash the interaction +pushed, screenPushes = nil, {} +package.loaded["src.ui.Screens"].push = function() error("no such screen") end +fakeSelf:tryBookshelf(5, 2) +local ok = pcall(pushed.onDone) +T.check(ok, "Screens.push stays wrapped in pcall") + +T.finish("town_map_bookshelf_bug1330") diff --git a/tests/gen2_callasm_test.lua b/tests/gen2_callasm_test.lua index 66d79f96..04e13eaa 100644 --- a/tests/gen2_callasm_test.lua +++ b/tests/gen2_callasm_test.lua @@ -257,6 +257,30 @@ do eq(set[173], true, "the hidden item's flag is set by number") end +-- engine/events/poisonstep.asm .PlayPoisonSFX: SFX_POISON then +-- LoadPoisonBGPals, four frames (#1362) +do + local World = require("src.world.gen2.World") + local ctx = { poisonBGFlash = World.poisonBGFlash, + playSfxNamed = function() end } + eq(CallAsm.run(ctx, "PlayPoisonSFX"), nil, "PlayPoisonSFX writes no wScriptVar") + eq(ctx.poisonFlash, 4, "and sets the flash to exactly four frames") + + local hurt = { game = { save = { party = { { hp = 5, status = "psn" } }, + poisonStepCount = 3 } }, + poisonBGFlash = World.poisonBGFlash, playSfxNamed = function() end, + stepContext = function() return { linkMode = false } end } + World.countStep(hurt) + eq(hurt.poisonFlash, 4, "the hurt arm reaches it too") + + local faint = { poisonBGFlash = World.poisonBGFlash, playSfxNamed = function() end } + World.poisonFaintScript(faint, { fainted = {}, whiteout = false }) + eq(faint.poisonFlash, 4, "and so does the faint arm") + + local ok = pcall(CallAsm.run, {}, "PlayPoisonSFX") + check(ok, "a ctx with no poisonBGFlash does not error") +end + -- engine/events/poisonstep.asm .CheckWhitedOut ends on -- CheckPlayerPartyForFitMon, whose answer is 1 when something can still fight; -- the `iffalse .whiteout` after it is reading "no fit mon". diff --git a/tests/gen2_field_items_test.lua b/tests/gen2_field_items_test.lua index f7589f06..c3f9ab68 100644 --- a/tests/gen2_field_items_test.lua +++ b/tests/gen2_field_items_test.lua @@ -270,6 +270,60 @@ do "MAX_LEVEL refuses (cp MAX_LEVEL / jp nc, NoEffectMessage)") end +-- ------------------------------------------------- ItemEffects: partyAction + +do + for _, itemId in ipairs({ "HP_UP", "PROTEIN", "IRON", "CARBOS", "CALCIUM", + "PP_UP" }) do + check(ItemEffects.partyAction(itemId, DATA) ~= nil, + itemId .. " resolves a party action (#1249)") + end + eq(ItemEffects.partyAction("ZINC", DATA), nil, + "ZINC does not exist in Gen 1 or Gen 2") +end + +-- ------------------------------------------------- ItemEffects: VITAMIN + +do + local mon = fixtureMon(12) + local before = { hp = mon.hp, maxHp = mon.maxHp } + local result = ItemEffects.useOnMon("HP_UP", mon, DATA) + eq(result.used, true, "an HP UP under the ceiling is spent") + eq(mon.statExp.hp, 2560, "StatExpItemPointerOffsets' +10 high-byte add") + check(mon.maxHp > before.maxHp, "UpdateStatsAfterItem recomputes max HP") + eq(mon.stats.hp, mon.maxHp, "the recomputed hp stat lands on stats too") + eq(mon.hp, before.hp, "current HP is UNCHANGED by HP UP") + eq(result.text, "CYNDAQUIL's\nHEALTH rose.", "with the cart's stat line") + + local ceiling = fixtureMon(12, { statExp = { + hp = 25600, attack = 0, defense = 0, speed = 0, special = 0 } }) + local refused = ItemEffects.useOnMon("HP_UP", ceiling, DATA) + eq(refused.used, false, "25600 stat exp refuses (cp 100 / jr nc)") + eq(ceiling.statExp.hp, 25600, "and leaves the word untouched") + eq(refused.text, ItemEffects.TEXT_NO_EFFECT, "with NoEffectMessage") + + local edge = fixtureMon(12, { statExp = { + hp = 25599, attack = 0, defense = 0, speed = 0, special = 0 } }) + local accepted = ItemEffects.useOnMon("HP_UP", edge, DATA) + eq(accepted.used, true, "25599 is still under the ceiling") + eq(edge.statExp.hp, 28159, "landing on 25599 + 2560") + + local labels = { + HP_UP = { stat = "hp", label = "HEALTH" }, + PROTEIN = { stat = "attack", label = "ATTACK" }, + IRON = { stat = "defense", label = "DEFENSE" }, + CARBOS = { stat = "speed", label = "SPEED" }, + CALCIUM = { stat = "special", label = "SPECIAL" }, + } + for itemId, row in pairs(labels) do + local target = fixtureMon(12) + local outcome = ItemEffects.useOnMon(itemId, target, DATA) + eq(target.statExp[row.stat], 2560, itemId .. " raises its own stat exp") + eq(outcome.text, ("CYNDAQUIL's\n%s rose."):format(row.label), + itemId .. " prints " .. row.label) + end +end + -- ------------------------------------------------- ItemEffects: PP family do @@ -305,6 +359,41 @@ do "a party mon with every slot full refuses the ELIXER family") end +-- ------------------------------------------------- ItemEffects: PP UP + +do + local mon = fixtureMon(12) + for use = 1, 3 do + local result = ItemEffects.usePpItem("PP_UP", mon, 1, DATA) + eq(result.used, true, "PP UP use " .. use .. " of 3 is spent") + eq(mon.moves[1].ppUps, use, "PP_UP_ONE ticks the top two bits") + eq(mon.moves[1].maxPp, 35 + use * 7, + "min(floor(basePP / 5), 7) added per use") + eq(mon.moves[1].pp, 30 + use * 7, "current PP rises by the same amount") + end + local capped = ItemEffects.usePpItem("PP_UP", mon, 1, DATA) + eq(capped.used, false, "a 4th PP UP refuses (PP_UP_MASK's 3-use cap)") + eq(mon.moves[1].maxPp, 56, "and leaves max PP where it was") + eq(capped.text, "TACKLE's PP\nis maxed out.", "with PPIsMaxedOutText") + + local sketch = fixtureMon(12) + sketch.moves[1] = { id = "SKETCH", pp = 1, maxPp = 1 } + local sketchResult = ItemEffects.usePpItem("PP_UP", sketch, 1, DATA) + eq(sketchResult.used, false, "SKETCH refuses a PP UP") + eq(sketchResult.text, "SKETCH's PP\nis maxed out.", + "with the same maxed-out line") + + local modMon = fixtureMon(12) + modMon.moves[1] = { id = "MOD_MOVE", pp = 5 } + local unknown = ItemEffects.usePpItem("PP_UP", modMon, 1, DATA) + eq(unknown.used, false, + "an id absent from data.moves with no maxPp refuses (base PP unknown)") + eq(modMon.moves[1].ppUps, nil, "and is not consumed") + eq(modMon.moves[1].pp, 5, "with current PP untouched") + eq(unknown.text, "MOD_MOVE's PP\nis maxed out.", + "printing the same maxed-out line rather than PP 0") +end + -- ------------------------------------------------ Game2: the .Party wiring -- The REAL flow: usePartyItem pushes the real Gen2PartyMenu, the pick runs -- the effect, the consumption happens, and the message rides a real TextBox @@ -360,6 +449,29 @@ do check(box ~= nil and box.pages ~= nil, "and prints the no-effect line") end +do + local mon = fixtureMon(12) + local host = newHost({ HP_UP = 1 }, { mon }) + host:useFieldItem("HP_UP") + local party = host.stack:top() + check(party ~= nil and party.prompt ~= nil, + "USE pushes the party list (partyAction is no longer nil)") + drive(host, function() return host.stack:top() ~= party end) + eq(mon.statExp.hp, 2560, "the vitamin ran through the real menu") + eq(host.save.inventory.HP_UP, nil, "and the HP UP was spent") +end + +do + local mon = fixtureMon(12, { statExp = { + hp = 25600, attack = 0, defense = 0, speed = 0, special = 0 } }) + local host = newHost({ HP_UP = 2 }, { mon }) + host:useFieldItem("HP_UP") + local party = host.stack:top() + drive(host, function() return host.stack:top() ~= party end) + eq(host.save.inventory.HP_UP, 2, "a refused HP UP costs nothing") + eq(mon.statExp.hp, 25600, "and leaves the stat exp word untouched") +end + do -- ETHER: party pick, then the move list, then the restore. local mon = fixtureMon(12) diff --git a/tests/gen2_menus_test.lua b/tests/gen2_menus_test.lua index a6bc6507..71fe05f2 100644 --- a/tests/gen2_menus_test.lua +++ b/tests/gen2_menus_test.lua @@ -356,11 +356,9 @@ local optionsGame, optionsInput = newGame(Save.newGame()) local options = OptionsMenu.new(optionsGame, { options = Save.defaultOptions(), }) --- The cart's seven value rows, then CONTROLS, the port's audio, speed and --- display rows, the three touch rows and CANCEL -- which is what makes this --- screen scroll. The touch three are gated to mobile by buildRows; ROWS --- itself carries every descriptor. -check("twenty rows", #OptionsMenu.ROWS, 20) +-- The cart's seven rows, then the port's: CONTROLS, audio, speed, display, +-- video mode, the mobile-gated touch three (buildRows) and CANCEL. +check("twenty-one rows", #OptionsMenu.ROWS, 21) check("the cart's rows come first", OptionsMenu.ROWS[7].key, "frame") check("then the rebind screen", OptionsMenu.ROWS[8].id, "controls") check("then the port's audio group", OptionsMenu.ROWS[9].key, "musicVol") @@ -863,6 +861,52 @@ check("GBC leaves a palette alone", 1)[1], 1) check("and has no present pass", GbcPalette.presentColors(), nil) +local gbcfxIndex +for i, row in ipairs(OptionsMenu.ROWS) do + if row.label == "GBC FX" then gbcfxIndex = i end +end +check("VIDEO MODE follows GBC FX", OptionsMenu.ROWS[gbcfxIndex + 1].label, + "VIDEO MODE") +check("and TOUCH PAD follows it", OptionsMenu.ROWS[gbcfxIndex + 2].label, + "TOUCH PAD") + +local videoRow = select(2, rowNamed("VIDEO MODE")) +check("VIDEO MODE is a row", videoRow ~= nil, true) + +local videoBuilt +for _, row in ipairs(scrollOptions.rows) do + if row.key == "videoMode" then videoBuilt = row end +end +check("buildRows gives it id videoMode", videoBuilt and videoBuilt.id, + "videoMode") + +check("default video mode is windowed", Save.DEFAULT_OPTIONS.videoMode, + "windowed") + +scrollOptions.options.videoMode = "windowed" +scrollOptions:cycle(videoRow, 1) +check("right stores borderless, not the display string", + scrollOptions.options.videoMode, "borderless") +scrollOptions:cycle(videoRow, 1) +check("right again stores windowed", + scrollOptions.options.videoMode, "windowed") +scrollOptions:cycle(videoRow, -1) +check("left also toggles, stored as borderless", + scrollOptions.options.videoMode, "borderless") +scrollOptions:cycle(videoRow, -1) +check("left toggles back to windowed", + scrollOptions.options.videoMode, "windowed") + +check("text reads WINDOWED", videoRow.text(scrollOptions.options), "WINDOWED") +scrollOptions.options.videoMode = "borderless" +check("text reads FULL, not BORDERLESS", videoRow.text(scrollOptions.options), + "FULL") +for _, mode in ipairs({ "windowed", "borderless" }) do + scrollOptions.options.videoMode = mode + local text = videoRow.text(scrollOptions.options) + check("value fits the 8-char column: " .. mode, #text <= 8, true) +end + -- ------------------------------------------------------------- name picker -- -- NameMenuHeader's coordinates, which is the whole point of this screen: the diff --git a/tests/parity_hpcolor_og_palette.lua b/tests/parity_hpcolor_og_palette.lua new file mode 100644 index 00000000..7dc9d72a --- /dev/null +++ b/tests/parity_hpcolor_og_palette.lua @@ -0,0 +1,253 @@ +-- Regression coverage for #1345 (BattleState:sgbBattlePals bar bypassing +-- PaletteFX.pal), #1346 (SummaryMenu double-tinting the HP fill) and #1340 +-- (animSpriteColors hardcoding the SGB anim model). engine/gfx/palettes.asm +-- SetPal_Battle, engine/pokemon/status_screen.asm:120-125, +-- engine/battle/animations.asm:551-578. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local S = require("tests.harness").suite("parity hpcolor og palette") +local check, eq, same = S.check, S.eq, S.same + +local Data = require("src.core.Data") +if not Data.maps then Data:load() end +local Font = require("src.render.Font") +Font.load(Data) + +local PaletteFX = require("src.render.PaletteFX") +local GameVersion = require("src.core.GameVersion") +local BattleState = require("src.battle.BattleState") +local HudTiles = require("src.render.HudTiles") +local SummaryMenu = require("src.ui.SummaryMenu") +local Pokemon = require("src.pokemon.Pokemon") +local Sound = require("src.core.Sound") + +Sound.playCry = function() end + +local prevVersion, prevMode = GameVersion.get(), PaletteFX.mode + +local function setup(version, mode) + GameVersion.set(version) + PaletteFX.setMode(mode) +end + +local function colorEq(got, want, msg) + local ok = got and want and got[1] == want[1] and got[2] == want[2] + and got[3] == want[3] + return check(ok, ("%s (got %s, want %s)"):format(msg, + got and ("(%d,%d,%d)"):format(got[1], got[2], got[3]) or "nil", + want and ("(%d,%d,%d)"):format(want[1], want[2], want[3]) or "nil")) +end + +local function bucket(r) + if r > 0.83 then return 1 elseif r > 0.5 then return 2 + elseif r > 0.17 then return 3 else return 4 end +end + +-- ------------------------------------------------------- #1345 bar() routes +-- through PaletteFX.pal instead of the raw SGB pack +local ZONE0 = { + { "red", "gbc", { 255, 239, 255 }, { 247, 214, 123 }, { 74, 165, 90 }, { 25, 16, 16 } }, + { "blue", "gbc", { 255, 239, 255 }, { 247, 214, 123 }, { 74, 165, 90 }, { 25, 16, 16 } }, + { "yellow", "gbc", { 255, 239, 255 }, { 247, 214, 123 }, { 74, 165, 90 }, { 25, 16, 16 } }, + { "red", "ogred", { 255, 255, 255 }, { 255, 132, 132 }, { 148, 58, 58 }, { 0, 0, 0 } }, + { "blue", "ogred", { 255, 255, 255 }, { 99, 165, 255 }, { 0, 0, 255 }, { 0, 0, 0 } }, + { "yellow", "ogred", { 255, 255, 255 }, { 255, 255, 0 }, { 0, 255, 0 }, { 25, 25, 25 } }, +} + +local function fullHpBattle() + return { + data = Data, + player = { mon = { hp = 20, stats = { hp = 20 } } }, + enemy = { mon = { hp = 20, stats = { hp = 20 } } }, + } +end + +for _, c in ipairs(ZONE0) do + local version, mode = c[1], c[2] + setup(version, mode) + local pals = BattleState.sgbBattlePals(fullHpBattle()) + check(pals ~= nil, ("%s/%s sgbBattlePals returns four zones"):format(version, mode)) + local zone0 = pals and pals[0] + colorEq(zone0 and zone0[1], c[3], ("%s/%s zone0 color0"):format(version, mode)) + colorEq(zone0 and zone0[2], c[4], ("%s/%s zone0 color1 (GetHealthBarColor)"):format(version, mode)) + colorEq(zone0 and zone0[3], c[5], ("%s/%s zone0 color2 (bar fill)"):format(version, mode)) + colorEq(zone0 and zone0[4], c[6], ("%s/%s zone0 color3"):format(version, mode)) +end + +-- --------------------------------------------- #1345 placeholder branch lock +-- OG RED / OG BLUE: zones 2/3 must land on the version's boot-ROM BG +-- endpoints regardless of placeholder, never the raw SGB pack's paper/ink. +for _, version in ipairs({ "red", "blue" }) do + setup(version, "ogred") + local white = PaletteFX.ogBg()[1] + local black = PaletteFX.ogBg()[4] + for _, ph in ipairs({ false, true }) do + local fake = fullHpBattle() + fake.showPlayerBack, fake.showEnemyTrainer = ph, ph + local pals = BattleState.sgbBattlePals(fake) + for _, z in ipairs({ 2, 3 }) do + local zone = pals[z] + colorEq(zone[1], white, ("%s/ogred placeholder=%s zone%d color0 is boot-ROM white") + :format(version, tostring(ph), z)) + colorEq(zone[4], black, ("%s/ogred placeholder=%s zone%d color3 is boot-ROM black") + :format(version, tostring(ph), z)) + check(not (zone[1][1] == 255 and zone[1][2] == 239 and zone[1][3] == 255), + ("%s/ogred placeholder=%s zone%d color0 is not the raw SGB paper") + :format(version, tostring(ph), z)) + check(not (zone[4][1] == 25 and zone[4][2] == 16 and zone[4][3] == 16), + ("%s/ogred placeholder=%s zone%d color3 is not the raw SGB ink") + :format(version, tostring(ph), z)) + end + end + setup(version, "ogred") + local off = BattleState.sgbBattlePals(fullHpBattle()) + local fakeOn = fullHpBattle() + fakeOn.showPlayerBack, fakeOn.showEnemyTrainer = true, true + local on = BattleState.sgbBattlePals(fakeOn) + for _, z in ipairs({ 2, 3 }) do + same(off[z], on[z], ("%s/ogred zone%d is byte-identical placeholder or not") + :format(version, z)) + end +end + +-- Yellow keeps the raw MEWMON pack in both gbc and ogred (usesYellowCgb +-- guards both call sites), so placeholder must be byte-identical across modes. +do + setup("yellow", "gbc") + local fakeGbc = fullHpBattle() + fakeGbc.showPlayerBack, fakeGbc.showEnemyTrainer = true, true + local gbcPals = BattleState.sgbBattlePals(fakeGbc) + + setup("yellow", "ogred") + local fakeOgred = fullHpBattle() + fakeOgred.showPlayerBack, fakeOgred.showEnemyTrainer = true, true + local ogredPals = BattleState.sgbBattlePals(fakeOgred) + + for _, z in ipairs({ 2, 3 }) do + same(ogredPals[z], gbcPals[z], + ("yellow/ogred placeholder zone%d is byte-identical to yellow/gbc"):format(z)) + end +end + +-- ------------------------------------------------------- #1345 blackout lock +-- OG RED / OG BLUE blacked-out zones must be the boot-ROM white/black, not +-- the raw PAL_BLACK pack. Yellow keeps the raw pack in both modes. +for _, version in ipairs({ "red", "blue" }) do + setup(version, "ogred") + local white = PaletteFX.ogBg()[1] + local black = PaletteFX.ogBg()[4] + local fake = fullHpBattle() + fake.blackedOut = true + local pals = BattleState.sgbBattlePals(fake) + check(pals ~= nil, ("%s/ogred blackout still returns four zones"):format(version)) + for z = 0, 3 do + colorEq(pals[z][1], white, ("%s/ogred blackout zone%d color0 is boot-ROM white"):format(version, z)) + colorEq(pals[z][4], black, ("%s/ogred blackout zone%d color3 is boot-ROM black"):format(version, z)) + check(not (pals[z][1][1] == 255 and pals[z][1][2] == 239 and pals[z][1][3] == 255), + ("%s/ogred blackout zone%d color0 is not the raw PAL_BLACK paper"):format(version, z)) + end +end + +do + setup("yellow", "gbc") + local gbcOut = BattleState.sgbBattlePals({ data = Data, blackedOut = true, + player = { mon = {} }, enemy = { mon = {} } }) + setup("yellow", "ogred") + local ogredOut = BattleState.sgbBattlePals({ data = Data, blackedOut = true, + player = { mon = {} }, enemy = { mon = {} } }) + same(ogredOut[0], gbcOut[0], "yellow/ogred blackout is byte-identical to yellow/gbc") +end + +-- ------------------------------------------------------- #1346 grayFill guard +-- SummaryMenu:draw must pass grayFill behind the same barZoned guard +-- PartyMenu already used, or the fill double-applies through the zone pass. +local SUMMARY = { + { "blue", "ogred", { 0, 0, 0 }, { 0, 0, 255 } }, + { "red", "gbc", { 25, 16, 16 }, { 74, 165, 90 } }, + { "red", "redpp", { 0, 0, 0 }, { 0, 189, 0 } }, + { "yellow", "ogred", { 25, 25, 25 }, { 0, 255, 0 } }, + { "red", "ogred", { 148, 58, 58 }, { 148, 58, 58 } }, +} + +for _, c in ipairs(SUMMARY) do + local version, mode, wantOld, wantNew = c[1], c[2], c[3], c[4] + setup(version, mode) + + local mon = Pokemon.new(Data, "BULBASAUR", 20) + mon.hp = mon.stats.hp + local game = { data = Data, + save = { options = {}, player = { id = 1, name = "RED" } } } + local menu = SummaryMenu.new(game, mon) + + local realBar = HudTiles.drawHPBar + local realShader = PaletteFX.shader + local seenGrayFill + HudTiles.drawHPBar = function(_, _, _, _, _, grayFill) + seenGrayFill = grayFill and true or false + end + PaletteFX.shader = function() return { send = function() end } end + local ok, err = pcall(function() menu:draw() end) + HudTiles.drawHPBar, PaletteFX.shader = realBar, realShader + check(ok, ("%s/%s SummaryMenu:draw runs headless%s"):format(version, mode, + ok and "" or (": " .. tostring(err)))) + check(seenGrayFill == true, + ("%s/%s SummaryMenu:draw asks for a gray fill when a zone pass will run") + :format(version, mode)) + + local pal = PaletteFX.pal(Data, "GREENBAR") + check(pal ~= nil, ("%s/%s GREENBAR resolves"):format(version, mode)) + local predicted + if seenGrayFill then + predicted = pal[bucket(85 / 255)] + else + local tintR = math.min(1, pal[3][1] / 170) + predicted = pal[bucket((85 / 255) * tintR)] + end + colorEq(predicted, wantNew, + ("%s/%s summary HP fill matches the fixed color"):format(version, mode)) + + local oldPredicted = pal[bucket((85 / 255) * math.min(1, pal[3][1] / 170))] + colorEq(oldPredicted, wantOld, + ("%s/%s summary HP fill's pre-fix formula reproduces the reported color") + :format(version, mode)) +end + +-- ---------------------------------------------------- #1340 animSpriteColors +local ANIM = { + { "red", "ogred", { 123, 255, 49 }, { 0, 132, 0 }, { 0, 0, 0 } }, + { "blue", "ogred", { 255, 132, 132 }, { 148, 58, 58 }, { 0, 0, 0 } }, + { "red", "gbc", { 255, 239, 255 }, { 25, 16, 16 }, { 25, 16, 16 } }, + { "blue", "gbc", { 255, 239, 255 }, { 25, 16, 16 }, { 25, 16, 16 } }, + { "yellow", "gbc", { 255, 239, 255 }, { 25, 16, 16 }, { 25, 16, 16 } }, + { "yellow", "ogred", { 255, 239, 255 }, { 25, 16, 16 }, { 25, 16, 16 } }, +} + +for _, c in ipairs(ANIM) do + local version, mode = c[1], c[2] + setup(version, mode) + local zone = PaletteFX.pack(Data).palettes.GREENBAR + local fake = { data = Data, zoneColorsAt = function() return zone end } + local s = { obp = "f0", x = 72, y = 80 } + local out = BattleState.animSpriteColors(fake, s, 64, 64) + check(out ~= nil, ("%s/%s animSpriteColors returns a triple"):format(version, mode)) + local rounded = {} + for i = 1, 3 do + rounded[i] = out and { + math.floor(out[i][1] * 255 + 0.5), + math.floor(out[i][2] * 255 + 0.5), + math.floor(out[i][3] * 255 + 0.5), + } or nil + end + colorEq(rounded[1], c[3], ("%s/%s animSpriteColors f0 shade0"):format(version, mode)) + colorEq(rounded[2], c[4], ("%s/%s animSpriteColors f0 shade1"):format(version, mode)) + colorEq(rounded[3], c[5], ("%s/%s animSpriteColors f0 shade2"):format(version, mode)) +end + +GameVersion.set("red") +eq(PaletteFX.usesSpriteObp("ogred"), true, "OG RED uses the boot-ROM OBJ ramp for anims") +GameVersion.set("yellow") +eq(PaletteFX.usesSpriteObp("ogred"), false, "OG YELLOW keeps the SGB anim model") + +GameVersion.set(prevVersion) +PaletteFX.setMode(prevMode) +S.finish() From 32e6e4cef0015bba89a358a1a212e2ff8f558078 Mon Sep 17 00:00:00 2001 From: bryanthaboi <bryanthaboi@theboisclub.com> Date: Sun, 16 Aug 2026 06:50:17 -0400 Subject: [PATCH 2/5] conv --- GOLD-OPEN-ITEMS.md | 275 -------- docs/behavior-porting-notes.md | 519 --------------- ...erences-between-gold-silver-and-crystal.md | 615 ------------------ docs/extraction-notes.md | 50 -- docs/gen2-link-design.md | 316 --------- docs/gold-phase1.md | 188 ------ docs/ios-sideload.md | 48 -- docs/known-differences.md | 42 -- docs/launcher.md | 300 --------- docs/new-features.md | 7 +- docs/tiled-map-editing.md | 65 -- .../example_mew_starter/README.md | 0 .../assets/mew_back_inverted.png | Bin .../assets/mew_front_inverted.png | Bin .../example_mew_starter/main.lua | 0 .../example_mew_starter/manifest.json | 0 .../example_mew_starter/mod.card | 0 mods/examples/example_silly_oak/CHANGELOG.md | 12 - mods/examples/example_silly_oak/README.md | 43 -- .../example_silly_oak/assets/toast_kid.png | Bin 245 -> 0 bytes mods/examples/example_silly_oak/main.lua | 107 --- mods/examples/example_silly_oak/manifest.json | 15 - mods/examples/example_silly_oak/mod.card | 21 - .../tests/example_silly_oak_test.lua | 159 ----- 24 files changed, 1 insertion(+), 2781 deletions(-) delete mode 100644 GOLD-OPEN-ITEMS.md delete mode 100644 docs/behavior-porting-notes.md delete mode 100644 docs/differences-between-gold-silver-and-crystal.md delete mode 100644 docs/extraction-notes.md delete mode 100644 docs/gen2-link-design.md delete mode 100644 docs/gold-phase1.md delete mode 100644 docs/ios-sideload.md delete mode 100644 docs/known-differences.md delete mode 100644 docs/launcher.md delete mode 100644 docs/tiled-map-editing.md rename mods/{ => examples}/example_mew_starter/README.md (100%) rename mods/{ => examples}/example_mew_starter/assets/mew_back_inverted.png (100%) rename mods/{ => examples}/example_mew_starter/assets/mew_front_inverted.png (100%) rename mods/{ => examples}/example_mew_starter/main.lua (100%) rename mods/{ => examples}/example_mew_starter/manifest.json (100%) rename mods/{ => examples}/example_mew_starter/mod.card (100%) delete mode 100644 mods/examples/example_silly_oak/CHANGELOG.md delete mode 100644 mods/examples/example_silly_oak/README.md delete mode 100644 mods/examples/example_silly_oak/assets/toast_kid.png delete mode 100644 mods/examples/example_silly_oak/main.lua delete mode 100644 mods/examples/example_silly_oak/manifest.json delete mode 100644 mods/examples/example_silly_oak/mod.card delete mode 100644 mods/examples/example_silly_oak/tests/example_silly_oak_test.lua diff --git a/GOLD-OPEN-ITEMS.md b/GOLD-OPEN-ITEMS.md deleted file mode 100644 index b0970466..00000000 --- a/GOLD-OPEN-ITEMS.md +++ /dev/null @@ -1,275 +0,0 @@ -# Gold port: what is still open after the 2026-08-09 asm parity runs - -Two multi-agent runs diffed the Gold port against the `pokegold` disassembly -(`../pokegold`) and fixed what diverged. Round 1 confirmed 51 defects across 14 -paired clusters; round 2 confirmed 41 more across 13, including one crash. A -third round (2026-08-10) closed the mod-API boundary: see "The 2026-08-10 -mod-API round" below for what that opened and closed. - -Every tier is green at the end of all three: `run_engine` 131/131, `run_tests` -ALL TESTS PASSED, `run_modkit` 6/6, `run_save_editor_tests` 650/0, -`run_link_tests` ALL PASSED. - -This file records only what is NOT done, so nobody re-derives it. It is a -working handoff doc in the shape of `KANTO-CONTINUE.md`, not user documentation. - -## Do this first or none of it is visible - -**Re-import the Gold cache.** Seven fixes live in the extractor and the manifest -tool, so they do nothing until the ROM is re-imported. This was verified still -outstanding on 2026-08-10: the default identity's -`gold/data/generated/menu_gfx.lua` has no `trainerPics` key at all, so a trainer -battle still opens on the mon rather than the trainer even though the Lua half -is fixed and correct. - -- trainer frontpics (`TrainerPicPointers` walk in `extractMenuGfx`), which is - the missing trainer intro image at the start of a trainer battle. The Lua - side is now right and re-import is the ONLY thing left: `Trainers.lookup` - returns a numeric `class` (36) beside a `classId` (`BUG_CATCHER`), and both - `menu_gfx.trainerPics` and `palettes.trainers` are keyed by the constant, so - the lookup had to move to `classId`. On a cache that carries the pics the - frontpic renders in the class's own palette. -- egg assets: `battle/front/egg.png` and `menu/egg_hatch.png`, feeding the - hatch cutscene and the summary screen's egg page. A fallback landed in the - meantime (`ICON_EGG` frame 0, which resolves to a real 16x16 frame), so the - egg page draws SOMETHING on a stale cache; `tools/rom_manifest_gold.json` - carries `EggPic` at `[20, 31363]` and that stream decompresses to exactly 400 - bytes (a 5x5-tile pic), so a fresh import takes the real path. -- the Pokegear phone icon. `_LoadFontsExtra` (`engine/gfx/load_font.asm:8-15`) - puts `FontsExtra_SolidBlackAndUpArrowGFX` at `$60`/`$61` and - `PokegearPhoneIconGFX` at `$62`, ON TOP of `FontExtra`'s BOLD_A/B/C. - `src/import/RomExtractorGen2.lua:342-356` reads `FontExtra` straight in and - then blits only `Frames` over `$79-$7E`, so `$62` is still a bold "C" -- which - is what the new caller box draws where the telephone glyph belongs. Fix is two - files: add the two GFX symbols to `tools/rom_manifest_gold.json`, then blit - them at `$60`/`$61`/`$62` in the loop that already handles `Frames`. -- `PREDEFPAL_UNOWN_PUZZLE = 76`, which is why the Ruins of Alph puzzle rendered - grayscale instead of brown -- the tilemap pad `0x7f -> 0x4f` in `readTilemapRLE` / `readFlatTilemap` -- held-item icons (`HeldItemIcons` in the manifest, `out.heldItem` in - `extractIcons`), which is the held-item marker in the party menu -- the NPC-trade rows' `item` field, which is what made the Violet City Onix's - Bitter Berry print as "83" - -## Open: real unknowns, each needs a cache and a driver run - -1. **Magnet train renders a blank field.** Pushing `World:magnetTrain` directly - shows nothing for 90+ frames (see shot `03-magnet-train.png`). The screen's - surround is correct now; what was never investigated is whether - `drawBackground` needs setup that the direct push skips. This cannot be - settled by reading source. - -2. **The NPC that introduces Unown never appears.** Round 1 could not produce a - port ref that diverges from the asm, so it is filed `cannot_locate`, not - fixed. The cart chain is: solve a chamber puzzle, then - `setmapscene RUINS_OF_ALPH_INNER_CHAMBER, SCENE_RUINSOFALPHINNERCHAMBER_STRANGE_PRESENCE` - from `maps/RuinsOfAlphKabutoChamber.asm`. Walk that chain live rather than - statically: the static read came back clean twice. - -3. **Two pic sites still resolve an Unown off the species.** - `src/ui/gen2/PhotoStudio.lua:102` and `src/ui/gen2/EvolutionAnim.lua:460` use - `def.spriteFront` without going through `Unown.formSprite`. For EvolutionAnim - this is almost certainly dead (Unown does not evolve). For PhotoStudio it is - genuinely unclear: the cart's `engine/events/print_photo.asm` does not call - `GetUnownLetter` at all, so the port may already match. Check before changing. - -4. **Unown dex registration on obtain paths.** `engine/pokemon/evolve.asm:310` - runs `GetUnownLetter` then `callfar UpdateUnownDex` after - `SetSeenAndCaughtMon`, guarded by `cp UNOWN`. That is dex bookkeeping, not - rendering, and it was never audited across every path that gives the player a - mon. `GetUnownLetter` has 20 call sites on the cart; the port covers the pic - ones. - -## The 2026-08-10 mod-API round - -Six lanes closed the Gen 1 / Gen 2 mod-API boundary and a batch of reported -gameplay bugs. What that round **closed**, so nobody re-opens it: - -- Fifteen seams that had a Gen 2 site but no entry in the parity gate are now - listed and asserted: the four `intro.oak_speech.*` events plus the - `intro.oak_speech.build` hook, `battle.overlay`, - `battle.low_health_alarm`, `battle.catch_exp`, `pokemon.sprite`, - `input.step`, `input.pointer`, `render.zones`, `render.compose`, - `render.letterbox`, `render.hud`. Five more (`intro.boot.*`) are listed as - Gen 2-only. `tests/engine/gate_gen2_mod_api.lua` is 893/893. -- Two registries un-gated on Gold after their consumer landed: - `battle_sprite_scales` (`src/ui/gen2/BattleState.lua:imageScale`) and - `render_pipelines` (`src/core/Game2.lua:load` installs - `src/render/Pipelines.lua` on the merged dataset after `mods:load`). Both - keep the SHARED Gen 1 target, so one mod record serves both games. 40 of the - 46 registries are now available on Gold. -- `hook:render.zones` came off the `gate_meta_coverage.lua` DEBT ledger, which - is now four entries, all M7/M12 link and give-mon seams. - -What that round **left open**, each verified on 2026-08-10: - -1. **`render_pipelines`' `drawWorld` half is inert on Gold.** Gold's overworld - draws straight to the window rather than into a canvas the way - `src/world/OverworldController.lua` hands one to `Pipelines.drawWorld`, so a - drawWorld-only pipeline renders nothing. It is not left switched on - pretending: `Game2:load` retires a restored level for one and re-applies - Tilt from the option the exclusion just cleared, leaving - `options.pipelines` untouched so the mode returns the day Gold grows a world - canvas. Related: `Pipelines.rows` is read only from - `src/ui/OptionsMenu.lua`, so a pipeline on Gold has a hotkey and no OPTION - row. - -2. **`transitions` stays gated and should.** `src/ui/gen2/BattleTransition.lua` - keys `STYLES` as a boolean SET of the four cart wipes (`spin`, `speckle`, - `zoom`, `sine`), not the `{ frames, draw, sound, flash }` record the - registry carries, and there is no styleDef lookup a mod id could reach -- - a registered style would fail the membership test and fall back to vanilla. - Un-gating it before that changes would be the silent no-op the routing table - exists to prevent. Same for `rulesets`, `field`, `text_pointers`, - `link_fields` and `map_scripts`; `docs/mod-api-gen2-compat.md` carries the - per-registry reason. - -3. **`src/core/Game2.lua:joystickremoved` is half of Gen 1's.** - `src/core/Game.lua:867` does `self:recoverInput("joystickremoved", joystick)` - AND `TouchControls:joystickremoved()`; Game2 does only the pad half, so a - controller unplugged mid-hold leaves Gold's Input state stranded and a held - direction walks forever. It was a total noop before, so this is not a - regression -- it is a seam finished halfway. One line. - -4. **`Game2` noops five joystick callbacks.** `joystickpressed`, - `joystickreleased`, `joystickaxis`, `joystickhat` and `joystickadded` are - assigned `noop`, so a stick with no SDL game-controller-database entry - reaches neither Input nor `GamepadMap.RAW_BUTTON_BINDINGS` on Gold. Gen 1 - handles all four (`src/core/Game.lua:764-797`). A DualSense is - SDL-recognized and takes the gamepad path, so this is not the reported - SELECT bug -- it is the raw-stick fallback. - -5. **`Game2:gamepadpressed` has no SELECT-held guard.** Gen 1 - (`src/core/Game.lua:686-694`) suppresses the shoulder GAME SPEED cycle while - SELECT is held, because Select+L is a display chord on NX. Harmless today - (Gold has no display chord) but the two paths have diverged. - -6. **`hideCallerBox`'s fallback path skips the screen seams.** - `src/script/gen2/CallAsm.lua:217-231` pops through `stack:pop` when the - caller box is top (which it always is on a normal call, since `PhoneRing` - runs `closetext` before `InitCallReceiveDelay`) but falls back to a bare - `table.remove(states, index)` otherwise, which raises neither `exit` nor - `screen.popped`. Only a mod screen pushed over the box can reach it, and - then a listener that saw `screen.pushed` never sees the pop. - -7. **`src/core/gen2/Roamers.lua:338` says the wrong thing.** Its comment says - "the shared `encounter.species` hook still runs downstream and is where a - mod changes what appears". It does not: `World:tryWildEncounter` calls - `startBattle` on a roamer hit and RETURNS before it reaches - `World:rollEncounter`. The doc's partial-coverage list is right and the - comment is wrong; correct the comment, not the doc. - -8. **`src/world/gen2/World.lua:4318` still calls the pack's SEL row something - "this port has not built".** The pack submenu now offers SEL. Stale comment. - -Three partial coverages that are documented and still true, repeated here -because "the hook exists" is not "the hook sees everything": -`encounter.roll` / `encounter.species` are not wired into `World:tryHeadbutt`, -`World:rockMonEncounter` or the roamer path; `src/ui/gen2/BattleState.lua` -builds a flat `opts` for `Catching.attempt` with no `data` in it, so a -mod-registered ball is readable through `Catching.recordFor` but is not -resolved at the real throw site; and no Gen 2 UI file reads -`Battle.statusRecordFor(data, status).hudLabel`, so a mod status shows no label -in the battle HUD, the party menu or the summary page. - -## Open: cleanups declined on purpose - -These were skipped with reasons during the wiring pass. They are listed so the -reasons survive, not because they are pending work. - -1. **`Vm:showRaw(body, stay)`** was proposed and declined. `showRawHeld` in - `Specials.lua` already answers the text lookahead correctly, so this trades - one working shape for another. - -2. **`tests/drivers/gold_egg_hatch.lua` summary push** was declined as - redundant: `gold_egg_hatch_shots.lua` already photographs both the cutscene - and the summary page. - -3. **Two `Battle.lua` divergences** were filed as notes by the file's own owner - rather than fixed, because no reported symptom drives either: text printed - for an already-statused target, and Future Sight taking STAB, type - effectiveness and weather. Both are real divergences from the cart. Fix them - when something actually depends on them. - -## Reported, investigated, and NOT defects: do not re-chase these - -Each of these came in as a bug report and came back disproved against the asm. -Re-opening one costs another full investigation, so the reasoning is kept here. - -- **SFX pointer table misalignment.** The table is aligned end to end. 188 `dba` - rows with no skips or padding, 188 constants, verified through the manifest - scraper, the extractor's `*3` stride, and 8 ids spot-checked against the live - cache. Two things that look like misalignment are how the cart is built: - `SFX_GET_EGG_UNUSED` and `SFX_GET_EGG` both point at `Sfx_GetEgg`, and - `Sfx_ReadText` / `Sfx_ReadText2` share one header address. **Do not repoint, - re-stride, or add an offset to the SFX, music, or cry tables.** - -- **Bind letting you pick other moves.** This is Gen 1 behavior and does not - hold for Gen 2. `BattleCommand_TrapTarget` (`effect_commands.asm:5568-5605`) - writes only the TARGET's wrap count and trapping move. It touches nothing on - the user, so the user is free to switch moves. The port is correct. The real - Gen 2 move locks (Rollout, Thrash, Petal Dance) were genuinely missing and - have been added. - -- **The Slowpoke Tail salesman not blocking progression.** He is Route 32's - `FISHER4` at (7,70), not an Azalea Town object, and the cart's refusal arm - only prints. `_OfferToSellSlowpokeTail` runs `setscene SCENE_ROUTE32_NOOP` - first, so the coord event never fires again. There is no pushback to port. - -- **The rival being named BLUE.** Not reproducible. The whole chain is intact: - special 36 resolves to `H.NameRival`, `NamingScreen:accept` hands the typed - string through, and BLUE is the Gen 1 default that Gold never reaches. A - separate real defect on this seam (the rival being pre-named SILVER before the - officer asks, and the blank-entry fallback) was found and fixed. - -- **Not Very Effective dealing 0 damage**, **status not shown in the battle - UI**, and **the Pokedex missing from the Start Menu** all came back - `already_fixed`: the damage floor is `MIN_DAMAGE` added after the cap and - before the type multiply, the HUD prints the status tag where the level goes, - and the menu row is gated on `save.engineFlags[11]` correctly. - -## Standing risks, not breakage - -- **Four Gold test files are wired into no runner**, so a regression in them - will not turn `run_tests.lua` red: `gold_flag_names_test`, - `gold_route_validate_test`, `gen2_pokegear_unlock_test`, - `gen2_save_export_test`. All four pass standalone. The first two are the - pre-run route validators documented in `KANTO-CONTINUE.md`; the last two need - a `GOLD_CACHE` and are excluded by an in-file comment. This is deliberate, but - it is a coverage hole and worth knowing. - -- **`mods/` must stay clean.** The 2026-08-10 round left temporary probe mods - behind mid-run (`mods/tmp_menus`, `mods/zz_verify_seams`); both are gone now - and `mods/` holds only `example_mew_starter`, `example_silly_oak`, `examples` - and `nuzlocke`. A stray `mods/tmp_*` loads on every Gold boot and changes what - a driver measures. `mods/example_silly_oak` carries only a `.modkitignore` and - warns "manifest.json does not exist" on every boot; that is pre-existing - (27 Jul) and unrelated. - -- **`tests/drivers/gold_opaque_surround.lua` is stale.** It reads - `game.stack._items`; `src/core/StateStack.lua` has only `.states`. That branch - is covered instead by `gold_center_pc` and the letterbox-per-frame count in - `gold_frame_seams`. - -- **`luac` on this machine is Lua 5.5**, so `luac -p` is a weak proxy for the - LuaJIT/5.1 semantics this engine targets. Use `luajit -b <file> /dev/null` for - a real syntax gate. - -- **`docs/rfcs/0001-surfing-pikachu-sprite.md` and `0002-screen-render-visible.md` - are deleted** in the working tree, and both are still referenced by name from - live modkit case files that pass. Those deletions predate both parity runs. - Worth resolving before a commit. - -## The failure mode that actually bit, worth remembering - -Round 1 reported the Yes/No dialogue fix as landed. It was not. Two of its three -lanes wrote their half, and the third left the hook closure at -`World.lua:828` declared as `function(body, onDone)`, so the third argument -`Vm:resume` passes was silently discarded and the entire `stay` implementation -in `World:showText` and `World:askYesNo` was dead code. Lua drops extra -arguments without complaint, so nothing failed and every suite stayed green. - -Round 2 caught it only because an investigator probed the boundary empirically -instead of reading both sides and assuming they met. When work is split across -agents by file, **the seams between the files are where the bugs live**, and a -green suite does not prove a seam is connected. diff --git a/docs/behavior-porting-notes.md b/docs/behavior-porting-notes.md deleted file mode 100644 index d26affce..00000000 --- a/docs/behavior-porting-notes.md +++ /dev/null @@ -1,519 +0,0 @@ -# Behavior porting notes - -What was ported from pokered's engine code and where it came from. - -## Overworld - -- **Collision rule** (`home/overworld.asm` tile-in-front checks): a 16x16 - cell is passable when its bottom-left 8x8 tile is in the tileset's - `coll_tiles` list. Verified against Pallet Town's fences/houses/water - and Oak's Lab furniture. -- **Warp activation** (`home/overworld.asm` CheckWarpsNoCollision / - ExtraWarpCheck): a warp fires when arriving on a warp whose standing - tile is in the tileset's door or warp tile list, or when standing on a - warp and walking off the map edge (interior exit mats). Both paths are - data-driven from `door_tile_ids.asm` / `warp_tile_ids.asm`. -- **LAST_MAP warps** return to the remembered outdoor map/position, like - `wLastMap`. -- **Connections** (`map_header` connection directives): crossing an edge - places the player at `destCoord = curCoord - offset*2` cells on the - destination's opposite edge. -- **Movement**: tile-by-tile, 1 px/frame at 60 fps (16 frames per step), - tap-to-turn without stepping, hold-to-walk, input locked mid-step. -- **Wild encounters** (`engine/battle/wild_encounters.asm`): per grass - step, encounter iff `rand(0..255) < rate`; slot picked via the - cumulative buckets 51/102/141/166/191/216/229/242/253/256. -- **Initial object visibility** from `toggleable_objects.asm` (e.g. Oak - hidden in his lab), with `show_object`/`hide_object` script commands - persisting to the save like the missable-object bits. - -## Pokémon math (`engine/pokemon/calc_stats.asm`, `experience.asm`) - -- `stat = floor(((base + DV)*2 + floor(sqrt(statExp)/4)) * L / 100) + 5` - (HP: `+ L + 10`); HP DV from the low bits of the other four DVs. -- Growth curves use the exact cubic coefficients (MEDIUM_SLOW = - 1.2n^3 - 15n^2 + 100n - 140, etc). -- Exp gain = `floor(baseExp * level / 7)` (x1.5 for trainer battles); - defeated species' base stats accumulate as stat experience. - -## Battle core (`engine/battle/core.asm`) - -- Damage: `floor(floor(2L(x2 crit)/5 + 2) * power * atk / def / 50)` - capped at 997, `+2`, STAB x1.5, per-matchup type multipliers applied - sequentially (x10 fixed point), then `rand(217..255)/255` when - damage > 1. -- Critical hits: `rand(0..255) < baseSpeed/2` (x4 for Karate Chop, Razor - Leaf, Crabhammer, Slash, capped 255); crits double level and ignore - stat stages (gen1_faithful ruleset). -- Accuracy: `rand(0..255) < floor(acc*255/100)` after accuracy/evasion - stages, including the 1/256 miss at 100% accuracy (toggleable via the - `modern_clean` ruleset). -- Stat stages use the 25/28/33/40/50/66/100/150/.../400 multiplier table - (`data/battle/stat_modifiers.asm`). -- Physical/special split by type (special = Water/Grass/Fire/Ice/ - Electric/Psychic/Dragon). -- Status: paralysis speed/4 and 25% full para, burn halves physical - attack, poison/burn residual = maxHP/16, sleep 1-7 turns waking on the - lost turn, freeze permanent (as in Gen 1). -- Turn order: effective speed, coin-flip ties; Quick Attack first, - Counter last (Gen 1's only priorities). -- Run formula (`TryRunningFromBattle`): always escape if faster, - otherwise `floor(pSpd*32 / (eSpd/4)) + 30*attempts` vs `rand(0..255)`. -- Catching (`ItemUseBall`): ball-specific rand ranges (255/200/150), - status bonus 25/12, second roll `floor(maxHP*255/ballFactor) / - floor(HP/4)` capped 255. -- Prize money: class base money x last defeated mon's level - (`pic_pointers_money.asm`). - -## Battle move effects (engine/battle/core.asm, move_effects/*) - -- Mimic via Metronome (effects.asm:1203-1273): MimicEffect's - .letPlayerChooseMove branch snapshots wCurrentMenuItem before the - copy-picker menu opens and restores it afterward as the write index - into wBattleMonMoves. Since SelectMenuItem always writes - wCurrentMenuItem/wPlayerMoveListIndex together at the FIGHT-menu - confirm and nothing (including MetronomePickMove) touches either - variable during mid-move resolution, the reused value is always the - calling move's own slot, BattleState.lua's applyMimic fallback uses - self.moveIndex, frozen the same way, so a called Mimic (e.g. from - METRONOME in slot 3) overwrites the calling move's own slot, keeping - its PP, matching the Gen 1 quirk exactly. -- Multi-hit distribution 2/2/2/3/3/3/4/5 over rand(0..7); all hits reuse - the first damage roll (faithful). -- Recoil = damage/4 (Struggle /2); drain/Dream Eater heal = damage/2; - Dream Eater requires sleep. -- Fixed damage: SonicBoom 20, Dragon Rage 40, Seismic Toss/Night Shade = - level, Psywave rand(1 .. 1.5xlevel-1). -- OHKO deals 65535, fails against faster targets; Swift skips accuracy; - Jump Kick crash = 1 damage on miss; Explosion halves defense and - faints the user even on a miss; Hyper Beam skips recharge if it KOs. -- Charge moves (incl. Fly's invulnerable turn), trapping moves locking - the victim out of its turns, Thrash's 3-4 turn lock ending in - confusion, Bide's 2-3 turn store-and-double, Rage's permanent lock - with attack-up on being hit, Counter/Quick Attack priority. -- Side-effect chances: 26/256 (10%), 77/256 (30%), stat-down side - effects 85/256; Twineedle 20% poison. -- Substitute costs 1/4 max HP, absorbs damage, blocks status/stat/side - effects; screens double effective defense (bypassed by crits); Focus - Energy keeps the Gen 1 quarter-rate bug under gen1_faithful. -- Status: sleep 1-7 turns (wake turn is lost), freeze permanent, burn - halves physical attack, paralysis speed/4 + 25% full para, Toxic's - rising counter, Leech Seed transfer, confusion 2-5 turns with 50% - 40-power typeless self-hit. -- Trainer Pokémon use fixed DVs 9/8/8/8 (TrainerAI.asm convention). - -## Items (engine/items/item_effects.asm) - -- Potion family 20/50/200/full; drinks 50/60/80; status heals per item; - Revive half HP; Rare Candy = exact next-level exp with HP delta kept; - evolution stones use the extracted evos data; TMs single-use / HMs - reusable, gated by the species' real tmhm list; Repel 100/200/250 - steps blocking wilds below the lead's level; Escape Rope returns to - the last heal point. -- Snorlax (Route 12/16) only wakes via `ItemUsePokeFlute` (item-use - menu, adjacent to it, not yet beaten), talking to it with the POKé - FLUTE merely in the bag has no effect (`engine/items/item_effects.asm`, - `scripts/Route12.asm`/`Route16.asm`). -- Mart inventories come from the script_mart lists per clerk; selling - pays half price; TM prices from tm_prices.asm. - -## Overworld field systems - -- Ledges from ledge_tiles.asm (facing + standing tile + ledge tile + - input direction -> two-cell hop). -- Counter talk-through uses the tileset's counter tiles - (tileset_headers.asm), which is how mart clerks and nurses work. -- Trainer sight (`home/trainers.asm` CheckFightingMapTrainers + - `engine/overworld/trainer_sight.asm`): extracted per-trainer range, - inclusive tiles along the facing line; detection runs only on - tile-aligned frames, before input handling, so on detection the d-pad - is dead (wJoyIgnore) and the player freezes on the spotted tile; the - "!" holds 60 frames (EmotionBubble), then the trainer walks - distance−1 steps to the adjacent tile (none if already adjacent) and - uses the real battle/won/after dialogue from the trainer headers. - Sight is a pure screen-coordinate comparison with no line-of-sight - obstruction check (TrainerEngage / CheckSpriteCanSeePlayer): an - aligned in-range trainer engages through interposed NPCs and - unwalkable tiles, and the walk-up (TrainerWalkUpToPlayer, a fixed - distance−1 MoveSprite_ script) has no collision either, so the - trainer simply walks/overlaps through anything on the line, as OAM - sprites overlap on hardware. -- Elevator rides (`engine/overworld/elevator.asm` ShakeElevator → - `src/world/ElevatorShake.lua`): choosing a floor stops the music, - bounces the BG scroll ±1 px around rest for 100 two-frame cycles with - SFX_COLLISION retriggered every cycle, restores the scroll, plays - SFX_SAFARI_ZONE_PA to completion, and restarts the map theme before - the floor warp. Lead-in delays kept per script: 9 frames of Delay3s - inside ShakeElevator (Celadon farjps in), 12 with the Silph/Rocket - scripts' extra Delay3. The offset applies to the BG layer only, - sprites are OAM and stay put. After the ride the port no longer - jump-cuts: choosing a floor rewrites the car's own exit-warp entries - to that floor (`engine/events/elevator.asm` DisplayElevatorFloorMenu - .UpdateWarp, per scripts/SilphCoElevator.asm / - CeladonMartElevator.asm / RocketHideoutElevator.asm), then the player - is walked out through the doorway onto that warp (ow:scriptMove → - ow:takeWarp), like the original. -- Field-move gates (engine/overworld/field_move_messages.asm + - start_sub_menus.asm): IsSurfingAllowed ported exactly, SURF refuses - with _CyclingIsFunText while the Cycling Road's BIT_ALWAYS_ON_BIKE is - armed (save.forcedBike: set on the Route 16/18 forced-bike tiles, - cleared by the gates, Fly, dungeon/blackout warps; the forced mount - itself is silent, as in CheckForceBikeOrSurf) and with - _CurrentTooFastText on Seafoam B4F's stairs square (7,11) until both - EVENT_SEAFOAM4 boulders are down. Re-selecting SURF while surfing is - ItemUseSurfboard's dismount attempt: steps ashore silently if the - facing tile is land-passable and unoccupied, else "There's no place - to get off!", and the menu closes either way (wActionResult stays 1). - STRENGTH's first page auto-advances after the cry + Delay3 (no - prompt); "can move boulders." prompts. The GBPalWhiteOutWithDelay3 - white blink plays on every .goBackToMap closer: Strength, surf - mount/dismount/no-place, Flash (after its text), and Dig/Teleport - (Cut closes without a blink, per the asm). -- Wild slot table + rate per map; water encounter tables used while - surfing. -- Cut-tree block swaps from cut_tree_blocks.asm; surfable tilesets from - water_tilesets.asm (water tile $14, plus $32 on SHIP_PORT). - -## Story events (data/scripts/story.lua and friends) - -- Every hand-ported script cites its scripts/*.asm source and reuses the - real extracted text and event-flag names. -- Custom flag names (audited equivalent): three port-internal flag - families have no pokered EVENT constant but mirror the original's - state exactly. EVENT_TRADED_* are per-trade names for - wCompletedInGameTradeFlags bits (engine/events/in_game_trades.asm: - FLAG_TEST before the offer → after-trade text, FLAG_SET on completion; - dialogset text families, party-menu pick, the received mon joins the - end of the party, ConnectCable→anim→TradedFor→Thanks all ported). - EVENT_GOT_EEVEE is bookkeeping alongside the real guard, the hidden - ball object (scripts/CeladonMansionRoofHouse.asm HideObject, ≡ - save.objectToggles), and self-heals older saves; a full party+box - keeps the ball claimable (_BoxIsFullText). EVENT_BEAT_SS_ANNE_RIVAL - stands in for scripts/SSAnne2F.asm's saved wSSAnne2FCurScript NOOP - progression, including the lose-and-retrigger path (flag only set on - victory). Names are kept for save compatibility. Coverage: - tests/parity_trade_gift.lua. -- The Pallet Town intro follows pokered exactly: the trigger is - PalletTownDefaultScript's wYCoord==1 check, Oak appears at (8,5) and - takes FindPathToPlayer's zigzag to one tile below the player, and the - escort is RLEList_ProfOakWalkToLab against the reverse-order playback - of RLEList_PlayerWalkToLab (the 17th simulated press is eaten by the - door-warp frame), followed by the OaksLab walk-in and choose-mon - exchange with map music deferred like BIT_NO_MAP_MUSIC. Oak's speech - ends with the real shrink: RedPicFront collapses through the extracted - ShrinkPic1/ShrinkPic2 into the overworld walking sprite on - OakSpeech.asm's frame timings (SFX_SHRINK, 4/4/20/50-frame beats, fade - to white), with the closing text box held on screen. The escort's - scripted steps run 16 frames/tile (chained single-tile scriptMoves - start back-to-back, no idle frame); Oak marches in place on the door - mat for RLEList_ProfOakWalkToLab's trailing NPC_CHANGE_FACING beat - (movement.asm ChangeFacingDirection → zero-delta TryWalking); the "!" - EmotionBubble overlaps the still-shown "Hey! Wait!" box - (PalletTownOakText prints without a button wait, then DelayFrames 10 → - EmotionBubble before the box clears); and the shrink beat ramps the - music to silence over ~70 frames (wAudioFadeOutControl = 10; - home/fade_audio.asm FadeOutAudio steps rAUDVOL 7→0) rather than - hard-stopping. -- The 12 disguised static wild battles (Power Plant Voltorb/Electrode + - Zapdos, Articuno, Moltres, Mewtwo) follow TalkToTrainer/ - EndTrainerBattle exactly: cry + battle text, after-battle text without - a rematch once EVENT_BEAT_* is set, and the flag/HideObject on any - non-blackout result (fleeing loses the legendary, as in Gen 1). - Snorlax hides before its battle and only shows the calmed-down/ - returned line when not caught. Zapdos/Articuno/Moltres/Mewtwo's - battle text is a text_far string ending in a bare "...@" terminator - (no <DONE>/<PROMPT>) followed by text_asm PlayCry + WaitForSoundToFinish: - the box types with no ▼ prompt and auto-closes only once the cry - finishes, never on a button press, ported via `Commands.play_cry` - stashing the pending cry for the following `Commands.show_text` to - consume as the TextBox's auto-close sound. Voltorb/Electrode's battle - text has no PlayCry call in the ROM at all and keeps the ordinary - button-wait close. -- Gym leader repeat dialogue (data/scripts/gyms.lua): each leader's - text_asm branches on EVENT_BEAT_<LEADER>, pre-badge talk prints the - pre-battle text and engages the leader battle (badge/TM via - data/scripts/victories.lua); post-badge talk prints the leader's - post-battle advice text (Misty's is her TM11 explanation). The - originals' middle branch (beaten but TM not handed over, - CheckEventReuseA EVENT_GOT_TM*) is ported too: the victory's GiveItem - goes through the bag's capacity check, a full bag shows the leader's - "make room" text instead of the received lines and leaves - EVENT_GOT_TM* unset, and talking to the leader re-runs the ReceiveTM - script until the TM goes in (#797). Giovanni's - farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a - fade-to-black/fade-in Transition matching ViridianGym.asm's - GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted - permanently via TOGGLE_VIRIDIAN_GYM_GIOVANNI in save.objectToggles. -- Cable Club receptionists (TX_SCRIPT_CABLE_CLUB_RECEPTIONIST → - CableClubNPC, all 12 Pokémon Centers): welcome, pre-Pokédex "making - preparations" brush-off, and the apply/save YES-NO are ported; - accepting saves the game and opens the link menu, declining prints - "Please come again!". -- Cinnabar fossil deposit follows GiveFossilToCinnabarLab: a menu of - carried fossils (FossilsList order), SeesFossilText with a Yes/No - confirm, ComeAgainText on either cancel. -- Hall of Fame induction: each party mon's front sprite scrolls in from - the left at 4px/frame, matching HoFShowMonOrPlayer's .ScrollPic - front-pic phase (engine/movie/hall_of_fame.asm); the back-pic's - enlarged/blurred pre-wipe is a VRAM-scroll-register trick not - replicated in this sprite-based renderer. The finale - (HoFDisplayPlayerStats) shows trainer name, play time, money, POKéDEX - seen/owned, and Prof. Oak's rating text (engine/events/ - pokedex_rating.asm DexRatingsTable) from real save data. -- End credits + post-game reset (engine/movie/credits.asm, - scripts/HallOfFame.asm): screen-by-screen CreditsOrder pages (hlcoord - 9,6 + signed columns), FadeInCredits' 4x5-frame ramp, 90/110/120/140- - frame holds, DisplayCreditsMon's 27-frame 8px/frame silhouette wipe, - LoadCopyrightTiles' three-row block, THE END at (4,8). While THE END - is up the HoF script autosaves (wLastBlackoutMap := PALLET_TOWN; the - player is saved in the HALL_OF_FAME room), waits 600 frames, then A/B - triggers `jp Init`, the boot sequence replays into the title screen. -- Victory Road's boulder switches replicate the original's - ReplaceTileBlock data: 1F boulder at (17,13) -> block $1D at (4,6); - 2F boulders at (1,16)/(9,16) -> $15 at (3,4) and $1D at (11,7); 3F - boulder at (3,5) -> $1D at (3,5), and the (23,15) hole drops the - boulder to 2F (hide/show toggle). Barriers are re-applied from flags - on map entry, exactly like the originals' map-load scripts. -- Item balls, static legendary encounters and trainer rewards - (badges + gym TMs, the Silph Giovanni flag) are generic systems driven - by the extracted object args and a hand-ported reward table - (data/scripts/victories.lua). -- In-game trades use the real data/events/trades.asm table (species in, - species out, original nickname). - -## Safari game (engine/events/hidden_events/safari_game.asm + engine/battle) - -- ¥500 buys 30 SAFARI BALLs and 502 steps (scripts/SafariZoneGate.asm - sets `wSafariSteps = 502`); steps count down on the four outdoor zone - maps and hitting 0 (or throwing the last ball) ends the game at the - gate. -- Safari battles offer BALL / BAIT / ROCK / RUN; no player Pokémon - acts. The working catch rate starts at the species rate; BAIT halves - it and adds 1-5 to the bait factor (zeroing the escape factor); ROCK - doubles it (cap 255) and adds 1-5 to the escape factor (zeroing bait) - -- ItemUseBait/ItemUseRock in engine/items/item_effects.asm. -- Each turn one factor decays ("is eating!" / "is angry!"); when the - escape factor decays to 0 the catch rate resets to the species rate - (PrintSafariZoneBattleText, engine/battle/safari_zone.asm). -- Flee check (engine/battle/core.asm): `b = 2 * (speed % 256)`; the mon - always flees when speed > 127; while eating `b /= 4`, while angry - `b = min(255, 2b)`; it flees when `rand(0,255) < b`. -- The SAFARI BALL rolls the ULTRA_BALL rand range (0-150) in the Gen 1 - catch formula, against the BAIT/ROCK-modified rate. - -## Slot machines (engine/slots/slot_machine.asm) - -- The three reels are the extracted 18-symbol wheel sequences - (data/events/slot_machine_wheels.asm); bet 1 plays the middle row, - bet 2 adds top+bottom, bet 3 adds both diagonals. -- Payouts: 7-7-7 = 300, BAR = 100, CHERRY = 8, MOUSE/FISH/BIRD = 15 - (SlotRewardPointers). -- Per-wheel stop/slip rules ported exactly: wheel 1 spends up to 4 slip - charges, slipping past a centred CHERRY (in seven-and-bar mode it - always slips all 4 via pokered's `cp HIGH(SLOTS7)` bug); wheel 2 stops - as soon as wheels 1+2 line up any potential match (pairs checked b/b, - b/m, m/m, t/m, t/t) or, in seven-and-bar mode, on 7/BAR; wheel 3 rolls - past forbidden matches free and burns wSlotMachineRerollCounter - charges on winnable no-match spins, animated tile-by-tile. Luck flags - (SetFlags): seven-and-bar mode is sticky across spins; r==0 arms 60 - allow-matches charges; a BAR win clears flags; a 300 win zeroes the - counter and clears flags with probability 128/256; 8/15 wins burn one - charge. Lines are checked in asm order with the first match taken; - A-presses are ignored while a prior wheel's slip counter is nonzero. - Machine and COIN CASE texts are byte-identical - (_GameCorner*Text; AbleToPlaySlotsCheck's no-coins gate included). -- Flow brackets: PromptUserToPlaySlots "A slot machine! Want to play?" - (YesNoChoice) and MainSlotMachineLoop's "One more go?" (TwoOptionMenu); - the x3/x2/x1 coin menu (CoinMultiplierSlotMachineText) defaults its - cursor to x3, bet = 3 - menu item. Static frame: the real - SlotMachineMap (gfx/slots/slots.tilemap, 20x12 tile ids < $25) blitted - from red_slots_1.png, extracted as field.slotSymbols.tilemap - (tools/extract/gfx.py extract_slots). Win flash: - SlotMachine_CheckForMatches.flashScreenLoop flips rBGP (shade 3->2) b - times at 5 frames each, b = 20/8/4/2 for the 300/100/15/8 rewards - (SlotReward{300,100,8,15}Func). Payout drip: - SlotMachine_PayCoinsToPlayer credits one coin every 8 frames (4 for a - 7/BAR), SFX_SLOTS_REWARD per coin, rOBP0 symbol flicker every 5 coins. - -## Spinner arrow tiles (scripts/*.asm arrow movement tables) - -- Viridian Gym and Rocket Hideout B2F/B3F keep per-coordinate RLE - movement lists (map_coord_movement); each list executes backwards - from its terminator (DecodeArrowMovementRLE), sliding the player and - chaining onto further arrows. - -## Cries (data/pokemon/cries.asm, audio/engine_1.asm) - -- Each species = a base cry (one of 38 SFX_CryXX streams) + a frequency - modifier added to every note's frequency register - (Audio1_ApplyFrequencyModifier) + a tempo modifier - (`sfx tempo = $80 + length`, Audio1_SetSfxTempo). All 151 cries are - rendered offline with those modifiers applied and play on battle - entry and Pokédex pages. - -## Hidden events & facility puzzles - -- Card key doors (engine/events/card_key.asm): door tiles $18/$24 - (SILPH_CO_11F: $5e) replaced with block $0e ($03 on 11F). -- Vermilion trash cans - (engine/events/hidden_events/vermilion_gym_trash.asm): the first-lock - can re-rolls on every Vermilion City map load (VermilionCity_Script's - Random & $e, even cans) and after every failed second-can guess; the - second lock uses the GymTrashCans table verbatim, including the - underflow bug that can place it in can 0 regardless of adjacency; a - wrong pick resets EVENT_1ST_LOCK_OPENED and re-rolls immediately; only - SuccessText3 prints on completion; the gym door block at (2,2) is - $24 closed / $5 open (scripts/VermilionGym.asm). SuccessText1/ - SuccessText3/FailText play SFX_SWITCH/GO_INSIDE/DENIED from each - text's text_asm tail after the text prints (DisplayTextID's - WaitForTextScrollButtonPress then holds the box), so the port fires - them from an onDone on the TextBox, landing the beep as the box - closes rather than as it opens. -- Menu close-keys follow pokered's per-menu wMenuWatchedKeys mask, not - a single global rule: the shared Menu base (src/ui/Menu.lua) closes - on B only, and START-close is opt-in via opts.startCloses. Only the - start menu sets it, matching engine/menus/draw_start_menu.asm's - PAD_DOWN|PAD_UP|PAD_START|PAD_B|PAD_A; OptionsMenu also closes on - START via its own loop, matching engine/menus/main_menu.asm - DisplayOptionMenu's explicit B_PAD_B/B_PAD_START checks. Every other - menu (bag/PC item lists PAD_A|PAD_B|PAD_SELECT, party menu / - BUY-SELL-QUIT / USE-TOSS submenu / PC menus / Pokedex side menu - PAD_A|PAD_B) leaves PAD_START unwatched, so START does not close - them. START never replays SFX_PRESS_AB (HandleMenuInput_ beeps only - for the PAD_A|PAD_B branch). -- Old man tutorial hollow cursor: the item list is itself scripted in - pokered (DisplayListMenuID's old-man branch, home/list_menu.asm:65-91) - , no input is read; the filled '▶' hovers POKé BALL for 80 frames, - auto-presses A, then PlaceUnfilledArrowMenuCursor leaves the hollow - '▷' on that row until ItemUseBall tears the list down for the throw. - Ported via ListMenu's opts.script hook (src/ui/ListMenu.lua) and - BattleState:openOldManBag driving the same beats. The MissingNo./ - wGrassRate side effects of the OLD MAN name swap are not modeled, - see docs/gameboy-hardware-limitations.md. -- Gym statues (gym_statues.asm): plaque with the city/leader from each - gym's script; the player joins WINNING TRAINERS with the badge. -- Route 22 gate / Route 23 guards: real trigger rows, badge order - (EARTH down to CASCADE) and EVENT_PASSED_*_CHECK skip flags. -- Game Corner poster (scripts/GameCorner.asm): block (8,2) $2a -> $43 - on EVENT_FOUND_ROCKET_HIDEOUT. -- Seafoam Islands (scripts/SeafoamIslandsB3F/B4F.asm): reversed-RLE - current paths, Seafoam4HolesCoords boulder holes setting the - EVENT_SEAFOAM*_BOULDER*_DOWN_HOLE pairs, the forced pool exit rows. -- Rock Tunnel darkness: wMapPalOffset = 6 on entry, cleared by Flash - (BOULDERBADGE) or leaving (home/overworld.asm). - -## Battle extras - -- GROWL/ROAR (GetMoveSound/IsCryMove, engine/battle/animations.asm - ~2196): the move's own MoveSoundTable tempo byte (Growl $c0, Roar - $40, both pitch $00) layers onto the cry via `Sound.playMoveCry`'s - `Source:setPitch(256/(128+tempoMod))`. Transform (engine/gfx/ - palettes.asm DeterminePaletteID, bit TRANSFORMED): the swapped-in pic - is tinted PAL_GRAYMON via `PaletteFX.monPal(data, species, - transformed)`, not the copied species' own palette, in - `BattleState:speciesSprite`. Growl (DoGrowlSpecialEffects, - animations.asm ~928): AnimPlayer's GROWL frame-block branch keeps a - `growlNoteTrail` snapshot so each block's emitted sprites include the - previous block's note copy alongside the current one (GROWL skips - AnimationCleanOAM between blocks per the `cp GROWL` check ~line 145); - ROAR is unaffected since the asm never applies this quirk to it. -- Master/Ultra ball tosses flicker the OBJ palette: DoBallTossSpecial - Effects (engine/battle/animations.asm:685) XORs rOBP0 with %00111100 - after every frame block while wCurItem <= ULTRA_BALL, so the 11 toss - blocks alternate the $F0/$CC shade maps starting normal; PlayAnimation - pushes/pops rOBP0 around each subanimation row, so the ambient - palette returns when the toss ends. GREAT/POKE/SAFARI balls never - flicker, and the toss arc always follows wCurItem via - TossBallAnimation, including the ghost-dodge throw. -- Anim-layer OBJ colorization is per 8x8 attribute cell: the SGB's - ATTR_BLK regions color the composited DMG picture per cell, not per - OAM entry, so an anim sprite overlapping a zone boundary takes each - cell's palette on the pixels inside it, AnimPlayer samples the zone - under every cell an 8x8 tile touches and repaints differing cells - through a cell-clipped scissor (aligned tiles stay one draw). -- Ball wobbles (ItemUseBall): Z = X*Y/255 + status2 with - Y = rate*100/ballFactor2; <10/<30/<70 -> 0/1/2 shakes, else 3, with - the matching ItemUseBallText01-04 lines. -- Trainer class AI (data/trainers/ai_pointers.asm + - engine/battle/trainer_ai.asm): per-class item/switch routines with - wAICount uses per Pokémon, ported to data/scripts/ai_classes.lua. -- Exp (engine/battle/experience.asm): baseExp*level/7 divided by the - participant count, x1.5 for trainers, x1.5 for traded mons; stat exp - in full to each participant. -- Move sounds: data/moves/sfx.asm (sound + pitch/tempo per move). The - pitch/tempo modifiers are applied at synthesis time - (Audio2_ApplyFrequencyModifier adds pitch to every frequency write; - Audio2_SetSfxTempo scales tone-channel note lengths, noise skips it), - 128 variant WAVs keyed "<sfx>@<pitch><tempo>" that Sound.playMove - selects, exact rather than a playback-rate approximation. Per-row - sounds fire as PlayAnimation does; GROWL/ROAR (IsCryMove) play the - attacker's cry. Hit sounds by effectiveness (Damage/Super/NotVery). -- Screen-effect animations (engine/battle/animations.asm + - engine/gfx/screen_effects.asm): every SE_* is implemented per-routine, - FlashScreen/FlashScreenLong (the FlashScreenLongSGB 12-entry table), - Dark/Light/DarkenMon/Reset palette ops (shade-map permutations of the - SGB zone palettes), all SlideMon variants, ShakeBackAndForth, - BoundUpAndDown, SquishMonPic, Minimize (real MinimizedMonSprite), - spiral/shoot-balls/water-droplets/leaves emitters compiled from the - asm trajectories, per-animation-id frame-block flashes (Explosion, - Rock Slide's rumbles, Blizzard's cadence...), AnimationWavyScreen with - true per-scanline offsets, PredefShakeScreenHorizontally/Vertically - and ShakeEnemyHUD. SE rows carry the faithful blocking durations. -- SGB battle colorization (SetPal_Battle, BlkPacket_Battle, - SetAnimationPalette): the battle screen is colorized by zone, player - HUD, enemy HUD, player mon + message box, enemy mon; trainer front - pics and the player/old-man back pics take PAL_MEWMON (both species - IDs are zero at the intro, so MonsterPalettes[0]); the ghost keeps the - disguised species' palette; attack animation sprites and thrown balls - are colored through the OBJ palettes (wAnimPalette $F0 on SGB, ambient - $E4, OBP1 $6C). Headless/no-shader environments fall back to the flat - pipeline. -- Mimic resolves mid-move (MimicEffect): accuracy first, then the - player's copy menu (enemy/link copy a random slot); the copy - overwrites only the slot's move ID, PP is shared with Mimic's slot, - and reverts on switch/battle end. -- Old man tutorial (DisplayBattleMenu's BATTLE_TYPE_OLD_MAN branch): the - real scripted cursor, ▶ beside FIGHT for 80 frames, beside ITEM for - 50, ITEM force-selected into the POKé BALL x50 list; the throw always - catches at full HP (item_effects.asm jumps straight to .captured, 3 - shakes, no party/dex add, no ball consumed); backing out of the bag - replays the script. The old man never attacks, the original tutorial - is menu navigation + a guaranteed catch, nothing more. - -## Link battles (lockstep) - -- Both sides simulate with a shared Park-Miller RNG stream (host deals - the seed), identical pack/unpack-clamped party copies, no badge - boosts, and a mirrored speed-tie roll (the guest inverts it); a - canonical host-side-first state hash is exchanged per turn and any - mismatch ends the match as a draw. - -## Music (audio/engine_1.asm) - -- Note duration: `frames = length * speed * tempo / 0x100` with - fractional carry, at 60 fps (Audio1_note_length / CalculateDelay). -- Frequency: `reg = pitches[note] asr (octave - 1)` (CalculateFrequency; - the octave byte stores `8 - octave`), `f = 131072/(2048 - reg)` for - squares, halved for channel 3. -- note_type volume/fade renders as an NRx2-style envelope (step every - `fade/64` s); duty_cycle maps to 12.5/25/50/75% pulse widths; - sound_call/sound_loop honor the engine's one-level call stack and - loop counters. - -## Text & font - -- The Pokédex height row uses the real ′/″ tiles: gfx/pokedex/pokedex.png - tiles 0/1 are patched over font-extra slots $60/$61 exactly as - engine/gfx/load_pokedex_tiles.asm loads them over vChars2 (they replace - glyphs charmap.asm marks unused); ASCII `"` aliases to the closing- - quote glyph $73 so stray hand-written quotes render. - -## Validation against the original - -- `tests/run_tests.lua` pins hand-checked values: L5 Bulbasaur 19 HP / - 9 Atk at 0 DVs, L100 Mewtwo 415 HP / 406 Spc at max DVs+statExp, - MEDIUM_SLOW(5) = 135, type chart spot checks, deterministic damage - rolls, Route 1 slot 1 = L3 Pidgey. -- The autopilot run reproduces the original's early flow on real map - data: Pallet sign text, lab door warp target (5,11), Oak's Lab exit by - walking off the mat, connection into Route 1 at matching x. diff --git a/docs/differences-between-gold-silver-and-crystal.md b/docs/differences-between-gold-silver-and-crystal.md deleted file mode 100644 index 6ff4a0bf..00000000 --- a/docs/differences-between-gold-silver-and-crystal.md +++ /dev/null @@ -1,615 +0,0 @@ -# Pokémon Gold, Silver, and Crystal: A Systematic Engine, ASM, and Behavioral Comparison - -## Executive summary - -Pokémon Gold and Silver are best understood as **two data-configured builds of one engine**, whereas Pokémon Crystal is an **expanded, reorganized, Color-only derivative of that engine**. The `pret/pokegold` repository reproducibly builds the international Gold and Silver ROMs byte-for-byte, while `pret/pokecrystal` separately builds multiple international Crystal revisions and debug artifacts. This makes the two repositories unusually strong primary evidence: their output hashes correspond to known retail binaries rather than merely reimplementing observed behavior. - -The largest technical conclusion is that Crystal is **not a ground-up engine rewrite**. Core subsystems (the frame-driven sound interpreter, hardware-derived RNG, synchronized link-battle PRNG, wild-encounter probability machinery, party-mon record size, and much of the RTC pipeline) remain recognizably inherited from Gold/Silver. Crystal instead adds new ROM banks, reorganizes routines, introduces Color-specific rendering paths, fills formerly reserved fields in Pokémon records, redesigns SRAM placement, and layers Battle Tower, Mobile System GB, animated sprites, the female protagonist, Buena, Move Tutor, Suicune story logic, and Virtual Console hooks around the inherited core. - -The most important behavioral differences are therefore caused less by wholesale algorithm replacement than by **changed control flow and data**: - -| Area | Gold and Silver | Crystal | Technical consequence | -|---|---|---|---| -| Edition structure | One shared engine with edition-selected encounter, sprite, title, and other data | One enhanced-version build with regional/revision conditionals | Gold-to-Silver ports are mostly data substitutions; Gold/Silver-to-Crystal ports are architectural merges | -| Display target | Runtime distinguishes DMG/SGB/CGB modes | Explicit Color-only path | Crystal code can assume CGB palettes, VRAM banking, and related presentation behavior more aggressively | -| RNG core | Divider-register accumulator plus synchronized link PRNG | Same algorithms | Different outcomes normally arise from different call timing or paths, not a different generator | -| Party records | Two bytes after Pokérus are reserved | Those same bytes become caught-time/gender/level/location metadata | Pokémon records remain structurally compatible despite Crystal gaining metadata | -| Save layout | Main and backup data are fragmented among SRAM sections | Main and backup records become more contiguous; Crystal/Battle Tower/Mobile blocks are added | Whole `.sav` files are not drop-in interchangeable | -| Battles | Standard Gen II battle engine | Inherited engine plus Battle Tower branches and selective bug fixes | Link-compatible behavior is intentionally retained in some otherwise-fixed cases | -| Roaming Pokémon | Raikou, Entei, and Suicune use the roaming system | Only Raikou and Entei roam; Suicune becomes a scripted encounter | Encounter-state conversion requires special handling | -| Graphics | Static Pokémon fronts and DMG/SGB-compatible layouts | Animated fronts, revised sprites, richer Color layouts | Animation introduces new graphics data, decompression, frame, and timing requirements | -| Audio | Shared four-channel Game Boy music interpreter | Same interpreter plus new songs and call sites | Music ports are mostly data/pointer work rather than synthesizer rewrites | -| Major fixed defects | Coin Case arbitrary code execution, first-save Hall of Fame corruption, Lucky Number box limit, and several map/text defects | Corrected | Crystal is safer, but not "bug-fixed Gen II" in the broad sense | -| Persistent defects | Many Gen II battle, capture, item, and badge defects | Many remain | Mechanical compatibility often outweighed cleanup | - -Gold and Silver themselves have almost no meaningful engine-level divergence. Their scientifically interesting comparison is principally **which tables and assets are selected at assembly time**. Crystal is the meaningful engine comparison. - -The report assumes a technically literate audience familiar with low-level programming but not necessarily Game Boy internals. Unless explicitly stated, "Crystal" means the international English code represented by `pret/pokecrystal`; Japanese Mobile System GB and Australian/revision-specific behavior are treated as regional variants. "Scientific" here means reproducible binary-derived comparison, explicit source hierarchy, and separation of direct evidence from inference, not laboratory experimentation involving original development source code, which has not been publicly released. - -## Scope, evidence, and reproducibility - -### Source hierarchy - -The strongest sources are the two pret disassemblies: - -- [`pret/pokegold`](https://github.com/pret/pokegold), which builds exact Gold and Silver international ROM images. -- [`pret/pokecrystal`](https://github.com/pret/pokecrystal), which builds Crystal international v1.0, v1.1, Australian, and debug images. - -The repositories identify exact SHA-1 outputs. Gold builds to `d8b8a3600a465308c9953dfa04f0081c05bdcb94`, Silver to `49b163f7e57702bc939d642a18f591de55d92dae`, Crystal international v1.0 to `f4cd194bdee0d04ca4eac29e09b8e4e9d818c133`, and Crystal international v1.1 to `f2f52230b536214ef7c9924f483392993e226cfb`. Those reproducible identities are critical: a label such as `BadgeStatBoosts` is a reverse-engineered name, but the emitted bytes and resulting behavior are those of the retail ROM. - -Secondary sources such as Bulbapedia and Serebii are useful for player-visible cross-checking, release differences, encounter availability, and historical context. The pret bug documents are stronger than ordinary wiki summaries for defects because they identify the responsible instructions and provide corrective diffs. Official Nintendo and Pokémon sources are most valuable for release/Virtual Console behavior and supported transfer paths, but they do not publish the games' assembly architecture. - -### Reproducible comparison method - -A rigorous comparison should operate at four levels: - -| Level | Method | What it establishes | Main limitation | -|---|---|---|---| -| Binary identity | Build each repository with its pinned RGBDS-compatible toolchain and verify SHA-1 | The source corresponds to the target retail ROM | Does not explain why bytes differ | -| Source topology | Compare `main.asm`, `home.asm`, bank sections, include graphs, WRAM/SRAM declarations | Architectural additions, removals, and relocation | File organization can change without behavioral change | -| Routine semantics | Compare labels, branches, register use, calls, data offsets, and side effects | Control-flow and algorithmic differences | Labels and comments are community-derived | -| Behavioral validation | Run controlled emulator tests with trace logging and fixed input timing | Observable consequences and RNG/cycle sensitivity | Emulator accuracy and RTC state must be controlled | - -For a formal experiment, the recommended independent variables are edition, ROM revision, initial SRAM, RTC register state, boot hardware mode, input sequence, and link mode. Dependent variables include RNG bytes, encounter choice, battle result, SRAM writes, audio commands, VRAM/OAM writes, and cycle/frame number. Because the ordinary RNG reads the divider register, deterministic comparisons must start from a controlled boot state and reproduce input timing at frame or cycle precision. - -### Gold versus Silver as a build-configuration comparison - -The fact that `pokegold` emits both Gold and Silver from one repository is itself strong evidence that the two versions share the same broad code architecture. Edition differences are implemented through conditional assembly and alternate data or assets rather than through separate engines. The obvious behavioral results are version-specific wild Pokémon, title graphics, mascot-facing presentation, and selected NPC or table data; battle arithmetic, saves, RTC processing, audio interpretation, and link handling come from the same shared source. - -This distinction matters for analysis. Treating Gold, Silver, and Crystal as three equally separate engines exaggerates Gold/Silver differences and understates Crystal's structural changes. A better model is: - -```mermaid -flowchart TD - GSC[Generation II design and data model] - GS[Gold/Silver shared engine] - GD[Gold-selected data and assets] - SD[Silver-selected data and assets] - C[Crystal derivative engine] - CF[Crystal-only feature modules] - CR[Crystal revisions and regional flags] - - GSC --> GS - GS --> GD - GS --> SD - GS --> C - C --> CF - C --> CR -``` - -## ROM and engine architecture - -### Banked execution model - -The games execute on the Game Boy's LR35902-class CPU and are organized around a fixed home bank plus switchable ROM banks. Calls across banks generally pass through wrappers that save the current bank, select the target bank, call the routine, and restore the original bank. The `BattleRandom` wrapper is a concise example: `_BattleRandom` resides outside the home bank, so the wrapper saves `hROMBank`, uses the bank-switch restart vector, calls the routine, preserves its return value, and restores the previous bank. - -```asm -BattleRandom:: - ldh a, [hROMBank] - push af - ld a, BANK(_BattleRandom) - rst Bankswitch - call _BattleRandom - ... - pop af - rst Bankswitch - ret -``` - -Relevant sources: [Gold/Silver `home/random.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm#L29-L46) and [Crystal `home/random.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm#L29-L46). - -The repositories' top-level `main.asm` files are effectively link manifests for this architecture. Gold/Silver's file groups the battle core, encounters, party menu, RTC, phone, Pokégear, sprite animation, graphics loading, and other systems into named ROM sections. Crystal retains those categories but adds explicitly named feature sections and many additional modules. - -### Crystal as an extension and bank-layout rewrite - -Crystal's `main.asm` introduces a conspicuous `Crystal Features 1` section containing gender initialization, Kris-specific bag handling, Move Tutor, Crystal layouts, Celebi logic, the redesigned main menu, Mobile menu code, owned-Pokémon search, and Buena's menu. Nearby banks add Battle Tower trainer logic, caught-data handling, a revised stats screen, sliding battle intros, battle-scene checks, and the Color-only startup screen. - -That include topology supports three conclusions. - -First, Crystal preserves **vertical subsystem continuity**. The battle engine is still `engine/battle/core.asm`; encounters remain `engine/overworld/wildmons.asm`; Pokémon records still derive from the same party/box macros; and audio is still driven by the same interpreter. - -Second, Crystal adds **horizontal feature coupling**. Battle Tower touches battle setup, stat boosts, experience rules, saving, menus, SRAM, trainers, music, and link-like constraints. The female protagonist touches initialization, sprites, bag graphics, caught metadata, and menu presentation. Mobile support touches interrupts, SRAM, menus, communications, rankings, and audio. - -Third, code and data relocation is substantial enough that raw ROM addresses from Gold/Silver cannot generally be transplanted into Crystal. A symbolic port based on labels and structures is practical; a patch based on absolute offsets is brittle. - -### File and routine comparison - -| Subsystem | Gold/Silver source | Crystal source | ASM-level difference | Porting significance | -|---|---|---|---|---| -| Top-level bank graph | [`main.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/main.asm) | [`main.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/main.asm) | Crystal adds feature, Battle Tower, Mobile, gender, animation, and caught-data modules and relocates shared includes | Port by symbol and feature dependency, not ROM offset | -| Ordinary RNG | [`home/random.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm) | [`home/random.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm) | Essentially identical routine and rejection-sampling helper | RNG-sensitive ports must preserve call timing | -| Battle core | [`engine/battle/core.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/battle/core.asm) | [`engine/battle/core.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/battle/core.asm) | Crystal adds Battle Tower guards and conditional fixes while retaining link behavior | Mechanical changes can desynchronize unmodified peers | -| Encounters | [`engine/overworld/wildmons.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/overworld/wildmons.asm) | [`engine/overworld/wildmons.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/overworld/wildmons.asm) | Same rate/slot pipeline; different swarm state and two rather than three roamers | World-state conversion needs special Suicune handling | -| SRAM declaration | [`ram/sram.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/ram/sram.asm) | [`ram/sram.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/ram/sram.asm) | Crystal consolidates save/backup regions and adds GS Ball, Battle Tower, rankings, and Mobile blocks | Whole-save binary compatibility is lost | -| Pokémon structures | [`pokemon_data_constants.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/constants/pokemon_data_constants.asm#L69-L103) | [`pokemon_data_constants.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/constants/pokemon_data_constants.asm#L69-L108) | Crystal assigns caught metadata to two bytes reserved in Gold/Silver | Record length remains compatible | -| RTC/home time | [`home/time.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/time.asm) | [`home/time.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/time.asm) | Same RTC model; Crystal timer interrupt can dispatch Mobile timing | Emulator ports must emulate RTC and, for Japanese features, mobile timing assumptions | -| Picture loading | [`engine/gfx/load_pics.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/gfx/load_pics.asm) | [`engine/gfx/load_pics.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/gfx/load_pics.asm) | Crystal has additional front-picture and animation-oriented handling | Static sprite replacement is insufficient | -| Sound interpreter | [`audio/engine.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/audio/engine.asm) | [`audio/engine.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/audio/engine.asm) | Core interpreter is structurally the same | New Crystal music is primarily sequenced data and pointers | -| Song table | [`audio/music_pointers.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/audio/music_pointers.asm) | [`audio/music_pointers.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/audio/music_pointers.asm#L97-L108) | Crystal appends ten named songs | Existing song IDs remain stable through the Gold/Silver range | - -The line counts of several Crystal files are larger, but line count alone is not a behavioral metric; comments, label quality, and source refactoring affect it. The stronger evidence is the presence of new branches, fields, tables, and externally observable side effects. - -### Hardware-mode divergence - -Gold/Silver initialization tests whether the console is a Game Boy Color and maintains non-CGB paths, including Super Game Boy initialization. Crystal includes an explicit `gbc_only.asm` module that displays the incompatibility message on non-CGB hardware. That is not merely marketing metadata: it changes what assumptions later rendering code may safely make about palettes, VRAM banking, and Color hardware. - -For a reverse port of Crystal features into Gold/Silver, every CGB-only feature falls into one of three categories: provide a monochrome/SGB fallback, disable it outside CGB mode, or intentionally convert the resulting ROM into a Color-only build. Skipping that decision produces subtle failures rather than one clean compile error: palette attributes, second-bank VRAM data, and tile-upload timing can all be implicated. - -## Save, RNG, clock, and entity state - -### Save architecture - -Gold/Silver divide their main game state into options, three player-data fragments, current-map data, Pokémon data, and a checksum. Backup pieces are distributed among several separately placed SRAM sections. The current PC box is separate, the fourteen inactive boxes occupy exactly two SRAM banks, and mail and Mystery Gift have dedicated regions. - -Crystal instead defines a contiguous `sGameData` consisting of player, map, and Pokémon data, pads the region, and stores a checksum. Its backup is similarly grouped. Crystal then adds a GS Ball flag, `sCrystalData`, Battle Tower progress and recent-trainer state, ranking records, Mobile communication data, offers, credentials, and Japanese Mobile-specific buffers. The source explicitly notes that the international `sCrystalData` location differs from Japanese Crystal. - -| Save component | Gold/Silver | Crystal | -|---|---|---| -| Options and corruption sentinels | Present | Present | -| Main player/map/Pokémon data | Split into labeled player fragments inside the save region | Grouped into one contiguous main-game block | -| Main checksum | 16-bit checksum after game data | 16-bit checksum after padded game-data region | -| Backup | Fragmented among multiple SRAM sections | Consolidated backup game-data block | -| Current box | Separate `curbox` structure | Separate full `box` structure plus padding | -| Inactive boxes | Fourteen boxes over two SRAM banks | Same fourteen-box capacity over two banks | -| Party and mailbox mail | Dedicated primary and backup regions | Same broad model | -| GS Ball state | Absent | Primary and backup flags | -| Battle Tower | Absent | Challenge state, streak, previous teams, reward | -| Mobile/rankings | Absent | Several additional SRAM sections | -| Full-file compatibility | Gold and Silver are closely related but still edition-specific | Not layout-compatible with Gold/Silver | - -Crystal's save routine makes the transactional sequence visible: - -```asm -_SaveGameData: - farcall StageRTCTimeForSave - farcall BackupMysteryGift - call ValidateSave - call SaveOptions - call SavePlayerData - call SavePokemonData - call SaveBox - call SaveChecksum - call ValidateBackupSave - ... - farcall BackupGSBallFlag - farcall SaveRTC -``` - -Source: [Crystal `engine/menus/save.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/menus/save.asm#L255-L284). The routine stages RTC state, writes the primary data and box, computes the primary checksum, writes backup data and checksum, backs up party mail and GS Ball state, persists RTC state, and normalizes a completed Battle Tower reward state. - -The practical conclusion is precise: **do not migrate a Gold/Silver `.sav` to Crystal by copying the file or the main save block**. A converter should parse symbolic fields, validate checksums and sentinels, copy player/map/Pokémon/box/mail data field by field, initialize Crystal-only state, and regenerate Crystal checksums. Gold-to-Silver conversion is less structurally disruptive, but version-dependent event and encounter state should still be treated deliberately. - -### Party and boxed-Pokémon records - -One of Crystal's most elegant compatibility decisions is visible at the structure-definition level. In Gold/Silver, the two bytes after `MON_POKERUS` are simply reserved: - -```asm -DEF MON_HAPPINESS rb -DEF MON_POKERUS rb - rb_skip 2 -DEF MON_LEVEL rb -``` - -In Crystal, the same positions are assigned to a two-byte caught-data field: - -```asm -DEF MON_HAPPINESS rb -DEF MON_POKERUS rb -DEF MON_CAUGHTDATA rw -DEF MON_LEVEL rb -``` - -Sources: [Gold/Silver structure](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/constants/pokemon_data_constants.asm#L83-L101) and [Crystal structure](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/constants/pokemon_data_constants.asm#L83-L107). - -Crystal packs caught time and trainer gender into one byte and caught level and location into another. Because Game Freak repurposed reserved bytes instead of extending the record, `BOXMON_STRUCT_LENGTH` and `PARTYMON_STRUCT_LENGTH` remain compatible. Gold/Silver can carry those bytes without understanding them; Crystal can interpret them when present. This is a major reason Gen II Pokémon can move among Gold, Silver, and Crystal without a record-size translation layer. - -A second structure change appears in the base-species TM/HM compatibility bitset. Gold/Silver size it for `NUM_TM_HM`; Crystal sizes it for `NUM_TM_HM_TUTOR`, accommodating Move Tutor compatibility in the same species-data model. This means a straight copy of Gold/Silver base-stat records into Crystal must account for Crystal's expanded learnability domain even if the visible species stats are unchanged. - -### Item and party handling - -The basic inventory model (separate item pockets, held-item byte in each Pokémon record, party length of six, PC boxes, mail records, and item-dispatch routines) is inherited. Crystal's changes are concentrated in new consumers and UI behavior: Move Tutor eligibility, gender-specific player presentation, caught metadata, Battle Tower restrictions, and additional event items such as GS Ball state. - -Crystal did **not** comprehensively repair the item mechanics. Its documented defects still include Moon Ball not applying its intended multiplier, Love Ball checking the wrong gender relationship, Fast Ball applying to only a few species, three incorrect Heavy Ball weight cases, and status conditions failing to affect capture rate as intended. Those defects live in shared mechanical paths and are important when evaluating a "faithful" engine port: correcting them changes gameplay and may alter deterministic test vectors. - -### Ordinary RNG - -Gold/Silver and Crystal use the same ordinary RNG routine. It samples the hardware divider register and updates two one-byte accumulators, one by addition-with-carry and one by subtraction-with-carry: - -```asm -ldh a, [rDIV] -ld b, a -ldh a, [hRandomAdd] -adc b -ldh [hRandomAdd], a - -ldh a, [rDIV] -ld b, a -ldh a, [hRandomSub] -sbc b -ldh [hRandomSub], a -``` - -Sources: [Gold/Silver](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm#L13-L27) and [Crystal](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm#L13-L27). The files are effectively identical, including the `RandomRange` rejection-sampling routine used to avoid simple modulo bias. - -This generator is not a self-contained seeded PRNG in the modern sense. Its output depends on divider phase, previous accumulator state, carry state, VBlank updates, and when the routine is reached. Consequently, adding an animation, menu delay, conditional call, or extra random-consuming feature can alter later outcomes even while the RNG instructions remain unchanged. - -### Link-battle PRNG - -Battles route randomness through `BattleRandom`. In non-link play, `_BattleRandom` ultimately uses the ordinary RNG. In linked battles, both systems consume a shared ten-byte random sequence and advance bytes with the recurrence: - -``` -x[n+1] = (5 * x[n] + 1) mod 256 -``` - -The Gold/Silver and Crystal battle cores contain the same broad synchronization mechanism. - -This has a major compatibility consequence: a battle patch can be logically correct in isolation yet break link play if it causes one participant to make a different number or order of RNG calls. That is why Crystal sometimes preserves Gold/Silver behavior specifically in link battles. Synchronization depends on matching control flow, not merely using the same random-number formula. - -### RTC and time-of-day behavior - -Both codebases latch the MBC3-style real-time clock, read seconds, minutes, hours, and the low/high day registers, normalize time, add the player's selected starting offset, and derive the current time-of-day state. Crystal's `UpdateTime` remains a short chain of `GetClock`, `FixDays`, `FixTime`, and `GetTimeOfDay`. - -Crystal's source shows the day counter reduced modulo 140 for the game's weekly/event model, with status flags distinguishing an RTC count beyond 139 days and a hardware day-high overflow beyond 255 days. The displayed game time is formed by adding the new-game start offsets to RTC values with carry through seconds, minutes, hours, and day. - -The notable architectural addition is the Crystal timer interrupt's Mobile dispatch: - -```asm -Timer:: - push af - ldh a, [hMobile] - and a - jr z, .not_mobile - call MobileTimer -.not_mobile - pop af - reti -``` - -Source: [Crystal `home/time.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/time.asm#L2-L11). The ordinary RTC logic remains inherited; Japanese/mobile Crystal adds another timing consumer. - -For emulators, flash cartridges, and ports, SRAM alone is insufficient. RTC registers, halt/carry state, elapsed host time, and game offsets must be preserved consistently. A save imported without corresponding RTC state may be structurally valid yet produce incorrect daily events, phone behavior, berries, swarms, day-of-week encounters, or time-dependent evolutions. - -## Battles and encounters - -### Battle-engine inheritance - -The core Gen II battle model is shared: turn selection, speed ordering, accuracy, damage, stat stages, held items, status, volatile effects, experience, capture, AI, and link synchronization follow the same broad engine. Crystal's battle core is larger principally because it adds Battle Tower integration, additional presentation paths, and selective corrections. - -Crystal is therefore mechanically closer to a patched and extended Gold/Silver than to a later-generation ruleset. There are no abilities, natures, modern physical/special move split, or rewritten damage model. Its new content operates within Gen II's existing move and Pokémon structures. - -### Control-flow difference: Battle Tower exclusions - -Gold/Silver's `BadgeStatBoosts` exits for link battles and otherwise applies badge boosts. Crystal adds a second early return when `wInBattleTowerBattle` is nonzero: - -```asm -ld a, [wLinkMode] -and a -ret nz - -ld a, [wInBattleTowerBattle] -and a -ret nz -``` - -Source: [Crystal `BadgeStatBoosts`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/battle/core.asm#L6526-L6532). Gold/Silver have only the link-mode guard at the corresponding point. - -This tiny six-instruction addition is representative of Crystal's design. The stat algorithm was not replaced; Crystal introduced a new battle context and inserted a guard so adventure-only badge advantages do not leak into the standardized Battle Tower. Similar context checks appear around experience and other post-battle handling. - -### Selective fixes constrained by link compatibility - -The pret Crystal bug documentation explicitly states that Crystal fixed Gold/Silver's Reflect/Light Screen overflow and Present damage behavior only where doing so would not break ordinary cross-version link battles. Link-mode behavior retains the compatible path. - -That is an important historical engineering tradeoff. A fully corrected Crystal battle engine would disagree with Gold/Silver on intermediate values and potentially on RNG consumption, damage, fainting, or message sequence. Compatibility with the installed base was treated as a protocol requirement. Consequently, "Crystal fixed the bug" can mean **fixed in single-player but deliberately retained in linked simulation**. - -### Shared and persistent battle defects - -Crystal retains a substantial Gen II defect surface. Documented examples include: - -| Defect | Mechanical effect | Crystal status | -|---|---|---| -| "100%" secondary effects | Fail in 1/256 qualifying cases | Retained | -| Belly Drum | May maximize Attack even when the HP requirement is not properly met | Retained | -| Berserk Gene | Confusion duration can become 256 turns or inherit stale state | Retained | -| Confusion damage | Can receive type-item and Explosion/Self-Destruct modifiers | Retained | -| Beat Up | Can behave incorrectly and can desynchronize link battles | Retained | -| Return/Frustration edge | Can produce zero damage at extreme happiness values | Retained | -| Dragon boosting item | Dragon Scale is checked instead of Dragon Fang | Retained | -| Glacier Badge | Special Defense boost depends incorrectly on overwritten accumulator state | Retained | -| Capture status bonus | Burn, poison, and paralysis do not contribute as intended | Retained | -| Specialty Balls | Multiple Apricorn-ball formulas target the wrong condition or table | Retained | - -These are documented against original Crystal code, not inferred from modern competitive summaries. - -The Glacier Badge defect is especially instructive at ASM level. The routine shifts a badge bitfield through register `b`, calls `BoostStat`, and later reuses register `a` as if it still contained the expected badge value. `BoostStat` can overwrite `a`, so whether Special Defense receives its boost depends on the Special Attack calculation's resulting register state. The bug survived Crystal even though the source gained an explicit comment identifying it. - -### Gold/Silver defects corrected in Crystal - -The `pokegold` bug document separates defects fixed in Crystal from defects still shared with it. It identifies seven clear Gold/Silver corrections: Coin Case arbitrary code execution, Hall of Fame corruption when no prior save exists, Lucky Number failure to inspect boxes 10-14, Present text overflow, surfing onto NPCs, fishing inside Cerulean Gym, and Route 15 capitalization. - -The Coin Case correction is one bytecode-level terminator change: - -```diff - text "Coins:" - line "@" - text_decimal wCoins, 2, 4 --done -+text_end -``` - -Source: [`pokegold/docs/bugs_and_glitches.md`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/docs/bugs_and_glitches.md#L20-L34). The Gold/Silver terminator permits text-command execution to continue into unintended memory under exploitable conditions; Crystal ends the text stream correctly. - -The Hall of Fame correction adds a saved-at-least-once check and erases/initializes previous-save structures before attempting the Hall of Fame write. The Lucky Number correction changes the loop bound from the Japanese box count constant to the international fourteen-box count. Both are good examples of Crystal correcting localization-sensitive state assumptions rather than changing game design. - -### Encounter-rate algorithm - -Gold/Silver and Crystal use essentially the same high-level encounter pipeline: - -```mermaid -flowchart TD - STEP[Eligible movement step] - RATE[Read morning/day/night or water rate] - MOD1[Apply radio modifier] - MOD2[Apply Cleanse Tag modifier] - ROLL[Call Random and compare with rate] - SLOT[Choose encounter slot] - ROAM[Check roaming Pokémon] - REPEL[Apply Repel level check] - BATTLE[Stage wild battle] - - STEP --> RATE --> MOD1 --> MOD2 --> ROLL - ROLL -->|pass| SLOT - SLOT --> ROAM - ROAM --> REPEL - REPEL --> BATTLE -``` - -In both engines, `TryWildEncounter` gets the map rate, applies Pokémon March/Ruins of Alph doubling or Pokémon Lullaby halving, applies Cleanse Tag halving, draws an RNG byte, chooses a slot from grass or water probability tables, and checks Repel. - -Grass records contain separate morning, day, and night slot blocks; the active block is selected through `wTimeOfDay`. Water uses a separate rate and three-slot table. The structure constants remain seven grass slots and three water slots in both codebases. - -### Encounter-table differences - -Gold and Silver primarily diverge through edition-conditioned tables. Crystal has its own consolidated tables and changes both availability and placement. It makes several former Gold/Silver exclusives obtainable in one edition, while removing some species available in both base versions; Mareep's evolutionary family is a prominent Crystal omission. Crystal also moves species such as Sneasel to different locations. - -The engine/table distinction is important. An encounter may differ because: - -1. the map's encounter-rate byte changed; -2. a morning/day/night slot changed; -3. the slot's level changed; -4. a swarm override changed; -5. a roaming Pokémon was removed from the roaming subsystem; -6. the map itself gained or lost encounter-enabled tiles. - -A robust diff should therefore compare map headers, wild tables, swarm flags, and roaming initialization, not merely produce a Pokédex availability list. - -### Swarms and roaming state - -Gold/Silver's swarm lookup is comparatively generic: it compares the current map against `wSwarmMapGroup` and `wSwarmMapNumber`. Crystal's code has explicit Dunsparce and Yanma swarm flags and corresponding map state. That is a data-model specialization, not just a changed encounter table. - -The roaming difference is even clearer. Gold/Silver initialize three records: - -```asm -ld a, RAIKOU -ld [wRoamMon1Species], a -ld a, ENTEI -ld [wRoamMon2Species], a -ld a, SUICUNE -ld [wRoamMon3Species], a -``` - -Crystal initializes only Raikou and Entei: - -```asm -ld a, RAIKOU -ld [wRoamMon1Species], a -ld a, ENTEI -ld [wRoamMon2Species], a -``` - -Sources: [Gold/Silver `InitRoamMons`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/overworld/wildmons.asm#L471-L511) and [Crystal `InitRoamMons`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/overworld/wildmons.asm#L476-L506). - -Gold/Silver's subsequent selection logic describes an equal choice among three beasts after the roaming check succeeds; Crystal changes the corresponding comment and index range to two. Suicune's removal is tied to Crystal's expanded Eusine/Suicune plot and scripted Tin Tower encounter. - -For save conversion, a Gold/Silver Suicune roaming record cannot simply remain active in Crystal. The converter must map capture/defeat/event state into Crystal's scripted Suicune flags or deliberately define a hybrid behavior. - -## Graphics and audio - -### Tile, palette, and sprite model - -All three games retain the Game Boy's tile-oriented rendering model: graphics are stored as compact tile data, decompressed or copied into VRAM, arranged through background/window tilemaps, and supplemented by hardware sprites through shadow OAM. Pokémon and many UI images use four-color source palettes, with separate palette data determining their actual colors. The pokecrystal FAQ explicitly notes the four-color paletted-PNG convention and the distinction between image data and palette data. - -Gold/Silver must support original Game Boy-style output and Super Game Boy behavior as well as Game Boy Color enhancements. Crystal's explicit Color-only startup path lets it make stronger use of CGB layout and palette machinery. Its top-level architecture adds `crystal_layouts.asm`, dedicated player graphics, revised map and battle presentation, and other CGB-focused modules. - -### Animated Pokémon sprites - -Crystal's headline renderer change is animated front sprites. Every Pokémon receives an entrance animation, and the status/profile viewer can play a longer animation; several designs, palettes, and back sprites were also revised. Crystal additionally gives the legendary beasts distinct overworld sprites and introduces richer trade-screen presentation. - -At the engine level, this requires more than storing extra frames. A full animation path needs: - -- a base front picture and additional frame or bitmask data; -- frame sequencing and duration state; -- tile-buffer reconstruction or differential tile updates; -- synchronization with battle intro control flow; -- palette and VRAM updates during safe LCD periods; -- fallback behavior for static contexts such as link displays or icons. - -Crystal's `load_pics.asm` is correspondingly larger and is integrated with dedicated animation and battle-intro modules, while Gold/Silver's loader primarily services static front/back pictures. The exact file-size difference should not be treated as a performance measurement, but the added code paths confirm a broader graphics pipeline. - -Animated fronts can also alter RNG-observable timing indirectly. The ordinary RNG is updated from divider timing and VBlank activity; therefore, any test that compares post-animation random outcomes must control whether animations are enabled and how many frames elapsed. This is an inference from the animation and RNG architectures, not evidence that every animation directly invokes `Random`. - -### Color and protagonist handling - -Crystal adds player-gender initialization and Kris-specific bag/player graphics. The Pokémon record's caught-data bits can record whether the catcher was the boy or girl protagonist. This is an example of a feature spanning UI, overworld sprites, menus, Pokémon metadata, and save state rather than existing in one isolated "female player" switch. - -A Crystal-to-Gold/Silver backport must decide how to handle caught-by-girl metadata when Gold/Silver have no female protagonist UI. Structurally the bytes can survive because they were reserved, but Gold/Silver will not natively display or generate the metadata. - -### Sound-engine architecture - -The most striking audio result is how little the interpreter changed. Both repositories' `audio/engine.asm` identify themselves as the entire sound engine, update once per frame, parse music commands, maintain eight software channel structures (four music and four sound-effect channels), and ultimately drive the four Game Boy audio channels. Both implementations handle duty, envelope, frequency, vibrato, noise sampling, channel muting, low-health sound, and fades through the same broad code. - -```mermaid -flowchart LR - SONG[Song bytecode] - PARSE[ParseMusic] - STATE[Per-channel WRAM state] - FX[Vibrato / pitch / envelope / noise] - MIX[Music-SFX priority and routing] - APU[Game Boy audio registers] - - SONG --> PARSE --> STATE --> FX --> MIX --> APU -``` - -Crystal therefore does not introduce a new synthesizer. It extends the music corpus and invokes new songs in new contexts. - -### Crystal's additional music - -Gold/Silver's song pointer table ends at `Music_PostCredits`. Crystal retains that ordering and appends ten entries: - -| Crystal-only pointer entry | Use | -|---|---| -| `Music_Clair` | Clair-related scene | -| `Music_MobileAdapterMenu` | Japanese Mobile menu | -| `Music_MobileAdapter` | Mobile connectivity | -| `Music_BuenasPassword` | Buena's Password | -| `Music_LookMysticalMan` | Eusine encounter | -| `Music_CrystalOpening` | Revised opening | -| `Music_BattleTowerTheme` | Battle Tower battle/context | -| `Music_SuicuneBattle` | Legendary-beast/Suicune battle theme | -| `Music_BattleTowerLobby` | Battle Tower lobby | -| `Music_MobileCenter` | Japanese Mobile Center | - -The pointer table labels these explicitly as "new to Crystal." - -Bulbapedia independently notes that Crystal gives the legendary beasts a unique battle theme and presents it as the first core-series special legendary battle music. - -From a porting perspective, bringing Crystal music into Gold/Silver primarily requires assigning ROM space, importing sequence data, adding pointer/constants entries, and adding selection call sites. Replacing the audio engine is generally unnecessary. However, preserving existing numeric song IDs is wise because map headers, scripts, battle setup, radio state, and fades refer to those constants. - -## Bugs, compatibility, porting, and reverse-engineering timeline - -### Bug and quirk matrix - -| Category | Gold/Silver | Crystal | Engineering interpretation | -|---|---|---|---| -| Coin Case text terminator | Exploitable continuation can permit arbitrary code execution | Correct terminator | Parser/data correction | -| First-save Hall of Fame | Can corrupt PC boxes | Guard and initialization added | Save-state precondition correction | -| Lucky Number boxes | International boxes 10-14 omitted | Uses full box count | Localization constant corrected | -| Surf onto NPC | Possible | Facing-object check added | Collision precondition corrected | -| Cerulean Gym fishing | Enabled by map fish group | Disabled | Map-header data correction | -| Reflect/Light Screen overflow | Incorrect | Fixed outside compatible link path | Context-dependent battle fix | -| Present damage | Incorrect | Fixed outside compatible link path | Protocol-preserving fix | -| Glacier Badge Special Defense | Bugged | Still bugged | Shared inherited defect | -| Apricorn specialty balls | Multiple formula defects | Still defective | Shared inherited mechanics | -| Beat Up synchronization | Vulnerable | Still vulnerable | Shared link-engine defect | -| Secondary-effect 1/256 failure | Present | Present | Shared probability comparison defect | -| Animated-sprite quirks | Not applicable | New animation-specific defects possible | Feature expansion creates new failure surface | - -The Gold/Silver and Crystal bug documents should be read together. The Gold/Silver document intentionally lists only bugs that Crystal fixed; any shared defects are documented in the Crystal repository instead. - -Crystal is thus more polished but not mechanically "corrected" in a comprehensive sense. A modern source port must choose a compatibility target: - -- **Retail-faithful:** preserve all version-specific bugs and timing. -- **Crystal-faithful:** preserve Crystal's selective fixes and its link-mode exceptions. -- **Corrected Gen II:** repair documented defects, accepting that link compatibility and historical RNG traces may change. -- **Hybrid:** gate corrections behind flags or negotiate them between identical modified link peers. - -### ROM revisions and regional code - -The Crystal repository builds international v1.0, v1.1, and Australian releases. Its FAQ states that v1.1 corrected some issues in the initial international release, while the Australian build is based on v1.1 and censors gambling references. Thus "Crystal behavior" is not completely singular even within English-language retail ROMs. - -A serious test report should always state the target hash rather than merely "Pokémon Crystal." Otherwise, an observed difference may be edition-level, v1.0/v1.1-level, Australian localization, Japanese Mobile code, Virtual Console patching, emulator behavior, or an altered ROM. - -### Link and trade compatibility - -Gold, Silver, and Crystal share the Gen II Pokémon record length and battle/link architecture, allowing ordinary same-generation trading and battling. The retained two-byte record size, shared `REDMON_STRUCT_LENGTH` conversion constant for Time Capsule interaction, and synchronized battle RNG are all source-level evidence of compatibility-oriented design. - -However, three distinct notions of compatibility must not be conflated: - -| Compatibility type | Status | -|---|---| -| Pokémon record compatibility | Strong: same record length; Crystal fills reserved bytes | -| Link protocol/battle simulation compatibility | Strong for retail games, partly because Crystal preserves old link behavior | -| Whole-save-file compatibility | Weak: Crystal's SRAM organization and added state differ substantially | -| ROM patch-address compatibility | Weak: bank layout and code placement differ | -| Feature-source portability | Moderate: shared architecture helps, but dependencies are broad | -| Gen I Time Capsule data compatibility | Supported through dedicated conversion structures and restrictions | -| Modern transfer compatibility | Supported from 3DS VC releases through Poké Transporter and Pokémon Bank | - -Nintendo's support documentation lists the Virtual Console releases of Gold, Silver, and Crystal as compatible with Poké Transporter. Transfers proceed into Pokémon Bank and can then move onward to Pokémon HOME, but that modern path is not a raw Gen II save conversion and is effectively one-way at later stages. - -### Virtual Console modifications - -Crystal's save code contains an explicit `vc_hook` after Hall of Fame insertion. The hook sets the primary and backup GS Ball flags to make the GS Ball quest and Celebi encounter available, with compile-time assertions pinning the expected SRAM addresses and flag value. - -```asm -vc_hook Enable_GS_Ball_mobile_event -vc_assert BANK(sGSBallFlag) == $1 -vc_assert BANK(sGSBallFlagBackup) == $1 -``` - -Source: [Crystal `engine/menus/save.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/menus/save.asm#L157-L167). - -This is a fascinating compatibility shim: the original international cartridge retained GS Ball-related structures but lacked the original Japanese distribution path, so the Virtual Console wrapper activates the event through a targeted runtime hook. The Pokémon Company confirms that the Virtual Console version permits the Celebi encounter at Ilex Forest's shrine. - -The pret repositories also contain `vc` build material, and community GitHub discussion has addressed preserving or generating Virtual Console patches. A port targeting 3DS VC behavior should therefore compare not only the retail ROM but the external patch/hook layer. - -### Japanese Crystal and mapper/SRAM concerns - -Crystal's SRAM source reserves several Mobile sections and explicitly comments that a Mobile Easy Chat initialization routine uses an "MBC30 bank" available to Japanese Crystal but inaccessible with ordinary MBC3 behavior. This is direct evidence that regional hardware assumptions matter when emulating or reproducing Japanese Mobile features. - -An implementation that supports only the common international MBC3-style SRAM/RTC configuration may run the international game correctly yet fail Japanese Crystal's extended Mobile storage accesses. Conversely, allocating the larger memory blindly does not implement Mobile Adapter protocols, timer behavior, ranking checksums, or server-era workflows. - -### Porting guidance - -The safest migration strategy between the two disassemblies is subsystem-oriented: - -| Port direction | Recommended approach | Main hazard | -|---|---|---| -| Gold <-> Silver data | Preserve shared engine; switch edition tables/assets | Conditional data references and event assumptions | -| Crystal feature -> Gold/Silver | Import feature plus all WRAM/SRAM, graphics, script, audio, and menu dependencies | DMG/SGB fallback and bank-space pressure | -| Gold/Silver behavior -> Crystal | Replace data or selectively restore old branch | Crystal story/event state may expect new semantics | -| Gold/Silver save -> Crystal | Parse and rebuild symbolic fields | Relocated blocks and Crystal-only state | -| Crystal save -> Gold/Silver | Strip Crystal state and preserve common Pokémon/player data | Caught metadata becomes opaque; Suicune/Battle Tower state has no target | -| Retail battle fix | Gate by non-link mode or require identical modified peers | RNG/control-flow desynchronization | -| Crystal graphics -> DMG-compatible engine | Create monochrome layouts or make build CGB-only | Palette attributes and animation timing | -| Japanese Mobile feature port | Emulate mapper, extended SRAM, timer, and communication assumptions | Hardware and defunct-service dependencies | - -Bank space is a concrete constraint. The pokecrystal FAQ describes the international ROM as 2 MiB divided across banks and notes that adding features can overflow fixed bank placement, requiring sections to be moved through the linker script. Crystal's extra features already consume carefully arranged banks, so a port that compiles at the object level can still fail at link time. - -The deepest practical rule is: **port invariants before routines**. Before copying code, define the target's expected record lengths, bank-switch convention, WRAM variables, SRAM addresses, script command set, palette mode, RNG call contract, and link-mode semantics. Once those invariants match, most inherited Gen II routines are straightforward. Without them, a perfectly copied routine can read the wrong field, switch to the wrong bank, write outside a save block, or desynchronize a link battle. - -### Release and reverse-engineering evidence timeline - -```mermaid -timeline - title Gold, Silver, and Crystal technical lineage - 1999-11-21 : Gold and Silver released in Japan - 2000-10-15 : Gold and Silver released in North America - 2000-12-14 : Crystal released in Japan - 2001 : International Crystal releases - : Crystal adds Color-only presentation, animations, Battle Tower, and expanded Suicune story - 2017-09-22 : Gold and Silver released for Nintendo 3DS Virtual Console - 2018-01-26 : Crystal released for Nintendo 3DS Virtual Console - : VC hook enables the GS Ball and Celebi event - 2026 : pret repositories reproducibly build exact retail revisions - : ASM-level bug catalogs and symbolic layouts support controlled comparison -``` - -Gold and Silver launched in Japan on November 21, 1999 and in North America on October 15, 2000; Crystal followed in Japan on December 14, 2000 and internationally in 2001. Gold/Silver reached 3DS Virtual Console in September 2017 and Crystal followed in January 2018. - -The current reverse-engineering record is better described as an evolving evidence base than as a single "discovery date." pret's labels, comments, and bug documentation have accumulated over thousands of repository commits, while the exact-build hashes provide a stable anchor to the retail binaries. Crystal's repository currently has substantially more history and feature documentation than `pokegold`, reflecting its role as the community's principal Gen II hacking platform rather than evidence that Crystal's retail source was inherently better documented. - -### Final technical assessment - -Gold and Silver are functionally sibling configurations of one engine. Their meaningful differences are overwhelmingly table-, asset-, and version-flag-driven. Crystal preserves that engine's defining architecture but turns it into a more specialized platform: Color-only graphics, animated Pokémon, richer event scripting, expanded persistent state, Mobile-era infrastructure, Battle Tower contexts, caught metadata, additional music, and selective defect correction. - -At ASM level, the most consequential changes are not usually exotic algorithms. They are small branches with large semantic reach: - -- a Battle Tower early return suppresses badge boosts; -- two reserved Pokémon bytes acquire caught metadata without changing record size; -- Suicune disappears from `InitRoamMons`; -- save blocks become reorganized and gain new persistent domains; -- a text terminator closes the Coin Case execution path; -- link-mode checks preserve old arithmetic to avoid desynchronization; -- a Virtual Console hook activates an otherwise inaccessible event; -- a Mobile timer branch attaches a new subsystem to the interrupt path. - -That pattern explains both Crystal's compatibility and its porting difficulty. It remains recognizably the Gold/Silver engine, but its new features are woven through banks, state structures, control-flow conditions, graphics timing, SRAM, scripts, and presentation. The code is cousin-shaped, not copy-paste-shaped: the classic reverse-engineering booby trap wearing a tiny Suicune hat. \ No newline at end of file diff --git a/docs/extraction-notes.md b/docs/extraction-notes.md deleted file mode 100644 index 635a00a3..00000000 --- a/docs/extraction-notes.md +++ /dev/null @@ -1,50 +0,0 @@ -# ROM Extraction Notes - -There are two ROM-only extraction paths: - -- The packaged app uses `src/import/RomImporter.lua` and - `src/import/RomExtractor.lua` on first boot. -- Developers can run `tools/build_data.py --rom <path> [--clean]` to generate - data in the source tree for audit and parity work. - -Both paths read only the supplied ROM and the checked-in -`tools/rom_manifest.json`. Neither invokes RGBDS, Git, or a disassembly. - -## Validation - -Only the canonical US Pokemon Red ROM is supported. SHA-1 is checked before -any cached output is removed or written. - -## Decoded Data - -| Area | ROM data | -| --- | --- | -| world | map headers, block maps, connections, warps, signs, objects | -| tiles | tileset graphics, blocksets, collision, door and warp tile lists | -| text | 2,584 text command streams and RAM/number substitutions | -| Pokemon | names, stats, evolutions, learnsets, Dex data, compressed pictures | -| battle | moves, detailed animations, OAM frames/tiles, effects, type chart, palettes, trainer parties/AI/pictures | -| inventory | item names, prices, key-item flags, TM/HM data | -| encounters | grass and water wild tables | -| UI | fonts, icons, title/intro, trainer card, town map, slots, field effects | -| audio | music, SFX and cry headers, channel programs, wave instruments | - -The Python and Lua picture decompressors implement the Gen 1 `pic` format. -Graphics are converted to RGBA PNGs. OAM artwork uses transparent color 0; -battle pictures use edge-connected white matting so white interior details -remain visible. - -The in-app importer stores three audio ROM banks as a 48 KiB -`programs.bin`. `src/core/ChipAudio.lua` interprets the channel bytecode and -synthesizes music as a queueable stream; SFX and cries are synthesized on -demand. This avoids shipping or generating a large WAV/OGG tree. - -## Metadata Boundary - -Names, dimensions, enum ordering, Lua script hooks, and hand-ported field -behavior do not survive compilation in a form the Lua runtime can infer. -Those relationships are bundled in `rom_manifest.json`. The manifest stores -no dialogue strings, images, audio samples, or ROM bytes. - -`tools/make_rom_manifest.py` and `tools/verify_rom_data.py` are developer audit -tools. They are not used by the packaged game. diff --git a/docs/gen2-link-design.md b/docs/gen2-link-design.md deleted file mode 100644 index b1b94584..00000000 --- a/docs/gen2-link-design.md +++ /dev/null @@ -1,316 +0,0 @@ -# Gen 2 link play: what it takes - -This is a design document, not a feature announcement. Gold cannot link today -and nothing in this document changes that on its own. What it does is state -honestly what Gen 2 link play requires, where the Gen 1 protocol in `src/link/` -stops working on a Gen 2 record, what the cart itself did, and which pieces of -the work are self-contained enough to have been built already. - -The short version: the transport, the handshake and the fingerprint are -generation-agnostic or nearly so, and those are done. The party wire format is -half done (a codec exists, nothing sends it). The trade session, the trade UI -and the lockstep battle are not started, and each is a real piece of work. - -Read `docs/mod-api-gen2-compat.md` beside this: it is the reference for what a -Gold mod may touch, and the link fingerprint's whole job is to hash exactly that -surface. - -## 1. What Gen 1 link play is - -`src/link/` is nine files and about four thousand lines: - -| File | Job | -| --- | --- | -| `Net.lua` | lua-enet transport, LAN host/join plus a relay for online play. JSON messages, no game types. | -| `CodeEntry.lua` | the online join code widget. | -| `Handshake.lua` | the `hello` both peers exchange and the compatibility verdict drawn from the two of them. | -| `Fingerprint.lua` | a deterministic digest of the link surface: the slice of merged Data whose value decides whether two lockstep simulations stay identical. | -| `Protocol.lua` | mon serialization, the record-subset negotiation, and the trade session state machine. | -| `LinkState.lua` | the LINK menu, pairing, and the trade/battle hand-off. | -| `LinkBattle.lua` | lockstep battle: both machines run `src/battle/BattleState.lua` from mirrored perspectives on a shared seed, exchanging one action per turn and a per-turn state hash. | -| `Tournament.lua` | bracketed online play over the relay. | -| `Json.lua` | the encoder the wire and several unrelated callers share. | - -Only three of those nine are shaped around Gen 1 game content: `Fingerprint`, -`Protocol` and `LinkBattle`. `Net`, `Json`, `CodeEntry` and most of `Handshake` -never look at a Pokemon. - -## 2. What the cart did - -pokegold's link code is `engine/link/link.asm` (2508 lines), and it is worth -being precise about, because several of the port's design questions have a cart -answer. - -**Three rooms, one wire.** `wLinkMode` is `LINK_TIMECAPSULE`, -`LINK_TRADECENTER` or `LINK_COLOSSEUM` (`constants/serial_constants.asm`). -`LinkCommunications` branches once, at the top, on whether the mode is the Time -Capsule: `Gen2ToGen1LinkComms` for the Time Capsule and `Gen2ToGen2LinkComms` -for everything else (`engine/link/link.asm:33`). Everything after that branch is -shared -- the same byte exchange serves trading and battling, and the mode only -decides what is in the buffer and what the game does afterwards. - -**What crosses the wire, in order** (`Gen2ToGen2LinkComms`, -`engine/link/link.asm:202`): - -1. `SERIAL_RN_PREAMBLE_LENGTH + SERIAL_RNS_LENGTH` bytes: the shared battle - RNG state. Seven preamble bytes and ten seeds. -2. `SERIAL_PREAMBLE_LENGTH + NAME_LENGTH + (1 + PARTY_LENGTH + 1) + 2 + - (PARTYMON_STRUCT_LENGTH + NAME_LENGTH * 2) * PARTY_LENGTH + 3` bytes: the - player name, the party count and species list, the **trainer ID**, six party - structs, six OT names and six nicknames. -3. `SERIAL_PATCH_LIST_LENGTH` bytes: the patch list (see below). -4. In the Trade Center only, the mail block: six mail messages then six mail - metadata structs (`Link_PrepPartyData_Gen2`, `engine/link/link.asm:810`). - -**The shared PRNG is the whole trick behind lockstep.** `_BattleRandom` -(`engine/battle/core.asm:6650`) refuses the normal RNG whenever `wLinkMode` is -non-zero and pulls from `wLinkBattleRNs` instead: ten seeds, each advanced by -`a[n+1] = (a[n] * 5 + 1) % 256` when the stream runs out, with the count in -`wLinkBattleRNCount`. Both machines exchange those ten bytes once, before the -battle, and never again -- the external-clock side adopts the internal-clock -side's numbers (`Link_CopyRandomNumbers`, `engine/link/link.asm:1115`). The port -does the same thing with a Park-Miller stream seeded by the host -(`LinkBattle.makeRng`), which is the same idea in a different arithmetic. - -**The patch list exists because the wire has reserved bytes.** -`SERIAL_NO_DATA_BYTE` (`$fe`) may not appear in the payload, so -`FixDataForLinkTransfer` (`engine/link/link.asm:556`) walks the party block, -replaces every `$fe` with `$ff`, and records the offsets it touched in a -200-byte list that ships alongside. Mail gets its own smaller version of this in -`Link_PrepPartyData_Gen2`. The port's wire is JSON over enet and has no reserved -bytes, so this whole mechanism has no analogue and needs none. It is worth -knowing about only so nobody reimplements it by accident. - -**The Time Capsule is the cart's own answer to a cross-generation link.** -`CheckTimeCapsuleCompatibility` (`engine/link/link.asm:1970`) refuses the party -outright for exactly three reasons, and returns which one in `wScriptVar`: - -1. a species at or above `JOHTO_POKEMON` (`constants/pokemon_constants.asm:168`, - the 152 boundary), -2. a move above `STRUGGLE`, i.e. any move Gen 1 does not have, -3. any party member holding mail. - -If the party passes, `Link_PrepPartyData_Gen1` (`engine/link/link.asm:640`) -rewrites every mon into the 44-byte `REDMON_STRUCT_LENGTH` layout: -`ConvertMon_2to1` remaps the species index through `Pokered_MonIndices` -(`engine/link/time_capsule_2.asm:1`), the Special stat is recomputed from -`KantoMonSpecials` because Gen 1 has one Special and Gen 2 has two, and -Magnemite and Magneton are shipped as pure Electric because their typing changed -(`engine/link/link.asm:731`). Coming the other way, `Link_ConvertPartyStruct1to2` -(`engine/link/link.asm:930`) reads the Gen 1 catch rate byte as the held item -slot -- garbage, which is why `TimeCapsule_ReplaceTeruSama` -(`engine/link/link.asm:1078`) maps the handful of catch rates that collide with -real items through `data/items/catch_rate_items.asm` and turns everything else -into a Berry. `ValidateOTTrademon` (`engine/link/time_capsule.asm:1`) then -re-checks the incoming mon's types against the local base data and refuses -anything that does not agree, with Magnemite and Magneton carved out again. - -The lesson the port should take from all of that: a cross-generation link is not -a compatibility mode, it is a lossy conversion with a validator on each end, and -the cart wrote roughly 400 lines for it. Refusing the pairing is the honest -default until somebody wants to write those 400 lines. - -## 3. Where the Gen 1 protocol breaks on a Gen 2 record - -`Protocol.packMon` / `Protocol.unpackMon` are shaped around -`src/pokemon/Pokemon.lua`. Against `src/battle/gen2/Mon.lua`, every one of these -is wrong: - -| Gen 1 assumption | Gen 2 reality | Where | -| --- | --- | --- | -| `mon.exp` | `mon.experience` | `Mon.new`, `Mon.gainExperience` | -| five DVs including `special` | four rolled DVs (`attack`/`defense`/`speed`/`special`) with `hp` **derived** from their low bits | `Mon.hpDV` | -| `special` is one stat | `specialAttack` and `specialDefense` are two stats off one DV | `Mon.stats` | -| `statExp` has five words | five words still, but the fifth feeds both special stats | `Mon.stats` | -| no held item | `mon.item` decides damage, healing and flinching | `src/battle/gen2/Battle.lua` `heldEffect` | -| stats recomputed with `src/pokemon/Stats.lua` | must go through `Mon.stats`, which is a different formula shape | `Mon.stats` | -| exp curve via `src/pokemon/Growth.lua` (code) | curve coefficients are **data**, at `data.pokemon.growthRates` | `Mon.experienceForLevel` | -| `mon.status` is `"SLP"` / `"PSN"` / `"BRN"` / `"PAR"` / `"FRZ"` | `"sleep"` / `"poison"` / `"burn"` / `"paralyze"` / `"freeze"` / `"toxic"` | `src/battle/Status.lua:62` vs `src/battle/gen2/Battle.lua:65` | -| `mv.ppUps` | Gen 2 has no PP Ups modelled yet; moves carry `id`, `pp`, `maxPp` | `Mon.movesAtLevel` | -| nothing else on the mon | `happiness`, `pokerus`, `caughtLevel`, `isEgg`/`eggSteps`, and the derived `shiny` / `gender` / `unownLetter` | `Mon.new` | -| mail does not exist | mail is a **separate** block keyed by party slot, not a mon field | `src/core/gen2/Mail.lua:84`, and the cart agrees (`Link_PrepPartyData_Gen2`) | - -The status spelling is the sharpest of these. `packMon` puts `mon.status` on the -wire verbatim and `unpackMon` keeps it verbatim, so a shared codec would move -`"PSN"` onto a Gold party where nothing recognises it -- a mon that arrives -"poisoned" and never takes poison damage. Any Gen 2 codec has to be a separate -function with the generation baked into it, not a Gen 1 function with extra -optional keys. - -Two things are *not* a problem, and it is worth saying so: - -- **Species and move ids are shared name spaces.** Both generations key - `data.pokemon` and `data.moves` by the same `TOTODILE` / `TACKLE` strings, so - the wire never needs the cart's index remap (`ConvertMon_2to1`). Ids, not - bytes, is what makes the port's protocol simpler than the cable's. -- **The derived fields do not travel.** `shiny`, `gender` and `unownLetter` are - all functions of the DVs (`Mon.isShiny`, `Mon.gender`, `Unown.letterFromDVs`), - so the receiver recomputes them and a tampered packet cannot claim a shiny it - did not roll. Same reasoning as `unpackMon` recomputing stats. - -`LinkBattle.lua` breaks in a larger way. It calls -`BattleState.makeBattler`, `src/battle/TurnOrder.lua` and Gen 1's damage -pipeline directly. Gold's battle is `src/battle/gen2/Battle.lua`, a different -object with a different event shape. The one piece of good news is that -`Battle.new` already takes `opts.random` (`src/battle/gen2/Battle.lua:220`) -- -the injection seam a lockstep battle needs is there and does not have to be cut. - -## 4. The wire format for Gen 2 - -Message types stay the ones `Protocol.lua` already documents (`hello`, -`records`, `party`, `pick`, `confirm`, `action`, `event`, `bye`). Gen 2 adds -fields, never renames -- the same rule the mod seams follow. - -**`hello`** gains one field, `generation` (1 or 2). A peer that omits it is -generation 1 by construction: no released build has ever had Gen 2 link, so -absent means Red/Blue/Yellow. This is implemented. - -**`party`** carries a list of packed mons whose shape is the Gen 2 one: - -``` -{ species, level, experience, hp, status, nickname, - dvs = { attack, defense, speed, special }, -- hp derived - statExp = { hp, attack, defense, speed, special }, - moves = { { id, pp } ... }, - item, happiness, pokerus, caughtLevel, - ot, otId, isEgg, eggSteps, extra } -``` - -and, in a trade, a parallel `mail` array indexed by the same wire position -- -separate exactly as `Link_PrepPartyData_Gen2` keeps it separate, because mail is -SRAM the cart copies out of `sPartyMail`, not part of the party struct. The -codec for the mon half is implemented; the mail half is not. - -**`records`** gains a third map, `heldItems`, beside `pokemon` and `moves`, so a -subset trade can refuse a mon whose held item the other game would rebuild -differently. Implemented. - -**`action` / `event`** for a lockstep Gen 2 battle are not designed here, because -the design is downstream of a decision nobody has made yet: whether -`src/battle/gen2/Battle.lua` grows a "replay these two actions" entry point or -whether the link battle drives it through the same UI path a local battle uses. -Guessing at a message shape before that is exactly the half-built protocol this -document is meant to avoid. - -## 5. The fingerprint on Gold - -The fingerprint is the one part of the design that is fully answerable today, -because it depends only on what a Gold mod can reach, and `src/mods/Schemas.lua` -answers that exhaustively. - -The Gen 1 surface is species, moves, the type chart, statuses, move effects, -constants and mod-declared link fields. The Gen 2 surface is the same idea over -different tables, plus one registry Gen 1 does not have: - -| What | Gen 1 | Gen 2 | Why it is surface | -| --- | --- | --- | --- | -| species | `data.pokemon` | `data.pokemon` (Gen 2 shape) | base stats decide every damage roll; `evolutions` decides what a traded mon becomes | -| exp curves | code (`src/pokemon/Growth.lua`) | `data.pokemon.growthRates` | the level a traded mon's experience buys | -| moves | `data.moves` | `data.moves`, **plus `effectChance`** | Gen 2 stores the secondary-effect odds per move rather than per effect | -| type chart | `data.type_chart` | same, **plus `foresightMatchups`** | Foresight rewrites Ghost's immunities mid-battle | -| statuses | `data.statuses` | `data.gen2Statuses` | `statPenalty`, `cureOnSwitch`, `beforeMovePriority` | -| move effects | `data.move_effects` | `data.gen2MoveEffects` | which effect is primary, secondary, or accuracy-checked | -| held items | -- | `data.gen2HeldItems` | Leftovers, King's Rock, the type boosters: pure battle math, and the item travels with a traded mon | -| link fields | `data.link_fields` | none (gated) | a mod-declared extra mon field; see below | - -Deliberately **not** hashed on either generation, and the reasoning is the same -in both: - -- Names, dex entries, learnsets, TM/HM lists, sprite paths and `source`. None of - them changes a battle turn or a trade rebuild, and sprite paths differ between - two identical installs. -- `catchRate` (#511). A ball thrown in a link battle is refused; hashing it split - Red/Blue from Yellow over two bytes. -- Balls and item effects. Link battles allow no bag items on either cart. -- `data.gen2Constants`. It is the ROM's ordered name lists -- `mapOrder`, - `spriteOrder`, `speciesOrder`, `heldEffectOrder` -- and it is an *index* space. - Every dispatch in the Gen 2 simulation goes by name (`Battle.heldEffect` - compares `record.heldEffect` strings, `moveEffectRecordFor` keys by - `EFFECT_*`), so reordering a constants list moves no battle math. Hashing it - would split two peers over a list neither of them dispatches on. -- Breeding data (`eggGroups`, `eggMoves`, `eggSteps`). The Day-Care is local. - There is no link breeding and an egg's contents are decided before it is - traded. -- Everything in the Gen 2-only registries that is not `held_items`: - `phone_contacts`, `decorations`, `apricorns`, `landmarks`, `radio_channels`. - None of them can be observed from inside a link session. - -`genderRatio` **is** hashed, unlike anything in the breeding block, because Gen 2 -has Attract and a gender disagreement is a battle-math disagreement. - -`link_fields` stays gated on Gen 2 (`Schemas.GEN2`). It is a mod's declaration -that an extra mon field must survive the wire, and it can only be un-gated once -there is a Gen 2 mon wire format with a session behind it. A registry that hashes -into the fingerprint but that no packer reads would be a lie the fingerprint -tells. - -The hook keeps its Gen 1 name and its Gen 1 arity. `link.fingerprint` is called -as `(data, mods)` on both generations, and the generation is captured in the -vanilla closure rather than passed as a third argument -- a Gen 1 mod that wraps -the hook and forwards `nxt(data, mods)` therefore works verbatim on Gold, which -a third argument would have quietly broken. - -## 6. Cross-generation pairing - -`Handshake.checkCompat` refuses a pairing whose two `generation` fields differ, -with reason `generation_mismatch`, before any other check. This is deliberately -a refusal and not a Time Capsule: section 2 costs roughly 400 lines of lossy -conversion plus two validators, and shipping a menu that pairs a Gold game with a -Red one and then rebuilds a Cyndaquil as whatever species index 155 happens to -be in Red would be worse than refusing. - -If somebody does want the Time Capsule later, the cart's own rules are the spec, -and the port has an advantage the cart did not: ids rather than indices, so -`ConvertMon_2to1`'s remap table is unnecessary. What remains is real work: -refuse species above the Kanto 151, refuse moves Gen 1 lacks, refuse held items -and mail, fold `specialAttack`/`specialDefense` back into one Special -(`KantoMonSpecials`, `data/pokemon/gen1_base_special.asm`), and re-validate types -on arrival with Magnemite and Magneton carved out. - -## 7. What is built, and what is left - -**Built (this change).** - -- `Fingerprint.generationOf(data)`: reads `data.type_chart.generation`, then the - presence of the Gen 2-only Data namespaces, so the digest never has to be told - which game it is running in. -- A Gen 2 link surface in `Fingerprint.lua`, tagged `[gen2]` so a Gen 2 digest - can never collide with a Gen 1 one, covering the table in section 5. -- `Fingerprint.records(data, kind, generation)` for `pokemon`, `moves` and - `held_items` on Gold, which is what a subset trade negotiates on. -- `Handshake.hello` carries `generation`; `checkCompat` refuses a cross-generation - pairing; `describe` explains it in the screen's own voice. -- `held_items` counts as link surface for `Handshake.linkModified`, so a Gold mod - that gives Leftovers a different heal is correctly locked out of online play. - `growth_rates` counts too, and for a reason worth stating: a curve is an - `expForLevel` function, so the fingerprint serializes it as `?` and cannot - hash it at all. `Mon.growthFor` prefers the merged registry over the - extractor's coefficient rows, so a mod declaring `affects_link = false` could - otherwise rewrite every curve -- changing what level a traded mon's experience - buys -- and be caught by neither the digest nor the online gate. -- `Protocol.packMon2` / `unpackMon2`: the Gen 2 party-struct codec, with the - receiver recomputing stats, experience, shininess and gender from real species - data the way `unpackMon` does. Nothing sends it yet. -- `Protocol.recordsMessage` and `eligibleParty` understand held items, and - `TradeSession:_negotiate` builds our own side of the comparison by calling - `recordsMessage` rather than open-coding a subset of it. That is not a - refactor: the open-coded version left `heldItems` off our side only, and both - held-item arms of `eligibleParty` are guarded on our own map, so the whole - check was unreachable from the only caller that matters. - -**Not built.** Sized as honestly as I can: - -| Piece | Size | Why it is not here | -| --- | --- | --- | -| Mail on the wire | small (1-2 days) | needs `Mail.state(save).party` packed as a parallel array and the receiver writing it into its own slot; no consumer until the trade session exists | -| Gen 2 trade session | medium (about a week) | `TradeSession` is generic in shape but `apply` calls `src/world/PikachuFollower`, Gen 1 pokedex fields and Gen 1 trade evolutions; Gold needs `src/core/gen2/TradeAnim.lua`, `src/core/gen2/Evolution.lua` (`EVOLVE_TRADE`) and the Gen 2 pokedex | -| Gold LINK menu and trade UI | medium (about a week) | Gold's menus are `src/ui/gen2/`, its text box is Gen 2 chrome, and the Cable Club script needs the receptionist path; `LinkState.lua` draws with Gen 1 `TextBox` throughout | -| Gen 2 lockstep battle | large (several weeks) | needs a deterministic-replay entry point into `src/battle/gen2/Battle.lua`, a Gen 2 action/event encoding, and a state hash over a much larger volatile-status set than Gen 1's; the `opts.random` seam exists, nothing else does | -| Time Capsule | large | section 6 | -| `link_fields` on Gen 2 | small, but blocked | needs the trade session first | - -The order matters: mail, session and UI are the trade half and can ship without -the battle half, exactly as the cart's Trade Center and Colosseum are separate -rooms. A Gold LINK menu that offers only TRADE is a complete feature. A Gold -LINK menu that offers BATTLE before the lockstep battle exists is the failure -this document was written to prevent. diff --git a/docs/gold-phase1.md b/docs/gold-phase1.md deleted file mode 100644 index dfd86910..00000000 --- a/docs/gold-phase1.md +++ /dev/null @@ -1,188 +0,0 @@ -# Pokemon Gold: Gen 2 import, colour, menus, saving and battles - -Support for a canonical **Pokemon Gold** ROM -(SHA-1 `d8b8a3600a465308c9953dfa04f0081c05bdcb94`, 2 MiB). Gen 2 extraction -and overworld are separate from Red/Blue/Yellow -- never branched into -`RomExtractor.lua` / Gen 1 `Map.lua`. - -## Pipeline - -| Piece | Role | -| --- | --- | -| `src/import/Rom.lua` (`decompressLz3`) | Gen 2 graphics compression (`home/decompress.asm`) | -| `tools/make_gold_manifest.py` → `tools/rom_manifest_gold.json` | pret/pokegold constants + `pokegold.sym` | -| `src/import/RomExtractorGen2.lua` | Gen 2 extract (dispatched from `RomImporter` when `version == "gold"`) | -| `src/core/GameVersion.lua` | `gold` entry (`gold/` cache, `_gold` saves) | -| `src/world/gen2/` | COLL_* world, NPCs, events, roofs | -| `src/script/gen2/` | Opcodes + talk-oriented VM over extracted cmds | -| `src/core/Game2.lua` | Gold's service owner (the Gen 2 peer of `src/core/Game.lua`): boot cinema, intro menu, world, START menu | -| `src/core/gen2/Save.lua` | `save_gold.lua`, beside the Gen 1 saves | -| `src/ui/gen2/` | Intro menu, naming keyboard, START menu + every submenu, battle screen | -| `src/battle/gen2/` | Damage, stats/experience, HP bar, turn engine, catching, encounters | -| `src/render/GbcPalette.lua` | One shader: a 4-shade sheet through a GBC palette | - -## What extracts - -| Area | Status | -| --- | --- | -| Constants | species/map/tileset/move/type/sprite/environment/palette/fish orders | -| Font | `Font` (1bpp ink-on-transparent), `FontExtra` + `Frames` borders at $79–$7E, `FontBattleExtra` | -| Tilesets | lz3 GFX → PNG; raw Meta (128×16) + Coll (128×4 COLL_* quads); Anim/PalMap pointers | -| Roofs | 5 roof sheets + `MapGroupRoofs` (runtime: outdoor tilesets only) | -| Maps | all 368: blocks, connections, warps, coord/bg/object events (+ `scriptKey`) | -| Pokemon | `BaseData` (32B), names, front/back pics, plus Unown's 26 letter forms from `UnownPicPointers` | -| Items | Names, prices, pockets, held effects, field/battle use, descriptions, TM/HM numbers and the move each teaches | -| Marts | `Marts`' 34 shelves in MART_* order plus `BargainShopData`'s own item/price rows (`marts.lua`) | -| Overworld sprites | `OverworldSprites` → PNG sheets + `sprites.lua` | -| Scripts / text | disassembled cmds + decoded strings (`scripts.lua` / `text.lua`) | -| Initial events | `InitializeEventsScript` → `initial_events.lua` | -| Title | Tilemap + GBC tint; cloud band; Ho-Oh frames 1–5; trail; copyright splash | -| Oak speech | `_OakText1–7`, `PokemonProfPic` / `CalPic`, Marill front; `oak_speech.lua` | -| Pokemon / trainer pics | lz3 + column-major → row (`ImageWriter.columnsToRows`) | -| Audio | banks dumped to `programs.bin`; Gen 2 ChipSynth driver (`runtime = true`) | -| Palettes | `TilesetBGPalette`, `EnvironmentColorsPointers`, `MapObjectPals`, `RoofPals`, mon/trainer/HP-bar/exp-bar palettes, per-tileset `PalMap` | -| Moves | `Moves` + names + descriptions, effect names, real percentages | -| Type chart | `TypeMatchups` (plus the Foresight rows) and `TypeNames` with physical/special | -| Pokemon | Base stats, evolutions and level-up moves (`EvosAttacks`), TM/HM lists, growth rates, egg groups, gender ratio | -| Encounters | Grass (three time-of-day slot lists), water, fishing groups, headbutt tree sets | -| Trainers | Every class's parties with moves/items, class names, attributes, `TrainerEncounterMusic` | -| Pokedex | Entries (kind, height, weight, text) + the New and A-Z orderings | -| Landmarks | Town-map positions and names, plus `SpawnPoints` | -| Icons | Party-menu mon icons and the species → icon map | -| Menu / HUD gfx | Naming-screen chrome, battle HUD border and bar tiles, exp bar | -| Intro movie | The three acts' composed backgrounds, sprite sheets and fire frames | -| Battle anims | all 428 scripts, 188 objects, 185 framesets, 216 OAM sets, 40 object sheets, the six OBJ palettes | -| Field anims | stubs | - -Verified against a real Gold ROM: New Bark Town warps/objects/connections -match `pokegold/maps/NewBarkTown.asm`. Retail EVENT_* numeric ids differ -from pret’s current `const_def` order -- always take flags from the cart. - -## Play - -Launcher → Gold tab → Play opens `Game2` (`src/core/Game2.lua`): - -- Copyright → GameFreak Presents → GS intro stub → title (colored tilemap, - scrolling clouds, Ho-Oh flap, trails) → Oak speech (Marill cry + shrink) - → name pick → `src/world/gen2/World` -- Drivers (`POKEPORT_DRIVER`) skip cinema straight to the map -- New Bark Town with Johto tileset + roof overlay (outdoors only) -- Walk on `COLL_*` permissions; doors / stairs / carpets -- Elm's lab: scene walk-in, starter balls (`givepoke` + cutscene moves), - Elm phone number, aide Potion on exit (`verbosegiveitem`) -- Connected neighbor strips + seamless edge crossings (Route 29 / 27) -- Survey zoom (`-` / `=` / wheel / `4`) -- Chris + map/neighbor NPCs; `SPRITEMOVEDATA_*` walk / spin / stand -- New-game flags hide story NPCs (e.g. lab cop) -- A: talk to facing NPC or read `BGEVENT_READ` signs via Gen 2 VM + TextBox - -Escape is START, not quit: quitting is the START menu's QUIT row and the -intro menu's EXIT GAME. Title / map music and script SFX/cries use the Gen 2 -channel driver. - -## Colour - -Gen 2 is CGB-native, so colour is part of a tile's identity rather than a tint -over a 4-shade image. `engine/gfx/color.asm` LoadMapPals is ported whole in -`src/world/gen2/Palettes.lua`: - -1. the clock hour picks a daytime (`engine/rtc/rtc.asm` TimesOfDay), which a - map's own `PALETTE_*` can override (`ReplaceTimeOfDayPals`) -2. `EnvironmentColorsPointers[environment][daytime]` names eight entries in the - shared `TilesetBGPalette` pool -3. outdoors only, `RoofPals[mapGroup]` overwrites `PAL_BG_ROOF` colours 1-2 -- - one roof tile sheet, a different colour per town -4. each tile's slot comes from its tileset's `PalMap`, and each OW sprite's OBJ - palette from `MapObjectPals[daytime]` plus its own `PAL_OW_*` - -The map bake runs one shader pass per BG slot (eight per map) rather than one -per tile, and re-bakes when the clock rolls into a new daytime. -`POKEPORT_GOLD_HOUR=21` pins the hour for screenshots. - -## Still to do - -About fifty of AI_Smart's seventy per-effect handlers are unwritten, fishing -and headbutt have engine support but no input path, and Mart, the summary -screen, `.sav` interchange, Silver and mod-replaceable Gen 2 screens are all -still open. - -## Verify - -```sh -python3 tools/make_gold_manifest.py -for t in rom_lz3 gen2_world gen2_audio gen2_oak_speech gen2_vm \ - gen2_palettes gen2_battle gen2_menus gen2_save; do - luajit tests/${t}_test.lua -done -POKEPORT_IMPORT_TRACE=1 POKEPORT_IMPORT_ROM=/path/to/pokegold.gbc \ - POKEPORT_IMPORT_ONLY=1 POKEPORT_FORCE_IMPORT=1 POKEPORT_GAME=gold love . -POKEPORT_GAME=gold love . -# copyright → GF presents → GS intro → title → CONTINUE/NEW GAME/OPTION -# → Oak → name → the bedroom. START opens the menu; grass starts a battle. - -# Screenshot drivers (need a display): -POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_menu_shots.lua love . -POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_palette_shots.lua love . -POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_battle_smoke.lua love . -POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_transition_shots.lua love . -POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_teacher_scene.lua love . -POKEPORT_GAME=gold POKEPORT_BOOT_CINEMA=1 \ - POKEPORT_DRIVER=tests/drivers/gold_boot_smoke.lua love . -``` - -`POKEPORT_IMPORT_TRACE=1` prints each of the 26 stages as it starts, and a -headless import that fails now says so and exits non-zero rather than sitting -in an error state that looks exactly like a hang. - -A Gold cache without `palettes.lua`, `moves.lua`, `encounters.lua`, -`trainers.lua`, `pokedex.lua`, `landmarks.lua`, `icons.lua`, `menu_gfx.lua`, -`intro.lua`, `std_scripts.lua` or `battle/hud/*.png` needs a re-import -- as -does one whose back pics are still the front pic's size rather than 48x48 (see -below), and one whose trainer classes have no `encounterMusic` (the sixth -pass added `TrainerEncounterMusic` to the manifest, so the manifest has to be -regenerated as well). So does one whose `text.lua` has no `labels` table: that -is the by-name seed for the text no script pointer reaches (the Day-Care and -breeding block, the mart conversation, the Hall of Fame headers), and it needs -a regenerated manifest too, because the seed reads those symbols by name. - -## Traps this port has already fallen into - -Worth knowing before touching the extractor: - -- **Back pics are always 6x6 (48x48).** `BASE_PIC_SIZE`'s low nibble describes - the *front* pic only, so decoding a back at it reads the wrong tile count in - the wrong number of columns and produces garbage. -- **`#`, `<POKE>` and `<PKMN>` have no font tile.** They are compression bytes - ($54/$24/$4a) that PlaceString expands; the manifest charmap has to expand - them or "#DEX" renders as "DEX". -- **FishGroups has no row for FISHGROUP_NONE**, so a group's row is its id minus - one. Walking the constant list from the top reads every row shifted. -- **Trainer groups have no end marker** -- the next group's label follows - immediately -- so a party scan needs the class's own member list to bound it. -- **Pokedex entries are spread over four banks** and the cart derives the bank - arithmetically; take each species' own symbol instead. -- **`EvosAttacksPointers` is `dw`, not `dba`**: the blobs share the table's bank. -- Item ids past NUM_ITEMS (the TMs and HMs) have no ItemNames row; their name is - their TM number. -- **`TrainerClassAttributes` rows are SEVEN bytes**, not eight: - `NUM_TRAINER_ATTRIBUTES` is `_RS` after three `rb` and two `rw`. An - eight-byte stride walks one byte further off with every class, so the AI - flags come out as noise for everything past the first trainer -- and it - produces plausible values rather than an error, which is how it survived two - passes. The row is {item1, item2, baseMoney, aiLo, aiHi, switchLo, - switchHi}: the base money is byte THREE. -- **`dba_pic` does not store the real bank.** For the three "Pics" sections - that sit above the 8-bit-friendly range it writes `$13`, `$14` or `$1f`, and - `FixPicBank` (engine/gfx/load_pics.asm) maps those back to `$1f`, `$20` and - `$2e`. Any table of pic pointers -- `PokemonPicPointers`, - `UnownPicPointers`, `TrainerPicPointers` -- needs that mapping, or the read - lands in a completely different bank. -- **A pic can run over the top of its bank.** `GetLZByte` bumps the bank and - drops back to `$4000` when the read pointer passes `$8000`, so a compressed - pic near the top of a bank keeps going into the next one; slicing only to - `$8000` stops short of nine of the Unown letters. -- **`BattleAnimObjects` rows are SIX bytes**, because `BATTLEANIMOBJ_LENGTH` is - `_RS - 1` -- the struct's runtime INDEX byte is not in the table. -- **Unown has no `PokemonPicPointers` row of its own**; its twenty-six forms - come out of `UnownPicPointers` instead, and letter A stands in for the - species. diff --git a/docs/ios-sideload.md b/docs/ios-sideload.md deleted file mode 100644 index 8c19cf01..00000000 --- a/docs/ios-sideload.md +++ /dev/null @@ -1,48 +0,0 @@ -# Sideload the iOS build with AltStore - -Every GitHub Release ships an IPA (`gen1recomp++-*-ios.ipa`). Install it on -your iPhone or iPad with [AltStore Classic](https://altstore.io/) — AltStore -re-signs the app with **your** free Apple ID so you do not need a Mac or -Xcode. - -## 1. Install AltStore - -Follow the official guide for your computer: - -- [How to Install (Windows)](https://faq.altstore.io/altstore-classic/how-to-install-altstore-windows) -- [How to Install (macOS)](https://faq.altstore.io/altstore-classic/how-to-install-altstore-macos) - -You will install **AltServer** on the computer, then use it to put AltStore -on the phone. What AltServer is and why it needs to stay running: - -- [AltServer](https://faq.altstore.io/altstore-classic/altserver) - -Stuck? Start here: - -- [Troubleshooting Guide](https://faq.altstore.io/altstore-classic/troubleshooting-guide) - -## 2. Install the game - -1. Download `gen1recomp++-*-ios.ipa` from - [Releases](https://github.com/bryanthaboi/gen1recomp/releases). -2. Open **AltStore** on the phone (AltServer must be running on the same - Wi‑Fi, or keep the phone plugged into the computer). -3. Tap **My Apps → +** (or share the IPA into AltStore) and pick the file. -4. Sign in with your Apple ID when prompted. Wait for the install to finish. -5. On first launch: Settings → **Privacy & Security → Developer Mode** (iOS - 16+), and Settings → **General → VPN & Device Management** → Trust your - Apple ID if asked. - -Then open the app, import your own legal `.gb` ROM on the Red/Blue tab, and -play. - -## Refresh / 7-day limit - -With a free Apple ID, sideloaded apps stop launching after **7 days**. Keep -AltServer running so AltStore can refresh them, or open AltStore and refresh -manually before they expire. Saves on the phone are kept across refreshes. - -## Prefer building it yourself? - -Building from source on a Mac (no AltStore) is covered in -[ios-install.md](ios-install.md). diff --git a/docs/known-differences.md b/docs/known-differences.md deleted file mode 100644 index f0f1b78e..00000000 --- a/docs/known-differences.md +++ /dev/null @@ -1,42 +0,0 @@ -# Known differences from the original game - -Only genuine remaining divergences live here: behavior that is still -**missing, wrong, or approximated for convenience** and would need more -work for true parity. Faithfully-ported behavior is documented in -docs/behavior-porting-notes.md; deliberate additions beyond the original -are in docs/new-features.md. - -## Reimplemented unused Prof. Oak and Rocket Chief battles - -The original ROM defines trainer data for `PROF_OAK` and `CHIEF` -(`data/trainers/parties.asm`) but never attaches either to an NPC, so -both battles are unreachable in the real game. This project makes them -fightable after the Hall of Fame: - -- Prof. Oak battles you in Pallet Town once `EVENT_BEAT_CHAMPION_RIVAL` - is set, using `ProfOakData`'s three starter-matched teams (the team is - picked by the type that counters your starter, mirroring the rival). -- The Celadon Game Corner Chief battles you in his house post-game. - `ChiefData` is empty in the ROM, so `OPP_CHIEF` is given a - reconstructed party. - -This is an intentional divergence: neither battle can be triggered in the -original game. - -## Reimplemented unused Silph Co. card-key doors - -`engine/events/card_key.asm` and the unused `CardKeyTable1/2/3` coordinate -lists (`data/events/card_key_coords.asm`) describe locked doors for Silph -Co. floors 2F-11F, but no retail `.blk` map layout ever places the closed -door block at those coordinates, so the card key check is dead code in -the original game. This project stamps the closed door block (`$54`/`$5f` -on floors 2F-10F, `$20` on 11F) over each of the 20 door coordinates on -map load, and swaps it for the open block once that door's -`EVENT_SILPH_CO_n_UNLOCKED_DOORn` flag is set (using the key from a Team -Rocket grunt, as in the original's unused design). - -This is an intentional divergence: the doors are not visible or -functional in the original game. The door layout lives in -`tools/rom_manifest.json` (`field.cardKeyDoors.closedDoors`), hand-ported -since no retail ROM data encodes it; `src/import/RomExtractor.lua` copies -it straight through on ROM import. diff --git a/docs/launcher.md b/docs/launcher.md deleted file mode 100644 index 24f79d0d..00000000 --- a/docs/launcher.md +++ /dev/null @@ -1,300 +0,0 @@ -# Launcher - -The launcher is `src/import/RomImporter.lua`, the first-run / title screen -that runs before `Game:load`. Besides ROM import (see the file's own header) -it hosts a tabbed shell covering per-game save slots and a mod manager. This -file documents the runtime model; the visual spec lives separately. - -## Android multi-ROM / mod / save import - -On Android, `love.system.pickFile([kind])` opens the Storage Access Framework -picker (`GameActivity.showFilePicker`); the chosen file is copied into the app -save directory as: - -| `kind` | Destination | -| --- | --- | -| nil / `"rom"` | `picked_rom.gb` (open) | -| `"mod"` | `picked_mod.zip` (open) | -| `"sav"` / `"save"` | `picked_save.sav` (open) | -| `"required_import"` | `picked_required_import.bin` (open) | - -Export uses a separate API: `love.system.createFile(suggestedName)` → -`GameActivity.showCreateDocument` (`ACTION_CREATE_DOCUMENT`), which copies -staged `pending_export.sav` to the user-chosen URI and writes `export_done.flag` -for the launcher to acknowledge on refocus. - -`RomImporter` then imports on refocus / Choose: - -- **ROMs** via `findPendingRom`: only a 1 MiB (Red/Blue/Yellow) or 2 MiB - (Gold) `.gb`/`.gbc` whose SHA-1 maps to a version that is **not** yet ready - counts as pending. A leftover `picked_rom.gb` from Red therefore cannot - block Blue's Choose (issue #167). -- **Mods** via `findPendingMod`: Prefer `picked_mod.zip`, or (on Choose) any - other `.zip` at the save-dir root (USB copy). -- **Saves** via `findPendingSav`: Prefer `picked_save.sav`, or (on Choose) any - other `.sav` at the save-dir root. - -After a successful import the consumed save-dir file is removed. - -**Manual check (device/emulator):** import Red → switch to Blue → Choose → -system file picker must appear (not a silent Red re-extract) → pick Blue → -Blue becomes ready beside Red. On the MODS tab, Import mod .zip must open the -same system picker and install the chosen archive on return. - -## Tab structure - -`self.tab` is one of `"red"`, `"blue"`, `"yellow"`, `"gold"`, `"mods"`, -`"find"`. The tab bar draws one chip per game plus MODS / FIND chips and -rebuilds `self.tabRects` every frame so `mousepressed` can dispatch clicks; -switching tabs mid-import is allowed (a dropped ROM still routes by SHA-1 -regardless of which tab shows). Yellow uses the bright gold rail colour; -Gold (Gen 2) uses a deeper amber so the two stay distinct. On **NX**, -**Scan again** is stricter: it only starts an import whose SHA-1 matches the -open game tab, so a shared `imports/` folder with Red+Yellow cannot jump -Yellow → Red. - -- A game tab (`_drawGamePanel`) shows the ROM card, the SAVE FILES card, the - Play button, and the SAVE SLOT card in a responsive two-column grid (see - Responsiveness). The MODS tab (`_drawModsPanel`) shows the mod list instead. -- The self-updater banner (`self.Check`, see `docs/updater.md`) draws as a - centered pill in a reserved band just above the footer, on every tab. That - position is unchanged by this redesign, so `docs/updater.md` needed no edits. - -## Save slot model - -All slot I/O lives in `src/core/SaveData.lua` and goes through the same fs -abstraction (`persistFs`) every other save/options call uses, so portable -mode (an `io.*` filesystem used when `portable.txt` marks the install) -keeps working unchanged. - -- **Files.** A version's playthroughs live under `saves/<version>/`, one file - per slot: `saves/<version>/slot1.lua` plus a rolling `.bak` and staged - `.tmp` witness (`slotNames`), mirroring the write/recovery discipline - `SaveData.save`/`load` already use for the flat legacy file. Slot ids match - `slot%d+`; `createSlot` allocates one past the highest existing number so a - reused id can never collide with a lingering file. -- **Registry.** The ordered slot list and which one is active persist in - `options.lua` (via the existing `SaveData.loadOptions`/`saveOptions`): - `options.saveSlots = { [version] = { list = {"slot1", ...}, active = "slot1" } }`. - Custom slot labels (#205) live alongside them in the same registry: - `options.saveSlots[version].names = { slot1 = "Nuzlocke" }`, written by - `SaveData.renameSlot` (trimmed; an empty label clears it) and surfaced on - each `listSlots` row as `label` (the launcher row shows `label`, falling - back to the player name). `deleteSlot` drops the label with the slot. - Renaming never touches the save file, so an empty slot can be labeled. - On desktop, right-clicking a slot row opens the inline rename modal - (Enter commits, Esc cancels); touch has no secondary button, so the - affordance is desktop-only. -- **Active slot resolution.** `saveNames(version)`, the function every - existing caller (`TitleState` hasSave/load/save, recovery order) already - goes through, now resolves the *active* slot instead of a fixed flat name. - Resolved once per version per process (`ensureVersionSlots`, cached in - `activeSlotCache`/`slotsChecked`): a registry entry wins; otherwise a lazy - legacy migration may create one; otherwise the flat legacy path is used - (`save.lua` / `save_blue.lua`), so a pre-slots install keeps working as before. -- **Legacy migration.** One-time per version, lazy on first - `listSlots`/`load`/`saveNames` call (`tryMigrateLegacy`): if a flat legacy - file exists and no `saves/<version>/` registry does, its main + `.bak` are - copied into `saves/<version>/slot1.lua(.bak)`, verified readable - (`decodeSlot`: main, then `.tmp`, then `.bak`), and only then are the - originals removed and `slot1` registered as active. A copy that fails to - verify leaves the originals in place; migration never loses data. - -The launcher-facing API: -- `SaveData.listSlots(version)` -> array of `{id, exists, name, meta}` for - every registered slot. `name` is the save's player name, or `nil` for an - empty slot; `meta` is `{badges, timeText, dexCount}` (the same fields the - title screen's `ContinueInfo` shows) or `nil`. The pure part, - `SaveData.slotSummary(save)`, is unit-testable with no filesystem. -- `SaveData.setActiveSlot(version, slotId)` registers the id if new, persists - it as active, and updates the process cache so the very next save/load - lands there. The launcher calls this the moment a slot row is clicked - (`RomImporter:_selectSlot`); pressing Play needs no signature change, since - `Game.lua`/`main.lua` still just call `SaveData.load()`/`save()`. -- `SaveData.createSlot(version)` -> new slot id, registered but with **no - save file written**. An empty slot means the title screen offers NEW GAME - only, which needs no further changes. -- `SaveData.deleteSlot(version, slotId)` removes the slot's - main/`.bak`/`.tmp` files, drops it from the registry, and if it was active - points active at another remaining slot (or clears active when the list is - empty). The launcher's SAVE SLOT panel Delete control calls this. - -## Launcher mod manager - -`src/mods/LauncherMods.lua` is a launcher-only read of the mod set. It runs -before `Game:load`, so **it never loads a mod's entry chunk**; only -`manifest.json` is read and validated (`src/mods/Manifest.validate`), the way -`Loader:_discover` finds mods without running them. The real loader -(`src/mods/Loader.lua`) still owns the actual load at boot. - -- `LauncherMods.list()` scans `mods/` one level deep (first id wins on a - duplicate) and returns one row per mod: - `{id, name, version, badge, description, enabled, status, statusDetail}`. - `badge` is the manifest's `category`, falling back to `profile`, then - `"MOD"`, uppercased. `enabledByVersion` contains an answer for each game; - missing entries default to enabled (except experimental mods), matching the - loader. On the first run with per-game controls, legacy shared choices are - copied to every installed game's answer. -- `status` is `"ok"`, `"warn"`, or `"conflict"`, computed by the pure - `LauncherMods.deriveList`/`statusFor` against `ManagerState.resolveToggle` - and the validated manifests: `conflict` when enabling this mod collides - with another enabled one; `warn` for an out-of-range `game_version` or an - absent/disabled/wrong-version hard dependency; `ok` otherwise. Having no - `love.*` calls, this half is table-driven by the test suite on its own. -- `LauncherMods.setEnabled(id, bool, version)` persists the selected game's - answer, so the running game and the in-game `ManagerState` see the change on - next boot. The MODS panel renders a coloured checkbox for Red, Blue, Yellow, - and Gold on every row and re-derives the list right away - (`RomImporter:_refreshMods`) so a status change (e.g. a new conflict) - shows without waiting for a reload. -- `LauncherMods.installZip(path)` mounts the archive with - `love.filesystem.mount`, locates the mod root via `locateRoot` (manifest at - the zip root, or inside one top-level folder), validates its manifest, and - copies the tree into the save-dir `mods/<id>/` before unmounting. Rejects a - duplicate of an already-installed mod id, and accepts either an external - path string or a LOVE `DroppedFile`, staging a dropped file into a save-dir - temp first (mount only reaches save-dir-relative paths), the same way - `RomImporter` handles a dropped ROM. A failed copy rolls its partial tree - back, and every path unmounts and clears the staged temp file. -- `LauncherMods.uninstall(id)` removes `mods/<id>/` and clears - `options.mods[id]` so a later reinstall starts from the loader's default - (enabled). The mods panel Delete control calls this and re-derives the list. -- A mod that declares `github` shows its total GitHub downloads (every - release's summed asset `download_count`, from the same cached release - fetch the update check uses) as a highlighted body line like "12,345 - downloads across all releases - Released 2024-05-31 - Updated 2026-07-01" - (first and latest `published_at`). Old cache entries written before the - counts existed show no line rather than a wrong zero; a manual check - refreshes them. -- The MODS panel sorts its rows by Name, Popularity (downloads), - Release date (first release), or Last updated, chosen by chips under the - header and persisted in `options.modSort`. Mods without release data - (no `github` field, or a stale cache) sink to the bottom of data sorts. - -## Import / Export save - -The SAVE FILES card wires a raw Gen1 `.sav` battery image to the save slots -through `src/import/SaveFileIO.lua`, which sits on top of -`src/save_convert/SaveConvert.lua` and the slot API in `SaveData`. - -- **Import save** is live once the game's ROM is imported (playable). - On desktop it opens a native `.sav` picker (`chooseSav`); on Android, - `love.system.pickFile("sav")` → `picked_save.sav`, same SAF path as ROMs. - On **NX (Switch)** there is no picker: copy a `.sav` into - `getSaveDirectory()/imports/saves/<red|blue|yellow>/` via MTP / SD / FTP - (one folder per game), then press **Import save** on that game’s tab to - ensure the inbox and rescan (same pattern as the ROM `imports/` and mod - `imports/mods/` inboxes). Hidden `._*.sav` AppleDouble sidecars are skipped. - `SaveFileIO.importToSlot` reads the bytes (an absolute path, a save-dir - relative name, a dropped LOVE file, or raw bytes), - guards the 32768-byte size, runs `SaveConvert.importSav` (which also rejects - a bad main-data checksum), then registers a fresh slot (`SaveData.createSlot`), - writes it (`SaveData.writeSlot`), and makes it active (`SaveData.setActiveSlot`). - The meta stamp is re-stamped off `gen1_import` to the current numeric format - so `SaveData.load`'s migration pass accepts the slot. On success the SAVE SLOT - panel is refreshed with the new slot selected. On **NX**, a successful inbox - import retires the file to `*.sav.imported` and records a content hash in - `imports/saves/<game>/.imported-sha1` so a second **Import save** (or the same - bytes under a new name) does not clone slots; failures leave the original - `.sav`. Only that game’s folder is scanned. -- **Export save** is live only when the active slot actually holds a save - (checked against `listSlots`). `SaveFileIO.exportActiveSlot` loads the active - slot, encodes it back with `SaveConvert.exportSav` (a slot never keeps - `rawImport`, so this is a zero-filled template export, which is valid), and - writes `exports/<version>/gen1recomp-<version>-<slotId>.sav` under the same - root `persistFs` writes slots to: the portable game folder when `portable.txt` - marks the install, otherwise the save directory (`exports/` and - `exports/<version>/` are created as needed; #752). On desktop it returns the - absolute path (`SaveData.portableBaseDir()` when portable, else - `love.filesystem.getSaveDirectory()`), which the notice line shows with an - "Open folder" affordance (`love.system.openURL("file://" .. dir)`). - On Android the bytes are also staged as `pending_export.sav` and - `love.system.createFile(suggestedName)` opens `ACTION_CREATE_DOCUMENT` so the - player can save to Downloads / Drive / etc.; on return `export_done.flag` - makes focus show "Save exported." - On **NX**, export success sets a notice with the `exports/<game>/` path and an - MTP-oriented hint — no `openURL` / Open folder (pull the file via MTP / - SD / FTP instead). -- **Drag-drop.** `filedropped` routes a `.sav` to the import path for the - currently active game tab; when a non-game tab (mods, or the locked yellow - placeholder) is showing it defaults to red, the always-present first game - (`_savedropTarget`). `.gb` (ROM) and `.zip` (mod) routing is unchanged. -- **Failure UX.** Every error path (wrong size, bad checksum, write failure, - nothing to export, ROM not imported yet) surfaces as a red notice line on the - card. Nothing raises and nothing silently no-ops. - -`SaveFileIO` is love-free enough to unit-test through the same in-memory -filesystem stub the slot backend uses (`tests/engine/save_file_io_tests.lua`). - -## Responsiveness - -Every measurement derives from `love.graphics.getDimensions()` each frame -plus the existing global scale `s = clamp(height / 768, 0.7, 1.6)`; nothing -assumes a fixed window size. The game panel's two-column grid (ROM/SAVE -FILES/Play on the left, SAVE SLOT on the right) collapses to one stacked -column, slot card below Play, when the window is too narrow for both -`~300 * s`-wide columns. The save-slot list and the mod list both scroll -(wheel, or drag on touch/desktop) clamped to their own content extent, -recomputed every draw. The tab bar labels only the active chip so it stays -narrow-safe, and content caps out at `~1440 * s` wide, centered. - -The desktop window has a floor of 480x360 (`conf.lua` `minwidth`/`minheight`), -under which the cards stop being readable at all. Mobile ignores it: those -windows are fullscreen. - -### Page scroll - -Two columns fit any window the launcher is likely to open in; one stacked -column does not. On a phone-shaped window the ROM card, SAVE FILES, Play and -SAVE SLOT together run past the bottom, and a footer pinned to the window -bottom painted over them with the overflow unreachable. - -So the whole column under the tab bar -- panel, updater banner, footer -- -scrolls as one page whenever it is taller than the room below the tab bar: - -- The strip, logo and tab bar stay pinned, so navigation is always on screen. - Everything else draws at `contentTop - pageScroll` inside a scissor, and the - footer is laid out downward from `footerTop` right after the content instead - of upward from the window bottom. -- `RomImporter.pageScrollFor(naturalH, viewportH, scroll)` is the whole - decision, pure and pinned by `tests/engine/launcher_page_scroll.lua`. A - window that grows back drags the offset down with it, so the page can never - stay parked past its own end. -- The panels report their natural height as they draw (`_drawGamePanel` and - `_drawModsPanel` return it), so the decision reads the previous frame's - measurement -- the same one-frame settle the two lists already rely on. -- **One scroll axis at a time.** While the page scrolls, the panels draw - `paged`: the slot and mod lists take their natural height, keep no inner - scroll region and report a max of 0, so the wheel, the right stick and a drag - all move the page and never fight a list for the same gesture. Two-column - layouts do not overflow, `paged` stays false, and every one of these behaves - exactly as it did before. -- Hit testing follows the clip: `inside` (clicks) and `_ptIn` (hover) reject a - rect that scrolled out of the viewport, so a control that slid under the tab - bar cannot be clicked through it. Tab chips carry `pinned = true` and are - exempt. `pageScroll` resets on a tab change, each tab being a different - length. -- A press on empty background pans the page, resolved in `_updateSlotDrag` like - every other drag here. - -### Dragging on Android - -The launcher is handed no move events on any platform: `main.lua` forwards -neither `touchmoved` nor `mousemoved` while it is up, which is why every drag -here is resolved by polling inside `draw` instead. Desktop polls the mouse; -Android used to poll nothing at all ("no reliable pointer polling" meant its -mouse emulation), so it had no scroll gesture whatsoever -- fine while every -scroll region was an inner list with a wheel alternative, useless the moment -the page itself became the thing that scrolls, since a phone is exactly where -it overflows. - -`love.touch` is pollable, so `_pointerHold` reads the first active touch there -and hands `_updateSlotDrag` the same (held, y) pair the mouse gives on desktop. -Consequences: - -- Slot rows and mod toggles ARM on press and commit on release on Android too, - matching desktop, so a swipe that starts on a card scrolls instead of - selecting the row it started on. -- `touchPollable` (set once in `new`) gates all of it. Where `love.touch` is - missing, every Android path is exactly what it was: act on press, never arm, - no drag. diff --git a/docs/new-features.md b/docs/new-features.md index b633e4cb..2fca8c70 100644 --- a/docs/new-features.md +++ b/docs/new-features.md @@ -19,15 +19,12 @@ Features intentionally added beyond the original Pokémon Red, Blue, and Yellow * **Soft reset button combination** * **Keyboard and controller rebinding** * **Mod profiles** with separate mod settings and save slots -* **Sandboxed mods**: an installed mod can read only its own folder and write only its own storage, so it cannot reach the rest of your device, and mods that need the internet or heavy background work do it through permissions the mod manager shows you, without freezing the game +* **Sandboxed mods**: an installed mod can read only its own folder and write only its own storage * **Improved launcher and save editor UI**, including background downloads and update checks * **Direct-launch options** for shortcuts, Steam entries, and handheld frontends -* **Custom boot branding** ## Pokémon Gold (Gen 2) -A fourth game the launcher can import and play, built from pret/pokegold the same way Red/Blue/Yellow are built from pokered (and pokeyellow). Port extras beyond the cartridge: - * **COLOR, zoom, tilt, GBC FX, and quick save/load** * **UI that stays fixed while the overworld zooms** * **Border-block surrounds** for maps smaller than the screen @@ -38,6 +35,4 @@ A fourth game the launcher can import and play, built from pret/pokegold the sam * **Mod manager** with Gen 1 mod adapters, per-game targeting, and `modkit gen2check` * **Followers** for mods, plus Gen 2-only registries and hooks * **On-screen touch pad** and controller SELECT for registered items - - * **Older mods keep loading** after the sandbox change, through per-mod compat stand-ins for the pre-sandbox globals diff --git a/docs/tiled-map-editing.md b/docs/tiled-map-editing.md deleted file mode 100644 index 6ec9f7dd..00000000 --- a/docs/tiled-map-editing.md +++ /dev/null @@ -1,65 +0,0 @@ -# Tiled map editing (mod authoring) - -`tools/tiled_export.py` turns the imported ROM cache into a -[Tiled](https://www.mapeditor.org) workspace, so maps can be edited in a -real map editor and exported back out as a mod. The original had no map -editor at all; the port's own map data is plain Lua, which is what makes -this a data path rather than an asset path. - -Editing is done in our own Tiled build, -[bryanthaboi/tiled_gen1recomp](https://github.com/bryanthaboi/tiled_gen1recomp/releases), -which ships the `gen1-mod-export` extension the workspace relies on. Grab it -from that repo's releases; upstream Tiled opens the workspace but cannot -export a mod out of it. - -```sh -python3 tools/tiled_export.py # -> build/tiled/ (gitignored) -``` - -Then open `build/tiled/gen1.tiled-project` in that build of Tiled. - -- **The overworld is one surface.** All 222 maps become `maps/*.tmj`, and - `kanto.world` places the 36 connected overworld maps at their real - connection offsets. That world is pre-loaded (seeded into the workspace's - Tiled session), so opening any one overworld map draws its neighbors around - it and you scroll and edit straight across the seams. Everything else is a - double-click away in Tiled's project panel. -- **Extending Kanto wires both ends.** A connection lives on both maps, so - hooking a new map onto a base map also emits the return connection as a - patch on that base map, keeping its other directions intact. The return - offset is derived, not guessed: all 78 vanilla reciprocal pairs satisfy - `back.offset == -offset`. -- **A Tiled tile is a gen1 block.** Each of the 24 tilesets becomes a Tiled - tileset whose tiles are its 32x32 blocks, composited from the 8x8 sheet, - so a tile layer *is* the map's `blocks` array. Warps, signs and objects - sit on the 16px cell grid in object layers, which is the grid the engine - addresses them on. -- **Collision is visible.** View > Show Tile Collision Shapes draws the real - walkability: a rectangle covers each cell whose feet tile is not in the - tileset's `walkable` list, which is the rule `src/world/Map.lua` applies. -- **Maps are shown in their real colors.** Each map is atlased in the SGB - palette it renders with, so Cerulean is blue and Lavender is purple in the - editor exactly as in game. Vanilla resolves that through a cascade with - interiors inheriting the last outdoor map, so the workspace mirrors the - cascade and walks the warp graph to colour interiors. Changing a map's - `palette` exports `palette = "..."` on the record, which beats the cascade, - and the editor offers the real palette names as a dropdown. -- **New blocks and new tilesets.** `blocksets/*.tmj` show a tileset's blocks - as raw 8x8 tiles, four by four, so new blocks can be composed there; - per-tile flags on `tilesets/tiles_*.tsj` become `walkable`, `waterTiles`, - `doorTiles` and the rest. -- **Export is a diff, not a fork of the data.** The `gen1-mod-export` - extension (shipped in `tiled_gen1recomp`) writes either one map file or a whole - loadable mod folder. An edited vanilla map diffs against the imported data - and emits `mod.content.maps:patch` carrying *only* the fields that moved, so - a mod covers the parts it changes and leaves the rest to the base game; a - new map gets `:register` at an index of 1000 or above. An unchanged map - exports nothing at all. Exports pass `tools/modkit.py validate` and `lint`. -- **Or the whole record, on request.** Ticking `exactExport` on a map switches - it to `mod.content.maps:override`, pinning the map to exactly what the - editor shows. It is off by default because an override wins outright over - any other mod patching that map, where a patch composes. - -No ROM-derived art travels into an exported mod: a tileset still drawing on -the player's own imported sheet references that path rather than shipping the -pixels, and only a sheet the author supplied is copied in. diff --git a/mods/example_mew_starter/README.md b/mods/examples/example_mew_starter/README.md similarity index 100% rename from mods/example_mew_starter/README.md rename to mods/examples/example_mew_starter/README.md diff --git a/mods/example_mew_starter/assets/mew_back_inverted.png b/mods/examples/example_mew_starter/assets/mew_back_inverted.png similarity index 100% rename from mods/example_mew_starter/assets/mew_back_inverted.png rename to mods/examples/example_mew_starter/assets/mew_back_inverted.png diff --git a/mods/example_mew_starter/assets/mew_front_inverted.png b/mods/examples/example_mew_starter/assets/mew_front_inverted.png similarity index 100% rename from mods/example_mew_starter/assets/mew_front_inverted.png rename to mods/examples/example_mew_starter/assets/mew_front_inverted.png diff --git a/mods/example_mew_starter/main.lua b/mods/examples/example_mew_starter/main.lua similarity index 100% rename from mods/example_mew_starter/main.lua rename to mods/examples/example_mew_starter/main.lua diff --git a/mods/example_mew_starter/manifest.json b/mods/examples/example_mew_starter/manifest.json similarity index 100% rename from mods/example_mew_starter/manifest.json rename to mods/examples/example_mew_starter/manifest.json diff --git a/mods/example_mew_starter/mod.card b/mods/examples/example_mew_starter/mod.card similarity index 100% rename from mods/example_mew_starter/mod.card rename to mods/examples/example_mew_starter/mod.card diff --git a/mods/examples/example_silly_oak/CHANGELOG.md b/mods/examples/example_silly_oak/CHANGELOG.md deleted file mode 100644 index ec81d39b..00000000 --- a/mods/examples/example_silly_oak/CHANGELOG.md +++ /dev/null @@ -1,12 +0,0 @@ -# Changelog - -Format: [keep a changelog](https://keepachangelog.com/en/1.1.0/). -Version headings match `manifest.json`'s `version`. - -## 1.0.0 - -### Added - -- `intro.oak_speech.build` wrap that injects toast / MEW / snack / rival-trust / pineapple beats. -- Answers written to `mod.save` via `intro.oak_speech.answered`. -- Custom `toast_kid.png` sprite shown mid-speech. diff --git a/mods/examples/example_silly_oak/README.md b/mods/examples/example_silly_oak/README.md deleted file mode 100644 index 7278827e..00000000 --- a/mods/examples/example_silly_oak/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Silly Oak Intro Example - -Hooks Oak's NEW GAME speech: extra questions, sprite swaps (Oak, rival, -player, MEW, and a custom Toast Kid pic), and answers stored in `mod.save`. - -## Try it (play through yourself) - -```sh -rm -rf mods/example_silly_oak -cp -r mods/examples/example_silly_oak mods/ -love . -``` - -Then **NEW GAME** and mash A / pick the menus. Disable or delete -`mods/example_silly_oak` when you're done so vanilla boots clean. - -## Headless check - -```sh -luajit mods/examples/example_silly_oak/tests/example_silly_oak_test.lua -``` - -## Auto driver (screenshots + save asserts) - -```sh -rm -rf mods/example_silly_oak -cp -r mods/examples/example_silly_oak mods/ -SHOT_DIR=/tmp/silly_oak POKEPORT_IDENTITY=silly_oak_driver_test \ - POKEPORT_DRIVER=tests/drivers/silly_oak_intro_test.lua POKEPORT_SPEED=8 love . -``` - -`POKEPORT_IDENTITY` keeps this run's save out of your normal slot. - -## What it demonstrates - -| Seam | Where | -|---|---| -| `hooks:wrap("intro.oak_speech.build")` | `main.lua` — reshape the step list | -| `mod.ui.insertStepAfter` / `insertStepBefore` | `main.lua` — anchored on vanilla step ids | -| step kinds `say` / `yesno` / `choice` | `main.lua` | -| pics: `"oak"`, `"rival"`, `"player"`, pokemon, custom image | `main.lua` | -| `events:on("intro.oak_speech.answered")` | `main.lua` → `mod.save` | -| `events:on("intro.oak_speech.finished")` | `main.lua` | diff --git a/mods/examples/example_silly_oak/assets/toast_kid.png b/mods/examples/example_silly_oak/assets/toast_kid.png deleted file mode 100644 index 4778bdddf59057a01281609e5789eeffe4debe60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^79h;Q1|(OsS<3;bJ)SO(Ar-gY-r6X5$bg6SLeB$1 ziN-*QMnTDDLy2ZSHIDK=)uWv21780r*Gzf2Cr1Bm@w2FZY(MI3{X!cMU^8QM_G(ed zf-lx!vE2-&RSpgZ0v>#>i(Y%`z+OuRMn=Yd#x*;-moZfzN?}-@@ui_*GV|hzg6T^c z-J4zKPB!v&VOlObecQjr7j?6&_`kKC-J!FDf6xAHNutp^H+All+V=aD5_gI^GY1e@ gFnxLTZ^2hqwsQ+i8bzHh0R6$>>FVdQ&MBb@088Xwe*gdg diff --git a/mods/examples/example_silly_oak/main.lua b/mods/examples/example_silly_oak/main.lua deleted file mode 100644 index 8fe9ae02..00000000 --- a/mods/examples/example_silly_oak/main.lua +++ /dev/null @@ -1,107 +0,0 @@ --- Gallery entry: reshape Oak's intro speech with extra questions, sprite --- swaps (oak / rival / player / pokemon / a custom image), and answers --- that land in mod.save. --- --- Uses hooks:wrap("intro.oak_speech.build") plus intro.oak_speech.answered. - -return function(mod) - local toastPic = mod.path .. "/assets/toast_kid.png" - - mod.hooks:wrap("intro.oak_speech.build", function(next, steps, speech) - steps = next(steps, speech) - - -- after oak says hello, immediately derail - mod.ui.insertStepAfter(steps, "oak_welcome", { - id = "silly_quiz_intro", - kind = "say", - pic = "oak", - text = "Before we start,\nI have a few\vquestions.\fImportant ones.\nScientific ones.", - }) - - mod.ui.insertStepAfter(steps, "silly_quiz_intro", { - id = "silly_toast", - kind = "yesno", - pic = "oak", - saveKey = "likes_toast", - text = "Do you like\ntoast?", - }) - - -- brand new sprite mid-speech - mod.ui.insertStepAfter(steps, "silly_toast", { - id = "silly_toast_kid", - kind = "say", - pic = { type = "image", path = toastPic }, - reveal = "fade", - saveKey = nil, - text = "This is Toast Kid.\nHe is not a\vPOKéMON.\fHe just showed up\none day.\fAnyway.", - }) - - -- existing mon with a wipe + cry, parked after the real demo mon - mod.ui.insertStepAfter(steps, "demo_mon", { - id = "silly_mew", - kind = "say", - pic = { type = "pokemon", id = "MEW" }, - reveal = "wipe", - cry = "MEW", - text = "This is MEW.\nPlease do not\vtell anyone\vI showed you.", - }) - - mod.ui.insertStepAfter(steps, "silly_mew", { - id = "silly_snack", - kind = "choice", - pic = "oak", - saveKey = "snack", - text = "Pick a snack.\nThis goes on\vyour permanent\vrecord.", - choices = { "BERRIES", "LEFTOVERS", "OLD ROD" }, - }) - - -- swap to rival pic for a loaded question before naming him - mod.ui.insertStepBefore(steps, "ask_rival_name", { - id = "silly_trust", - kind = "choice", - pic = "rival", - reveal = "fade", - saveKey = "trusts_rival", - text = "Look at this kid.\nTrustworthy?", - choices = { "SURE", "NO" }, - values = { true, false }, - }) - - -- player pic for one last bit after both names are set - mod.ui.insertStepAfter(steps, "name_rival", { - id = "silly_pineapple", - kind = "yesno", - pic = "player", - saveKey = "pineapple_on_pizza", - text = "{PLAYER}. Be honest.\nPineapple on\vpizza?", - }) - - mod.ui.insertStepAfter(steps, "silly_pineapple", { - id = "silly_closing", - kind = "say", - pic = "oak", - text = "Great. Terrible.\nI have notes.\fLet's pretend this\nwas normal.", - }) - - return steps - end) - - -- every answered step with a saveKey lands in mod.save (and therefore - -- save.modData[mod.id] once the slot is written) - mod.events:on("intro.oak_speech.answered", function(ev) - if not ev.saveKey then return end - mod.save:set(ev.saveKey, ev.value) - mod.log:info("intro answer %s = %s", tostring(ev.saveKey), tostring(ev.value)) - end) - - mod.events:on("intro.oak_speech.finished", function(ev) - local answers = ev.answers or {} - for key, value in pairs(answers) do - if mod.save:get(key) == nil then - mod.save:set(key, value) - end - end - mod.save:set("quiz_done", true) - mod.log:info("silly oak quiz done") - end) -end diff --git a/mods/examples/example_silly_oak/manifest.json b/mods/examples/example_silly_oak/manifest.json deleted file mode 100644 index 928b5889..00000000 --- a/mods/examples/example_silly_oak/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "example_silly_oak", - "name": "Silly Oak Intro Example", - "version": "1.0.0", - "api": 2, - "entry": "main.lua", - "profile": "content", - "category": "UI", - "game_version": ">=0.0.0-0 <2.0.0", - "priority": 100, - "dependencies": [], - "optional_dependencies": [], - "conflicts": [], - "description": "Reshapes Oak's intro speech with extra questions, sprite swaps, and answers saved to mod.save." -} diff --git a/mods/examples/example_silly_oak/mod.card b/mods/examples/example_silly_oak/mod.card deleted file mode 100644 index 93a0bef0..00000000 --- a/mods/examples/example_silly_oak/mod.card +++ /dev/null @@ -1,21 +0,0 @@ --- Sharing metadata for the manager detail pane. -return { - summary = "Oak asks dumb questions during the intro and remembers your answers.", - author = "Pokemon Gen 1 Recompilation Project", - contact = "https://github.com/bryanthaboi/gen1recomp", - tags = { "intro", "ui", "oak", "hooks" }, - differences = { - changed = { - "Oak's NEW GAME speech gains extra questions and sprite beats", - }, - added = { - "mod.save keys: likes_toast, snack, trusts_rival, pineapple_on_pizza, quiz_done", - "Custom Toast Kid pic mid-intro", - }, - known = { "vanilla naming and the shrink-away still run" }, - }, - credits = { - { who = "Pokemon Gen 1 Recompilation Project", for_ = "the intro.oak_speech hooks" }, - }, - compat = { engine = ">=0.0.0 <2.0.0", modApi = 2 }, -} diff --git a/mods/examples/example_silly_oak/tests/example_silly_oak_test.lua b/mods/examples/example_silly_oak/tests/example_silly_oak_test.lua deleted file mode 100644 index 1bceba2c..00000000 --- a/mods/examples/example_silly_oak/tests/example_silly_oak_test.lua +++ /dev/null @@ -1,159 +0,0 @@ --- Standalone: luajit mods/examples/example_silly_oak/tests/example_silly_oak_test.lua --- Covers the intro.oak_speech build hook, step helpers, sprite descriptors, --- and answers landing in mod.save. --- --- Needs an imported ROM dataset (data/generated/). Headless CI and a --- fresh checkout without a ROM skip cleanly -- the gallery is also --- covered by tests/mod_examples_tests.lua when generated data is present. -package.path = "./?.lua;./?/init.lua;" .. package.path - -local function hasGenerated() - local handle = io.open("data/generated/constants.lua", "r") - if handle then handle:close() return true end - return false -end -if not hasGenerated() then - print("example_silly_oak_test skipped (needs data/generated/)") - os.exit(0) -end - -local T = require("tests.modkit") -local Runtime = require("src.mods.Runtime") -local OakSpeech = require("src.ui.OakSpeech") -local Data = require("src.core.Data") -Data:load() - -local run = T.sdk.loadMod("mods/examples/example_silly_oak", { data = Data }) -T.eq(#run.errors, 0, "loads clean (" .. tostring(run.errors[1]) .. ")") - -local mod = run.mod -T.check(mod ~= nil and mod.state == "loaded", "mod reached the loaded state") -local ModUI = require("src.ui.ModUI") -local bucket = function() - return run.loader.modSave.example_silly_oak or {} -end -local toastPath = (mod.path or "mods/examples/example_silly_oak") - .. "/assets/toast_kid.png" - --- ------- build hook injects every silly beat around vanilla anchors - -local speech = OakSpeech.new({ - data = Data, - save = { player = { name = "RED", rival = "BLUE" } }, - stack = { push = function() end, pop = function() end }, -}, nil) -local steps = speech:buildSteps() - -local ids = {} -for _, step in ipairs(steps) do ids[#ids + 1] = step.id end -local function has(id) - for _, x in ipairs(ids) do if x == id then return true end end - return false -end - -T.check(has("oak_welcome") and has("name_player") and has("shrink"), - "vanilla anchors still present") -T.check(has("silly_quiz_intro") and has("silly_toast") and has("silly_toast_kid"), - "toast quiz beats injected") -T.check(has("silly_mew") and has("silly_snack"), - "MEW reveal and snack choice injected") -T.check(has("silly_trust") and has("silly_pineapple") and has("silly_closing"), - "rival trust + pineapple beats injected") - --- order: toast kid before demo_mon, mew after demo_mon, trust before rival ask -local function indexOf(id) - for i, x in ipairs(ids) do if x == id then return i end end - return 0 -end -T.check(indexOf("silly_toast_kid") < indexOf("demo_mon"), - "Toast Kid shows before the demo mon") -T.check(indexOf("demo_mon") < indexOf("silly_mew"), - "MEW shows after the demo mon") -T.check(indexOf("silly_trust") < indexOf("ask_rival_name"), - "trust question is before rival naming") -T.check(indexOf("name_rival") < indexOf("silly_pineapple") - and indexOf("silly_pineapple") < indexOf("legend"), - "pineapple lands between rival name and the legend beat") - --- ------- step shapes cover choice / yesno / custom image / pokemon - -local byId = {} -for _, step in ipairs(steps) do byId[step.id] = step end - -T.eq(byId.silly_toast.kind, "yesno", "toast is a yes/no") -T.eq(byId.silly_toast.saveKey, "likes_toast", "toast writes likes_toast") -T.eq(byId.silly_snack.kind, "choice", "snack is a multi choice") -T.eq(#byId.silly_snack.choices, 3, "snack has three options") -T.check(byId.silly_toast_kid.pic and byId.silly_toast_kid.pic.type == "image", - "Toast Kid uses a custom image pic") -T.check(byId.silly_mew.pic and byId.silly_mew.pic.type == "pokemon" - and byId.silly_mew.pic.id == "MEW" and byId.silly_mew.cry == "MEW", - "MEW beat uses pokemon pic + cry") -T.eq(byId.silly_trust.pic, "rival", "trust question shows the rival pic") - --- ------- resolvePic covers trainer / pokemon / player / image shorthand - -local oakImg = OakSpeech.resolvePic({ data = Data }, "oak", speech) -local rivalImg = OakSpeech.resolvePic({ data = Data }, "rival", speech) -local playerImg = OakSpeech.resolvePic({ data = Data }, "player", speech) -local mewImg, mewFlip = OakSpeech.resolvePic({ data = Data }, - { type = "pokemon", id = "MEW", flip = true }, speech) -local customImg = OakSpeech.resolvePic({ data = Data }, - { type = "image", path = toastPath }, speech) --- headless love stub may return nil images; the call itself must not throw -T.check(oakImg == speech.oakPic or oakImg == nil or type(oakImg) == "userdata" - or type(oakImg) == "table", - "oak shorthand resolves without error") -T.check(rivalImg == speech.rivalPic or rivalImg == nil or type(rivalImg) == "userdata" - or type(rivalImg) == "table", - "rival shorthand resolves without error") -T.check(playerImg == speech.playerPic or playerImg == nil - or type(playerImg) == "userdata" or type(playerImg) == "table", - "player shorthand resolves without error") -T.check(mewFlip == true, "pokemon flip flag is honored") -T.check(customImg ~= nil or true, "custom image path is accepted") - --- ------- answered event writes mod.save (loader.modSave bucket) - -Runtime.emit("intro.oak_speech.answered", { - saveKey = "likes_toast", value = true, label = "YES", index = 1, - step = byId.silly_toast, speech = speech, -}) -Runtime.emit("intro.oak_speech.answered", { - saveKey = "snack", value = "OLD ROD", label = "OLD ROD", index = 3, - step = byId.silly_snack, speech = speech, -}) -Runtime.emit("intro.oak_speech.answered", { - saveKey = "trusts_rival", value = false, label = "NO", index = 2, - step = byId.silly_trust, speech = speech, -}) -Runtime.emit("intro.oak_speech.answered", { - saveKey = "pineapple_on_pizza", value = true, label = "YES", index = 1, - step = byId.silly_pineapple, speech = speech, -}) -Runtime.emit("intro.oak_speech.finished", { - speech = speech, answers = speech.answers, -}) - -local saved = bucket() -T.eq(saved.likes_toast, true, "likes_toast saved") -T.eq(saved.snack, "OLD ROD", "snack saved") -T.eq(saved.trusts_rival, false, "trusts_rival saved") -T.eq(saved.pineapple_on_pizza, true, "pineapple_on_pizza saved") -T.eq(saved.quiz_done, true, "quiz_done stamped on finish") - --- ------- ModUI step helpers (public surface) - -local tiny = { - { id = "a", kind = "say" }, - { id = "b", kind = "say" }, -} -ModUI.insertStepAfter(tiny, "a", { id = "mid", kind = "choice" }) -T.eq(tiny[2].id, "mid", "insertStepAfter lands behind the anchor") -ModUI.insertStepBefore(tiny, "b", { id = "pre_b", kind = "yesno" }) -T.eq(tiny[3].id, "pre_b", "insertStepBefore lands ahead of the anchor") -ModUI.removeStep(tiny, "mid") -T.check(tiny[2].id ~= "mid", "removeStep drops by id") - -run.release() -T.finish("example_silly_oak") From 65128e13a49e0df358378c94db12af56ecf1b115 Mon Sep 17 00:00:00 2001 From: bryanthaboi <bryanthaboi@theboisclub.com> Date: Sun, 16 Aug 2026 07:28:08 -0400 Subject: [PATCH 3/5] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1ffe28fa..68acd386 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -37,12 +37,16 @@ body: id: os attributes: label: Which build are you running + description: Official release targets. Pick Multiple platforms if you saw it on more than one. options: - macOS - Windows - Linux - Android - iOS + - Nintendo Switch + - Xbox + - Anbernic RG34XXSP - Multiple platforms validations: required: true From 1151c188a73a3f5a2162eb773a8b9eaaa463e1eb Mon Sep 17 00:00:00 2001 From: bryanthaboi <bryanthaboi@theboisclub.com> Date: Sun, 16 Aug 2026 08:55:40 -0400 Subject: [PATCH 4/5] CLOSES #1211, CLOSES #1228, CLOSES #1229, CLOSES #1232, CLOSES #1251, CLOSES #1265, CLOSES #1267, CLOSES #1276, CLOSES #1279, CLOSES #1282, CLOSES #1293, CLOSES #1296, CLOSES #1303, CLOSES #1329, CLOSES #1338, CLOSES #1341, CLOSES #1343, CLOSES #1344, CLOSES #1368, CLOSES #1385, CLOSES #1388, CLOSES #1389, CLOSES #1391 --- data/scripts/flavor/pewter_city.lua | 143 ++++++++++++++- data/scripts/oaks_lab.lua | 1 + data/scripts/oaks_lab_yellow.lua | 1 + data/scripts/story.lua | 4 + data/scripts/story2.lua | 12 ++ data/scripts/story3.lua | 7 +- src/battle/BattleState.lua | 30 ++- src/battle/EffectRegistry.lua | 10 +- src/battle/gen2/AnimRunner.lua | 18 +- src/battle/gen2/Battle.lua | 111 ++++++++---- src/core/Music.lua | 6 +- src/script/gen2/Specials.lua | 53 +++--- src/script/gen2/Vm.lua | 7 +- src/ui/DexEntryMenu.lua | 162 +++++++++++++---- src/ui/LeaguePC.lua | 112 ++++++++++++ src/ui/NamingScreen.lua | 12 +- src/ui/TownMap.lua | 47 +++-- src/ui/gen2/BattleState.lua | 72 ++++---- src/ui/gen2/PokedexMenu.lua | 22 +-- src/world/NPC.lua | 45 ++--- src/world/OverworldController.lua | 7 +- src/world/gen2/World.lua | 71 ++++++-- .../battle_move_slot_dashes_bug1343.lua | 102 +++++++++++ .../blues_house_daisy_walking_bug1338.lua | 80 ++++++++ .../exp_traded_ot_survives_reload_bug1265.lua | 49 +++++ .../gen2_ball_throw_pic_latch_bug1232.lua | 82 +++++++++ .../engine/gen2_battler_gfx_rows_bug1231.lua | 58 ++++++ .../gen2_charge_move_anim_param_bug1293.lua | 94 ++++++++++ tests/engine/gen2_coin_case_bug1251.lua | 83 +++++++++ .../gen2_enemy_move_fail_text_bug1296.lua | 166 +++++++++++++++++ .../engine/gen2_fishing_bite_gate_bug1368.lua | 92 ++++++++++ .../gen2_grass_encounter_tod_bug1389.lua | 115 ++++++++++++ .../gen2_pokedex_area_landmark_bug1267.lua | 88 +++++++++ .../engine/gen2_rocket_map_music_bug1385.lua | 93 ++++++++++ tests/engine/gen2_safeguard_bug1388.lua | 171 ++++++++++++++++++ tests/engine/gen2_shadow_ball_bgp_bug1269.lua | 47 +++++ .../engine/gen2_starter_nickname_bug1228.lua | 90 +++++++++ tests/engine/league_pc_bug1282.lua | 94 ++++++++++ .../engine/naming_screen_opacity_bug1329.lua | 86 +++++++++ tests/engine/npc_walk_cadence_bug1303.lua | 96 ++++++++++ tests/engine/nurse_bow_bug995.lua | 2 +- .../oaks_lab_rival_faces_down_bug1279.lua | 69 +++++++ tests/engine/pewter_museum_escort_bug1391.lua | 73 ++++++++ tests/engine/pokedex_entry_layout_bug1341.lua | 95 ++++++++++ .../engine/town_map_player_sprite_bug1344.lua | 86 +++++++++ tests/gen2_wild_cooldown_bug1229_test.lua | 116 ++++++++++++ tests/parity_ss_anne_departure.lua | 18 +- tests/parity_ss_anne_guard.lua | 7 +- tests/parity_true_color_ui.lua | 5 +- tests/run_tests.lua | 1 + 50 files changed, 2835 insertions(+), 276 deletions(-) create mode 100644 src/ui/LeaguePC.lua create mode 100644 tests/engine/battle_move_slot_dashes_bug1343.lua create mode 100644 tests/engine/blues_house_daisy_walking_bug1338.lua create mode 100644 tests/engine/exp_traded_ot_survives_reload_bug1265.lua create mode 100644 tests/engine/gen2_ball_throw_pic_latch_bug1232.lua create mode 100644 tests/engine/gen2_battler_gfx_rows_bug1231.lua create mode 100644 tests/engine/gen2_charge_move_anim_param_bug1293.lua create mode 100644 tests/engine/gen2_coin_case_bug1251.lua create mode 100644 tests/engine/gen2_enemy_move_fail_text_bug1296.lua create mode 100644 tests/engine/gen2_fishing_bite_gate_bug1368.lua create mode 100644 tests/engine/gen2_grass_encounter_tod_bug1389.lua create mode 100644 tests/engine/gen2_pokedex_area_landmark_bug1267.lua create mode 100644 tests/engine/gen2_rocket_map_music_bug1385.lua create mode 100644 tests/engine/gen2_safeguard_bug1388.lua create mode 100644 tests/engine/gen2_shadow_ball_bgp_bug1269.lua create mode 100644 tests/engine/gen2_starter_nickname_bug1228.lua create mode 100644 tests/engine/league_pc_bug1282.lua create mode 100644 tests/engine/naming_screen_opacity_bug1329.lua create mode 100644 tests/engine/npc_walk_cadence_bug1303.lua create mode 100644 tests/engine/oaks_lab_rival_faces_down_bug1279.lua create mode 100644 tests/engine/pewter_museum_escort_bug1391.lua create mode 100644 tests/engine/pokedex_entry_layout_bug1341.lua create mode 100644 tests/engine/town_map_player_sprite_bug1344.lua create mode 100644 tests/gen2_wild_cooldown_bug1229_test.lua diff --git a/data/scripts/flavor/pewter_city.lua b/data/scripts/flavor/pewter_city.lua index eb525b7d..137035f0 100644 --- a/data/scripts/flavor/pewter_city.lua +++ b/data/scripts/flavor/pewter_city.lua @@ -3,9 +3,8 @@ -- guide and SUPER_NERD2 garden nerd. -- -- The YOUNGSTER's gym escort (talk + east-exit onStep) lives in --- story5.lua so the lockstep RLE walk is not overwritten by this --- flavor merge. SUPER_NERD1's museum escort is not ported; only the --- YES/NO-branched flavor text is here. +-- story5.lua; SUPER_NERD1's museum escort (scripts/PewterCity.asm:47-113) +-- is below. local M = {} @@ -25,11 +24,138 @@ local function ask(game, s, cb) game.stack:push(TextBox.new(game, s, nil, { choice = cb })) end +-- RLEList_PewterMuseumGuy (engine/overworld/auto_movement.asm:199-204) +local museumGuySteps = { + "up", "up", "up", "up", "up", "up", + "left", "left", "left", "left", "left", "left", "left", "left", + "left", "left", "left", "left", "left", + "up", "up", "up", + "left", +} + +-- RLEList_PewterMuseumPlayer (engine/overworld/auto_movement.asm:192-197) +local museumPlayerRle = { + "NO", + "up", "up", "up", + "left", "left", "left", "left", "left", "left", "left", "left", + "left", "left", "left", "left", "left", + "up", "up", "up", "up", "up", "up", +} + +-- PewterMuseumGuyCoords (engine/events/pewter_guys.asm:58-75) +local museumPreambles = { + ["27,18"] = { "up", "up" }, + ["27,16"] = { "right", "left" }, + ["26,17"] = { "up", "right" }, + ["28,17"] = { "up", "left" }, +} + +-- PewterGuys (engine/events/pewter_guys.asm:1-49), same transform as +-- pewterEscort.playerPlan in story5.lua +local function museumPlan(x, y) + local pre = museumPreambles[x .. "," .. y] + if not pre then return nil end + local buf = {} + for i, d in ipairs(museumPlayerRle) do buf[i] = d end + buf[#buf] = pre[1] + for i = 2, #pre do buf[#buf + 1] = pre[i] end + local path = {} + for i = #buf, 1, -1 do path[#path + 1] = buf[i] end + local head = 0 + while path[head + 1] == "NO" do head = head + 1 end + local tail = #path + while tail > head and path[tail] == "NO" do tail = tail - 1 end + local steps = {} + for i = head + 1, tail do steps[#steps + 1] = path[i] end + return { steps = steps, guyHeadStart = math.floor(head / 8) } +end + +-- PewterCitySuperNerd1ShowsPlayerMuseumScript (scripts/PewterCity.asm:47-113) +local function museumEscortWalk(game, ow) + if ow.runner:isRunning() or #ow.scriptMoves > 0 then return false end + local plan = museumPlan(ow.player.cellX, ow.player.cellY) + if not plan then return false end + local Music = require("src.core.Music") + local t = text(game) + local guy = ow:npcByIndex(3) -- PEWTERCITY_SUPER_NERD1 + local head = plan.guyHeadStart + + -- SetSpritePosition2 + ShowObject back on his spawn (27,17), the same + -- snap walkHome does in story5.lua (scripts/PewterCity.asm:102-113) + local function walkOut() + if not guy then return end + local i = 0 + local function tick() + i = i + 1 + if i > 4 then + guy.cellX, guy.cellY = 27, 17 + guy.px, guy.py = 27 * 16, 17 * 16 + guy.moving = false + guy.targetX, guy.targetY = nil, nil + guy.facing = "down" + return + end + ow:scriptMove(guy, "down", 1, tick) + end + tick() + end + + -- SetSpritePosition1 pins him beside the museum door (map (17,12) minus + -- the +4 border offset = (13,8)), then MovementData_PewterMuseumGuyExit + local function afterWalk() + if guy then + guy.stepFrames = nil + guy.cellX, guy.cellY = 13, 8 + guy.px, guy.py = 13 * 16, 8 * 16 + guy.moving = false + guy.targetX, guy.targetY = nil, nil + guy.facing = "up" + end + Music.playMap(game.data, "PEWTER_CITY") + push(game, t._PewterCitySuperNerd1ItsRightHereText + or "It's right here!", walkOut) + end + + local function lockstep() + local i = 0 + local function tick() + i = i + 1 + local ps = plan.steps[i] + if not ps then + afterWalk() + return + end + local gs = museumGuySteps[head + i] + if guy and gs then ow:scriptMove(guy, gs, 1) end + ow:scriptMove(ow.player, ps, 1, tick) + end + tick() + end + + -- engine/overworld/movement.asm:737 (DoScriptedNPCMovement) + if guy then + guy.stepFrames = ow.player.stepFramesCur or ow.player.stepFrames + end + Music.play(game.data, "Music_MuseumGuy") + if guy and head > 0 then + local h = 0 + local function headTick() + h = h + 1 + if h > head then lockstep(); return end + ow:scriptMove(guy, museumGuySteps[h], 1, headTick) + end + headTick() + else + lockstep() + end + return true +end + M.PEWTER_CITY = { + museumEscort = { plan = museumPlan, guySteps = museumGuySteps }, talk = { - -- PewterCitySuperNerd1Text (scripts/PewterCity.asm): asks if you - -- checked out the museum; YES -> fossils comment, NO -> "you have - -- to go" (which in pokered also kicks off the escort script). + -- PewterCitySuperNerd1Text (scripts/PewterCity.asm:209-237): YES -> + -- fossils comment, NO -> "you have to go" and the museum escort TEXT_PEWTERCITY_SUPER_NERD1 = function(game, ow, npc, done) local t = text(game) ask(game, t._PewterCitySuperNerd1DidYouCheckOutMuseumText @@ -39,7 +165,10 @@ M.PEWTER_CITY = { or "Weren't those\nfossils from MT.\nMOON amazing?", done) else push(game, t._PewterCitySuperNerd1YouHaveToGoText - or "Really?\nYou absolutely\nhave to go!", done) + or "Really?\nYou absolutely\nhave to go!", function() + museumEscortWalk(game, ow) + if done then done() end + end) end end) end, diff --git a/data/scripts/oaks_lab.lua b/data/scripts/oaks_lab.lua index bb0b058f..039e519a 100644 --- a/data/scripts/oaks_lab.lua +++ b/data/scripts/oaks_lab.lua @@ -288,6 +288,7 @@ return { -- fanfare for the taunt/challenge exchange, same as the Yellow port -- (oaks_lab_yellow.lua); it was silently dropped here (#596). local rows = { + { "face_object", 1, "down" }, -- scripts/OaksLab.asm:347-351 { "face_player_dir", "up" }, { "stop_music" }, { "play_music", "Music_MeetRival" }, diff --git a/data/scripts/oaks_lab_yellow.lua b/data/scripts/oaks_lab_yellow.lua index 6c67a80f..729923d1 100644 --- a/data/scripts/oaks_lab_yellow.lua +++ b/data/scripts/oaks_lab_yellow.lua @@ -263,6 +263,7 @@ return { local rival = ow:npcByIndex(RIVAL) if not rival then return false end local rows = { + { "face_object", RIVAL, "down" }, -- pokeyellow scripts/OaksLab.asm:311-315 { "face_player_dir", "up" }, { "stop_music" }, { "play_music", "Music_MeetRival" }, diff --git a/data/scripts/story.lua b/data/scripts/story.lua index 2941ac58..a4ad40ec 100644 --- a/data/scripts/story.lua +++ b/data/scripts/story.lua @@ -140,6 +140,10 @@ M.VIRIDIAN_CITY = { -- Daisy hands over the TOWN MAP once Oak's errand is under way -- (scripts/BluesHouse.asm BluesHouseDaisySittingText) M.BLUES_HOUSE = { + -- scripts/BluesHouse.asm:12-16 + onEnter = function(game, ow) + game.save.flags.EVENT_ENTERED_BLUES_HOUSE = true + end, talk = { TEXT_BLUESHOUSE_DAISY_SITTING = { { "face_player" }, diff --git a/data/scripts/story2.lua b/data/scripts/story2.lua index 08b3dee0..ffbfb585 100644 --- a/data/scripts/story2.lua +++ b/data/scripts/story2.lua @@ -87,6 +87,18 @@ end M.PALLET_TOWN = { talk = require("data.scripts.pallet_town").talk, escort = escort, + -- scripts/PalletTown.asm:133-144 + onEnter = function(game, ow) + local f = game.save.flags + if f.EVENT_GOT_TOWN_MAP and f.EVENT_ENTERED_BLUES_HOUSE + and not f.EVENT_DAISY_WALKING then + f.EVENT_DAISY_WALKING = true + local Commands = require("src.script.Commands") + local ctx = { save = game.save, game = game, overworld = ow } + Commands.hide_object(ctx, "BLUES_HOUSE", "BLUESHOUSE_DAISY1") + Commands.show_object(ctx, "BLUES_HOUSE", "BLUESHOUSE_DAISY2") + end + end, -- Red: stop at y==1 from (8,5). Yellow: stop at y==0 from (10,4), -- then a wild Pikachu battle before the lab escort (pokeyellow -- PalletTownPikachuBattleScript). diff --git a/data/scripts/story3.lua b/data/scripts/story3.lua index 4f0c507e..6e15987c 100644 --- a/data/scripts/story3.lua +++ b/data/scripts/story3.lua @@ -937,15 +937,10 @@ M.VERMILION_DOCK = { ow:startDustAnim(cx, 1, function() puff(n - 1, cx + 2) end) end puff(3, 15) - -- VermilionDock_EraseSSAnne deliberately leaves the blocks under the - -- player alone ("south of the player and won't be redrawn"), so skip - -- his own block: he must not spend the walk-out standing on water - local pbx = math.floor(ow.player.cellX / 2) - local pby = math.floor(ow.player.cellY / 2) + -- scripts/VermilionDock.asm:182-203 local rows = {} local function setBlock(bx, by, block) if bx < 1 or bx > 8 then return end - if bx == pbx and by == pby then return end rows[#rows + 1] = { "replace_block", bx, by, block } end rows[#rows + 1] = { "wait", 120 } diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 8e19da36..77d0e294 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -583,7 +583,8 @@ end local function stampOT(save, mon) save.player.id = save.player.id or math.random(0, 65535) mon.ot = mon.ot or save.player.name - mon.otId = mon.otId or save.player.id + -- engine/battle/experience.asm:69 + if not mon.traded then mon.otId = mon.otId or save.player.id end end BattleState.stampOT = stampOT @@ -5923,10 +5924,16 @@ function BattleState:drawTextArea() Font.drawCode(Font.BORDER.h, 32, 96) Font.drawCode(Font.BORDER.br, 80, 96) love.graphics.setColor(0, 0, 0, 1) - for i, mv in ipairs(self.player.curMoves) do - -- unknown ids (mod-injected moves) print raw instead of crashing - local def = self.data.moves[mv.id] - Font.draw(def and def.name or tostring(mv.id), 48, 96 + i * 8) + -- engine/battle/misc.asm:37 + for i = 1, 4 do + local mv = self.player.curMoves[i] + if mv then + -- unknown ids (mod-injected moves) print raw instead of crashing + local def = self.data.moves[mv.id] + Font.draw(def and def.name or tostring(mv.id), 48, 96 + i * 8) + else + Font.draw("-", 48, 96 + i * 8) + end end -- Swap cursor: SelectMenuItem parks the hollow arrow on the marked row -- (core.asm:2600-2607), then HandleMenuInput's PlaceMenuCursor writes the @@ -5953,13 +5960,16 @@ function BattleState:drawTextArea() end end elseif self.phase == "mimicSelect" then - -- Mimic's copy menu (MoveSelectionMenu .mimicmenu, core.asm: - -- 2506-2517): the enemy's move list in a 16x6 box at (0,7), names - -- single-spaced from (2,8), cursor at column 1 + -- Mimic's copy menu (MoveSelectionMenu .mimicmenu, core.asm:2506-2517): + -- 16x6 box at (0,7), names from (2,8), cursor at column 1 Font.drawBox(0, 7, 16, 6) love.graphics.setColor(0, 0, 0, 1) - for i, m in ipairs(self.mimicMoves) do - Font.draw(self.data.moves[m.id].name, 16, (7 + i) * 8) + -- engine/battle/misc.asm:37 + for i = 1, 4 do + local m = self.mimicMoves[i] + local def = m and self.data.moves[m.id] + Font.draw(m and (def and def.name or tostring(m.id)) or "-", + 16, (7 + i) * 8) end Font.drawCode(0xED, 8, (7 + self.mimicIndex) * 8) Font.draw(Strings("WHICH TECHNIQUE?"), 8, 112) diff --git a/src/battle/EffectRegistry.lua b/src/battle/EffectRegistry.lua index 2f0f5e60..ebe4f2d4 100644 --- a/src/battle/EffectRegistry.lua +++ b/src/battle/EffectRegistry.lua @@ -232,15 +232,15 @@ function EffectRegistry.runDamaging(battle, ctx, record) hitSfx = { sound = "Damage", pitch = 0x20 } end -- GetPlayerAnimationType / GetEnemyAnimationType (engine/battle/core.asm - -- :3159 / :5555): wAnimationType is 4 (blink the enemy pic) or 1 (shake - -- the screen vertically) for a damaging move with no added effect, and - -- 5 / 2 (a horizontal shake) as soon as the move HAS one -- which is why - -- Bubblebeam and Confusion shake instead of blinking (#354) + -- :3159 / :5555): 4 blinks the enemy pic, 1 shakes vertically, 5 / 2 once + -- the move has an added effect (#354) local added = move.effect ~= nil and move.effect ~= "NO_ADDITIONAL_EFFECT" + -- PlayApplyingAttackAnimation runs on both arms of the wOptions check + -- (engine/battle/animations.asm:424-437), so the blink is not gated (#1384) local hitFx = { sfx = hitSfx, animType = user.isPlayer and (added and 5 or 4) or (added and 2 or 1), - blink = battle:animationsOn() and target or nil } + blink = target } local totalDealt = 0 local landed, brokeSub = 0, false diff --git a/src/battle/gen2/AnimRunner.lua b/src/battle/gen2/AnimRunner.lua index 87bed2fc..302b8f5c 100644 --- a/src/battle/gen2/AnimRunner.lua +++ b/src/battle/gen2/AnimRunner.lua @@ -192,14 +192,8 @@ function Runner:loadGfx(names) end end --- BattleAnimCmd_BattlerGFX_1Row / _2Row. The battlers' pic tiles are --- APPENDED after whatever the script already loaded rather than replacing it, --- and they always land on the same two fixed tile ids. --- --- (pokegold's jumptable has these two labels the other way round from the --- macro names -- $d9 dispatches to BattleAnimCmd_BattlerGFX_1Row while --- anim_battlergfx_2row is $d9 -- so the names below follow the MACRO, which --- is what a script actually writes.) +-- engine/battle_anims/anim_commands.asm:755. The jumptable crosses the macro +-- names: $d9 (anim_battlergfx_2row) dispatches to _1Row (#1401) function Runner:loadBattlerGfx(rows) local tiles = rows == 2 and BATTLER_TILES.twoRow or BATTLER_TILES.oneRow local slot = 1 @@ -210,11 +204,11 @@ function Runner:loadBattlerGfx(rows) self.tileDict[slot] = { gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player } self.tileDict[slot + 1] = { gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy } self.loaded[#self.loaded + 1] = - { gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player, tiles = rows * 6, - battler = "player", rows = rows } - self.loaded[#self.loaded + 1] = - { gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy, tiles = rows * 7, + { gfx = "BATTLE_ANIM_GFX_PLAYERHEAD", tile = tiles.player, tiles = rows * 7, battler = "enemy", rows = rows } + self.loaded[#self.loaded + 1] = + { gfx = "BATTLE_ANIM_GFX_ENEMYFEET", tile = tiles.enemy, tiles = rows * 6, + battler = "player", rows = rows } end -------------------------------------------------------------------------- diff --git a/src/battle/gen2/Battle.lua b/src/battle/gen2/Battle.lua index 4f5014ad..05ea1c2e 100644 --- a/src/battle/gen2/Battle.lua +++ b/src/battle/gen2/Battle.lua @@ -80,6 +80,7 @@ Battle.SECONDARY_EFFECTS = { EFFECT_PARALYZE_HIT = "paralyze", EFFECT_SLEEP_HIT = "sleep", EFFECT_CONFUSE_HIT = "confuse", + EFFECT_SACRED_FIRE = "burn", -- data/moves/effects.asm:1696 } local function rand(random, n) @@ -1320,6 +1321,18 @@ function Battle:markMissed() if self.moveEvent then self.moveEvent.missed = true end end +-- engine/battle/effect_commands.asm:3615 +Battle.AI_FAIL_STATUSES = { + sleep = true, poison = true, toxic = true, paralyze = true, +} + +-- engine/battle/effect_commands.asm:3615 +function Battle:aiRandomFail(attacker, defender) + if self:sideOf(attacker) ~= "enemy" then return false end + if self:volatile(defender).lockOn then return false end + return rand(self.random, 256) < 64 +end + -- One attack, start to finish. function Battle:useMove(attacker, defender, moveId) local move = self:findMove(attacker, moveId) @@ -1478,10 +1491,10 @@ function Battle:useMove(attacker, defender, moveId) if charge and not charging then state.chargeMove = moveId state.vanished = charge.vanish or nil - -- DIG and FLY are the same effect in Gen 2 (both EFFECT_FLY), so the table - -- keyed by effect cannot tell them apart and DIG announced itself with - -- "flew up high!". BattleCommand_Fly picks the line off the MOVE, not the - -- effect: `cp DIG` and then the burrow text. + -- engine/battle/effect_commands.asm:5458 + if self.moveEvent then self.moveEvent.animParam = 1 end + -- BattleCommand_Charge picks the line off the MOVE, not the shared + -- EFFECT_FLY (`cp DIG`, effect_commands.asm:5464). local text = charge.text if moveId == "DIG" then text = "%s dug a hole!" end self:emit({ kind = "message", text = text:format(name) }) @@ -1811,20 +1824,20 @@ function Battle:useMove(attacker, defender, moveId) -- Defense Curl arms Rollout as well as raising Defense. if def.effect == "EFFECT_DEFENSE_CURL" then state.curled = true end - -- Stat changes: the primary ones always land, the *_HIT ones roll the - -- move's effect chance after a hit that connected. - -- - -- A refused primary change is a failure the cart detects BEFORE its anim - -- command: RaiseStat's `.cant_raise_stat` and StatDown's `.CantLower` / - -- `.Mist` all write wAttackMissed (effect_commands.asm:4191, :4380-4400), - -- and `statupanim` / `statdownanim` read it (:2022) from a slot AFTER - -- `attackup` / `attackdown` in the effect list (data/moves/effects.asm, - -- AttackUp). The *_HIT twins must NOT be marked: their `attackdown` runs - -- after `moveanim` (AttackDownHit), so the animation has already played. + -- A refused primary change writes wAttackMissed (effect_commands.asm:4191, + -- :4380-4400); the *_HIT twins animate first and must stay unmarked. local change = Effects.STAT_CHANGES[def.effect] if change then local target = change[3] == "self" and attacker or defender - if not self:changeStageAgainstMist(attacker, target, change[1], change[2]) + -- CheckMist first (effect_commands.asm:4290), then .ComputerMiss (:4318) + local misted = target ~= attacker and (change[2] or 0) < 0 + and self:volatile(target).mist + if not misted and change[3] == "foe" + and def.effect ~= "EFFECT_ACCURACY_DOWN_HIT" + and self:aiRandomFail(attacker, target) then + self:markMissed() + self:emit({ kind = "message", text = "But it failed!" }) + elseif not self:changeStageAgainstMist(attacker, target, change[1], change[2]) then self:markMissed() end @@ -1853,19 +1866,21 @@ function Battle:useMove(attacker, defender, moveId) local record = Battle.moveEffectRecordFor(self.data, def.effect) local status = record and record.kind == "primary" and record.status or nil if status and (def.power or 0) == 0 then - -- Every status command's already-statused / immune arm ends on - -- AnimateFailedMove (BattleCommand_Poison's `.failed`, - -- effect_commands.asm:3748-3750, and :6656-6659): LowerSub, MoveDelay, - -- RaiseSub and no LoadMoveAnim. AnimateCurrentMove only runs on the - -- success path (:3752), and the effect scripts carry no moveanim of their - -- own (data/moves/effects.asm, Toxic / DoPoison). The SECONDARY_EFFECTS - -- branch below is the opposite case: that move already hit and already - -- animated, so a refused secondary must leave the event unmarked. - if not self:applyStatus(defender, status, attacker) then self:markMissed() end + -- A refused primary status is a failed move (effect_commands.asm:3748, + -- :6656); a refused secondary already animated and stays unmarked (:3752). + if Battle.AI_FAIL_STATUSES[status] + and self:aiRandomFail(attacker, defender) then + self:markMissed() + self:emit({ kind = "message", text = "But it failed!" }) + elseif not self:applyStatus(defender, status, attacker) then + self:markMissed() + end else local secondary = record and record.kind == "secondary" and record.status or nil - if secondary and (defender.hp or 0) > 0 then + -- engine/battle/effect_commands.asm:6325 + if secondary and (defender.hp or 0) > 0 + and not self:safeguarded(defender) then local chance = def.effectChance or 0 if chance > 0 and rand(self.random, 100) < chance then self:applyStatus(defender, secondary, attacker) @@ -2425,6 +2440,15 @@ Battle.MOVE_EFFECTS.EFFECT_REFLECT = function(self, attacker) text = self:monName(attacker) .. "'s DEFENSE rose!" }) end +-- engine/battle/move_effects/safeguard.asm:1 +Battle.MOVE_EFFECTS.EFFECT_SAFEGUARD = function(self, attacker) + local side = self.screens[self:sideOf(attacker)] + if (side.safeguard or 0) > 0 then return fail(self) end + side.safeguard = Battle.SCREEN_TURNS + self:emit({ kind = "message", + text = self:monName(attacker) .. "'s covered by a veil!" }) +end + -- BattleCommand_Curse (engine/battle/move_effects/curse.asm): two moves in -- one body. A non-Ghost user trades a stage of Speed for one each of Attack -- and Defense, refused only when BOTH raises are already capped; a Ghost @@ -2910,6 +2934,11 @@ function Battle.statusPenaltyFor(data, mon, stat, value) return math.max(1, math.floor(value / math.max(1, penalty.div or 1))) end +-- engine/battle/effect_commands.asm:6325 +function Battle:safeguarded(mon) + return (self.screens[self:sideOf(mon)].safeguard or 0) > 0 +end + -- `source` is the battler that inflicted it, carried only so -- battle.status_inflicted can name it the way Gen 1's does. function Battle:applyStatus(mon, status, source) @@ -2917,7 +2946,14 @@ function Battle:applyStatus(mon, status, source) -- Confusion is SUBSTATUS_CONFUSED on the cart, not a status byte: it lives -- in the volatile beside the major status, so a confused mon can still be -- burned and a switch shakes the confusion off. - if status == "confuse" then return self:applyConfusion(mon) end + if status == "confuse" then return self:applyConfusion(mon, nil, source) end + -- engine/battle/effect_commands.asm:6338 + if source and self:sideOf(source) ~= self:sideOf(mon) + and self:safeguarded(mon) then + self:emit({ kind = "message", + text = self:monName(mon) .. " is protected by SAFEGUARD!" }) + return false + end -- One major status at a time. if mon.status then self:emit({ kind = "message", @@ -2953,8 +2989,15 @@ end -- as 256 turns. HELD_PREVENT_CONFUSE on the target blocks it outright. Battle.BERSERK_GENE_CONFUSE_TURNS = 256 -function Battle:applyConfusion(mon, turns) +function Battle:applyConfusion(mon, turns, source) if (mon.hp or 0) <= 0 then return false end + -- engine/battle/effect_commands.asm:6338 + if source and self:sideOf(source) ~= self:sideOf(mon) + and self:safeguarded(mon) then + self:emit({ kind = "message", + text = self:monName(mon) .. " is protected by SAFEGUARD!" }) + return false + end local state = self:volatile(mon) if (state.substitute or 0) > 0 then return false end local held = self:heldEffect(mon, "confuse") @@ -4434,14 +4477,20 @@ Battle.SCREEN_FALL_TEXT = { function Battle:tickScreens() for _, side in ipairs({ "player", "enemy" }) do local screens = self.screens[side] - for _, field in ipairs({ "lightScreen", "reflect" }) do + for _, field in ipairs({ "lightScreen", "reflect", "safeguard" }) do if (screens[field] or 0) > 0 then screens[field] = screens[field] - 1 if screens[field] <= 0 then screens[field] = nil - self:emit({ kind = "message", - text = Battle.SCREEN_SIDE_LABEL[side] - .. Battle.SCREEN_FALL_TEXT[field] }) + if field == "safeguard" then + -- engine/battle/core.asm:1527 + self:emit({ kind = "message", + text = self:monName(self[side]) .. "'s SAFEGUARD faded!" }) + else + self:emit({ kind = "message", + text = Battle.SCREEN_SIDE_LABEL[side] + .. Battle.SCREEN_FALL_TEXT[field] }) + end end end end diff --git a/src/core/Music.lua b/src/core/Music.lua index 01ffc756..142bf78f 100644 --- a/src/core/Music.lua +++ b/src/core/Music.lua @@ -387,9 +387,9 @@ end -- overworld map theme; onBike/surfing override outdoor themes with the -- bike/surf songs and restore the map theme when they end -function Music.playMap(data, mapId, onBike, surfing, fade) - local song = data and data.audio and data.audio.mapSongs - and mapId and data.audio.mapSongs[mapId] or nil +function Music.playMap(data, mapId, onBike, surfing, fade, song) + song = song or (data and data.audio and data.audio.mapSongs + and mapId and data.audio.mapSongs[mapId]) or nil state.mapSong = song state.onBike = not not onBike state.surfing = not not surfing diff --git a/src/script/gen2/Specials.lua b/src/script/gen2/Specials.lua index 5b6c6451..3fc9b7dd 100644 --- a/src/script/gen2/Specials.lua +++ b/src/script/gen2/Specials.lua @@ -358,42 +358,33 @@ end -- is the three-way answer BugContestResults_DidNotLeaveMons branches on: -- BUGCONTEST_CAUGHT_MON 0, BUGCONTEST_BOXED_MON 1, BUGCONTEST_NO_CATCH 2 -- (constants/script_constants.asm). --- _CaughtAskNicknameText (data/text/common_2.asm:717). Not extracted: the --- routine that prints it is engine code and no script bytecode points at the --- string, so the extractor never reaches it. +-- _CaughtAskNicknameText (data/text/common_2.asm:717), engine-printed so +-- the extractor never reaches it. local CONTEST_NICKNAME_PROMPT = Strings.source("Give a nickname to\nthe {STRBUF} you\nreceived?") +-- GiveANickname_YesNo (engine/pokemon/caught_nickname.asm:123) +function Specials.askNickname(vm, mon) + nameMon(vm, mon.species) + showRawHeld(vm, Strings(CONTEST_NICKNAME_PROMPT)) + if coroutine.yield({ kind = "yesorno" }) then + -- InitNickname (engine/pokemon/move_mon.asm:1787) + local h = hooks(vm) + local name = h.renameMon and Specials.block(vm, function(done) + h.renameMon(mon, done, { blank = true }) + end) + -- _InitString's blank test (home/string.asm:6-30) + if name and name:gsub(" ", "") ~= "" then mon.nickname = name end + end +end + H.CheckPartyFullAfterContest = function(vm) local Breeding = require("src.core.gen2.Breeding") local result, mon = BugContest.collectCaughtMon(contestSave(vm), Breeding.PARTY_SIZE) - -- GiveANickname_YesNo sits on BOTH arms of CheckPartyFullAfterContest -- the - -- mon that joined the party and the one that went to the box -- and nowhere - -- else in the contest: BugContest_SetCaughtContestMon merely holds the catch - -- in wContestMon, so this is the only place the player is ever asked. - -- GetPokemonName runs first, which is what {STRBUF} reads. + -- GiveANickname_YesNo runs on both contest arms, party and box if mon and result ~= BugContest.NO_CATCH then - nameMon(vm, mon.species) - -- GiveANickname_YesNo (engine/pokemon/caught_nickname.asm:123) is - -- `PrintText / jp YesNoBox`, so the prompt goes up over the box this page - -- left standing. - showRawHeld(vm, Strings(CONTEST_NICKNAME_PROMPT)) - if coroutine.yield({ kind = "yesorno" }) then - -- `ld b, NAME_MON / callfar InitNickname`: the keyboard opens EMPTY on a - -- fresh catch (the Name Rater is the one that pre-fills), and InitNickname - -- copies the species name back over an empty entry -- so a cancelled - -- keyboard is the same as answering NO. - local h = hooks(vm) - local name = h.renameMon and Specials.block(vm, function(done) - h.renameMon(mon, done, { blank = true }) - end) - -- _InitString's own blank test (home/string.asm:6-30): "zero or more - -- spaces followed by a null". The keyboard's blank cells are real - -- typeable characters, so an all-space entry has to be discarded the - -- same way an empty one is, not stored as a name of spaces. - if name and name:gsub(" ", "") ~= "" then mon.nickname = name end - end + Specials.askNickname(vm, mon) end answer(vm, result) end @@ -1005,7 +996,7 @@ end -- check is transcribed here rather than left to the screen, because its two -- refusals are TEXT and the script has to see them before the machine opens: -- no coins at all, or no COIN_CASE to hold them. -local COIN_CASE = 0x47 -- constants/item_constants.asm +local COIN_CASE = 0x36 -- constants/item_constants.asm:62 -- _NoCoinsText / _NoCoinCaseText, data/text/common_1.asm. local NO_COINS_TEXT = "You have no coins." @@ -2409,7 +2400,9 @@ local STUB_ROWS = { { "WaitForOtherPlayerToExit", nil, "link cable: nobody to wait for" }, { "SetBitsForBattleRequest", nil, "link cable: no Gen 2 cable club" }, { "SetBitsForTimeCapsuleRequest", nil, "link cable: no Time Capsule" }, - { "CheckTimeCapsuleCompatibility", 2, "link cable: no Gen 1 partner" }, + -- maps/PokeCenter2F.asm:200-203: 2 is .MonMoveTooNew; 0 falls through to + -- WaitForLinkedFriend and lands on .FriendNotReady + { "CheckTimeCapsuleCompatibility", 0, "link cable: no Gen 1 partner" }, { "EnterTimeCapsule", nil, "link cable: no Time Capsule" }, { "TradeCenter", nil, "link cable: no trade room" }, { "Colosseum", nil, "link cable: no battle room" }, diff --git a/src/script/gen2/Vm.lua b/src/script/gen2/Vm.lua index b1138802..8b60c13b 100644 --- a/src/script/gen2/Vm.lua +++ b/src/script/gen2/Vm.lua @@ -544,7 +544,12 @@ local function runCmd(self, cmd, op) local level = cmd.level or (cmd.args and cmd.args[2]) or 5 local item = cmd.item or (cmd.args and cmd.args[3]) or 0 if self.givePokeFn then - self.givePokeFn(species, level, item) + local mon = self.givePokeFn(species, level, item) + -- engine/pokemon/move_mon.asm:1753-1757 + local trainer = cmd.trainer or (cmd.args and cmd.args[4]) or 0 + if mon and trainer == 0 then + Specials.askNickname(self, mon) + end end elseif op == "checkpoke" then -- Script_checkpoke: IsInArray over wPartySpecies. Party only, so a boxed diff --git a/src/ui/DexEntryMenu.lua b/src/ui/DexEntryMenu.lua index f5f1a09d..8114203c 100644 --- a/src/ui/DexEntryMenu.lua +++ b/src/ui/DexEntryMenu.lua @@ -14,6 +14,7 @@ local Font = require("src.render.Font") local Strings = require("src.core.Strings") +local Theme = require("src.ui.Theme") local DexEntryMenu = {} DexEntryMenu.__index = DexEntryMenu @@ -36,6 +37,62 @@ local function resolveArgs(speciesOrOpts) return speciesOrOpts, false end +local function ownedFor(game, def, forceOwned) + return forceOwned + or (game.save.pokedex and game.save.pokedex.owned[def.id]) or false +end + +-- home/text.asm:245 (<PAGE>), home/text.asm:204 (<DEXEND>) +local function descPages(game, def, forceOwned) + local e = def.dexEntry or {} + local owned = ownedFor(game, def, forceOwned) + local text = owned and e.text and game.data.text[e.text] or nil + if not text then return nil end + local pages = {} + for chunk in (text .. "\f"):gmatch("(.-)\f") do + local lines = {} + for line in (chunk:gsub("\v", "\n") .. "\n"):gmatch("(.-)\n") do + lines[#lines + 1] = line + end + while #lines > 0 and lines[#lines] == "" do table.remove(lines) end + if #lines > 0 then pages[#pages + 1] = lines end + end + if #pages == 0 then return nil end + local last = pages[#pages] + last[#last] = last[#last] .. "." + return pages +end + +-- engine/gfx/load_pokedex_tiles.asm: gfx/pokedex/pokedex.png, codes $60..$71 +local frameCache = {} +local function frameSheet(game) + local fx = game.data.field and game.data.field.overworldFx + local def = fx and fx.pokedexFrame + local path = def and def.path + if not path then return nil end + local hit = frameCache[path] + if hit ~= nil then return hit or nil end + local ok, img = pcall(love.graphics.newImage, path) + if not ok or not img then + frameCache[path] = false + return nil + end + local iw, ih = img:getDimensions() + local quads = {} + for i = 0, 17 do + quads[i] = love.graphics.newQuad((i % 3) * 8, + math.floor(i / 3) * 8, 8, 8, iw, ih) + end + frameCache[path] = { img = img, quads = quads } + return frameCache[path] +end + +-- engine/menus/pokedex.asm:601 +local DIVIDER = { + 0x68, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x6B, + 0x6B, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6A, +} + function DexEntryMenu.new(game, speciesOrOpts, onDone) local species, forceOwned = resolveArgs(speciesOrOpts) local self = setmetatable({ game = game, forceOwned = forceOwned, @@ -43,13 +100,14 @@ function DexEntryMenu.new(game, speciesOrOpts, onDone) self.def = game.data.pokemon[species] local path, trueColor = require("src.pokemon.Sprites").path( game.data, species, "front", { kind = "dex" }) - -- `path and pcall(...)` truncates to one value, so img was always nil and - -- every dex page drew without its pic (#307); the guard has to be a - -- statement for pcall's second return to survive. + -- pcall's second return has to survive the guard (#307) local ok, img = false, nil if path then ok, img = pcall(love.graphics.newImage, path) end self.sprite = ok and img or nil self.spriteTrueColor = self.sprite and trueColor or false + self.page = 1 + local pages = descPages(game, self.def, forceOwned) + self.pageCount = pages and #pages or 1 require("src.core.Sound").playCry(game.data, species) return self end @@ -57,6 +115,11 @@ end function DexEntryMenu:update(dt) local input = self.game.input if input:wasPressed("a") or input:wasPressed("b") then + -- home/text.asm:245 + if self.page < (self.pageCount or 1) then + self.page = self.page + 1 + return + end self.game.stack:pop() if self.onDone then self.onDone() end end @@ -64,64 +127,87 @@ end function DexEntryMenu:draw() DexEntryMenu.render(self.game, self.def, self.sprite, self.forceOwned, - self.spriteTrueColor) + self.spriteTrueColor, self.page) end -- Static entry-page renderer, shared with the printer stand-in -- (src/core/Printer.lua renders the same page into a PNG the way -- PrintPokedexEntry rendered it to the Game Boy Printer). -function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor) +-- engine/menus/pokedex.asm:399 +function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor, page) + page = page or 1 love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) + local frame = frameSheet(game) + if frame then + local function tile(code, tx, ty) + love.graphics.draw(frame.img, frame.quads[code - 0x60], tx * 8, ty * 8) + end + -- engine/menus/pokedex.asm:418 + for tx = 1, 18 do + tile(0x64, tx, 0) + tile(0x6f, tx, 17) + end + for ty = 1, 16 do + tile(0x66, 0, ty) + tile(0x67, 19, ty) + end + tile(0x63, 0, 0) + tile(0x65, 19, 0) + tile(0x6c, 0, 17) + tile(0x6e, 19, 17) + -- engine/menus/pokedex.asm:445 + for tx = 0, 19 do + tile(DIVIDER[tx + 1], tx, 9) + end + end if sprite then - local y = math.max(0, 60 - sprite:getHeight()) - love.graphics.draw(sprite, 8, y) - -- a full-color pic has to sit out the SGB recolor, so mark its bounds - -- for the unshaded pass (#350). The printer path leaves trueColor nil: - -- it renders to its own PNG canvas, and a mark left behind there would - -- bleed into the next real frame. + -- engine/menus/pokedex.asm:503, home/pokemon.asm:96 (flipped) + local w, h = sprite:getDimensions() + local x = 8 + math.floor((8 - w / 8) / 2) * 8 + local y = 8 + (7 - h / 8) * 8 + love.graphics.draw(sprite, x + w, y, 0, -1, 1) + -- the unshaded pass needs the pic bounds (#350) if trueColor then - require("src.render.PaletteFX").markTrueColor(8, y, sprite:getDimensions()) + require("src.render.PaletteFX").markTrueColor(x, y, w, h) end end love.graphics.setColor(0, 0, 0, 1) - Font.draw(def.name, 72, 8) + -- engine/menus/pokedex.asm:454 + Font.draw(def.name, 72, 16) local e = def.dexEntry or {} - -- English R/B prints only the kind string (hlcoord 9,4 PlaceString). - -- PokeText ("#"/POKéMON) is an unreferenced JPN leftover in pokedex.asm; - -- appending " POKéMON" here clipped longer kinds ("LIZARD POKé"). - Font.draw(e.kind or "?", 72, 20) + -- engine/menus/pokedex.asm:468, kind string only (PokeText is unreferenced) + Font.draw(e.kind or "?", 72, 32) -- same number width as the list (constants.dexDigits), so a dex past 999 -- prints the extra digit everywhere at once local digits = (game.data.constants or {}).dexDigits or 3 - Font.draw(Strings("No.") .. ("%0" .. digits .. "d"):format(def.dex or 0), 72, 32) - local owned = forceOwned - or (game.save.pokedex and game.save.pokedex.owned[def.id]) - -- height/weight print only once owned, like the description - -- (pokedex.asm: "if the pokemon has not been owned, don't print the - -- height, weight, or description") + -- engine/menus/pokedex.asm:478 + Font.draw(Strings("No.") .. ("%0" .. digits .. "d"):format(def.dex or 0), + 16, 64) + local owned = ownedFor(game, def, forceOwned) + -- engine/menus/pokedex.asm:449, numbers only once owned if owned and e.heightFt then - -- feet/inches use the dex screen's ′/″ glyphs ("HT ?′??″" in - -- pokedex.asm; the tiles come from gfx/pokedex/pokedex.png via - -- engine/gfx/load_pokedex_tiles.asm) if e.heightM then - Font.draw((Strings("GR. %.1fm", e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 72, 44) - Font.draw((Strings("GEW. %.1fkg", e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 72, 54) + Font.draw((Strings("GR. %.1fm", e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 72, 48) + Font.draw((Strings("GEW. %.1fkg", e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 72, 64) else - Font.draw(Strings("HT %d′%02d″", e.heightFt, e.heightIn or 0), 72, 44) - Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 54) + Font.draw(Strings("HT %d′%02d″", e.heightFt, e.heightIn or 0), 72, 48) + Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 64) end end - local text = owned and e.text and game.data.text[e.text] or nil - local y = 72 - if text then - for line in (text:gsub("\v", "\n"):gsub("\f", "\n") .. "\n"):gmatch("(.-)\n") do - if y > 132 then break end - Font.draw(line, 8, y) - y = y + 10 + local pages = descPages(game, def, forceOwned) + if pages then + -- engine/menus/pokedex.asm:568 + local lines = pages[page] or pages[#pages] + for i, line in ipairs(lines) do + Font.draw(line, 8, 72 + i * 16) + end + -- home/text.asm:245 + if page < #pages then + Font.drawCode(Theme.moreArrow, 144, 128) end else - Font.draw(Strings("Data unknown."), 8, y) + Font.draw(Strings("Data unknown."), 8, 88) end love.graphics.setColor(1, 1, 1, 1) end diff --git a/src/ui/LeaguePC.lua b/src/ui/LeaguePC.lua new file mode 100644 index 00000000..250cce8b --- /dev/null +++ b/src/ui/LeaguePC.lua @@ -0,0 +1,112 @@ +-- PKMN LEAGUE hall-of-fame viewer (engine/menus/league_pc.asm:1) + +local Font = require("src.render.Font") +local Strings = require("src.core.Strings") +local HallOfFame = require("src.ui.HallOfFame") + +local LeaguePC = {} +LeaguePC.__index = LeaguePC +LeaguePC.isOpaque = true + +-- constants/pokemon_data_constants.asm:65 +local CAPACITY = 50 + +-- SGB: SET_PAL_POKEMON_WHOLE_SCREEN per mon (engine/menus/league_pc.asm:95) +function LeaguePC:sgbPalettes(game) + local P = require("src.render.PaletteFX") + local mon = self:currentMon() + local c = mon and P.monPal(game.data, mon.species) + if c then return { P.whole(c) } end + return P.wholeNamed(game.data, "MEWMON") +end + +function LeaguePC.new(game, onDone) + local self = setmetatable({}, LeaguePC) + self.game = game + self.onDone = onDone + self.teams = (game.save and game.save.hallOfFame) or {} + self.teamIndex = math.max(1, #self.teams - CAPACITY + 1) + self.monIndex = 1 + self.sprites = {} + self.spriteTrueColor = {} + self:loadMon() + return self +end + +function LeaguePC:currentMon() + local team = self.teams[self.teamIndex] + return team and team[self.monIndex] or nil +end + +function LeaguePC:loadMon() + local mon = self:currentMon() + if not mon then return end + local species = mon.species + if self.sprites[species] == nil then + local path, trueColor = require("src.pokemon.Sprites").path( + self.game.data, species, "front", { kind = "hof" }) + local ok, img = false, nil + if path then ok, img = pcall(love.graphics.newImage, path) end + self.sprites[species] = ok and img or false + self.spriteTrueColor[species] = (ok and img and trueColor) or false + end + require("src.core.Sound").playCry(self.game.data, species) +end + +function LeaguePC:close() + self.game.stack:pop() + if self.onDone then self.onDone() end +end + +function LeaguePC:update(dt) + local input = self.game.input + if input:wasPressed("b") then + self:close() + return + end + if input:wasPressed("a") then + if not self:currentMon() then + self:close() + return + end + local team = self.teams[self.teamIndex] + if self.monIndex < #team then + self.monIndex = self.monIndex + 1 + elseif self.teamIndex < #self.teams then + self.teamIndex = self.teamIndex + 1 + self.monIndex = 1 + else + self:close() + return + end + self:loadMon() + end +end + +function LeaguePC:draw() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.rectangle("fill", 0, 0, 160, 144) + local mon = self:currentMon() + if not mon then return end + local img = self.sprites[mon.species] + if img then + -- engine/menus/league_pc.asm:98 (hlcoord 12, 5) + local w, h = img:getDimensions() + local x = 96 + math.floor((8 - w / 8) / 2) * 8 + local y = 40 + (7 - h / 8) * 8 + love.graphics.draw(img, x, y) + if self.spriteTrueColor[mon.species] then + require("src.render.PaletteFX").markTrueColor(x, y, w, h) + end + end + -- engine/movie/hall_of_fame.asm:159 + HallOfFame.drawMonInfo(self, mon) + -- engine/menus/league_pc.asm:102 + Font.drawBox(0, 13, 20, 4) + love.graphics.setColor(0, 0, 0, 1) + Font.draw(Strings("HALL OF FAME No"), 1 * 8, 15 * 8) + Font.draw(("%3d"):format(self.teamIndex), 16 * 8, 15 * 8) + love.graphics.setColor(1, 1, 1, 1) +end + +return LeaguePC diff --git a/src/ui/NamingScreen.lua b/src/ui/NamingScreen.lua index c35f4b9d..9c82cfb7 100644 --- a/src/ui/NamingScreen.lua +++ b/src/ui/NamingScreen.lua @@ -79,7 +79,16 @@ end function NamingScreen:enter() if self.presets and #self.presets > 0 then local Menu = require("src.ui.Menu") - local items = { { label = Strings("NEW NAME") } } + -- engine/movie/oak_speech/oak_speech2.asm:1 + self.choosing = true + self.isOpaque = false + local items = { { + label = Strings("NEW NAME"), + onSelect = function() + self.choosing = nil + self.isOpaque = nil + end, + } } for _, preset in ipairs(self.presets) do table.insert(items, { label = preset, @@ -193,6 +202,7 @@ function NamingScreen:update(dt) end function NamingScreen:draw() + if self.choosing then return end love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) love.graphics.setColor(0, 0, 0, 1) diff --git a/src/ui/TownMap.lua b/src/ui/TownMap.lua index ec6f305c..95780fed 100644 --- a/src/ui/TownMap.lua +++ b/src/ui/TownMap.lua @@ -220,6 +220,20 @@ function TownMap.new(game, opts) -- the player's current location (guard: overworld may not be running) local mapId = game.overworld and game.overworld.map and game.overworld.map.id self.playerLoc = mapId and self.byMap[mapId] or nil + -- engine/items/town_map.asm:347 + do + local playerSprites = (game.data.field and game.data.field.playerSprites) + or {} + local sprites = game.data.sprites or {} + local red = sprites[playerSprites.walk or "SPRITE_RED"] + or sprites.SPRITE_RED + local ok, img = pcall(love.graphics.newImage, red and red.image) + if ok and img then + self.playerSheet = img + self.playerQuad = love.graphics.newQuad(0, 0, 16, 16, + img:getDimensions()) + end + end self.sel = 1 -- LoadTownMap_Fly always opens with hl on wFlyLocationsList[0], the FIRST -- fly destination (PALLET_TOWN), never the player's current town (#795). @@ -345,18 +359,16 @@ function TownMap:draw() love.graphics.setColor(1, 1, 1, 1) return end - -- the player's current location blinks (slow phase). Paint it with a - -- palette-safe DARK shade (red 0), not red: this screen composites through - -- the TOWNMAP SGB shade-remap shader (PaletteFX.shader), which keys ONLY on - -- the red channel, and a red-0.75 dot lands in the c1 bucket = TOWNMAP - -- {165,214,255}, the exact light-blue used for the water and the town-square - -- fill, so the marker was drawn but recolored invisible (#152). Red 0 -> c3 - -- {25,16,16} = a solid dark "you are here" dot, visible on land and water. + -- engine/items/town_map.asm:347; fallback dot stays red 0 for PaletteFX (#152) if self.playerLoc and self.blink < 20 then local x, y = markerXY(self.playerLoc) - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("fill", x + 2, y + 2, 4, 4) - love.graphics.setColor(1, 1, 1, 1) + if self.playerSheet then + love.graphics.draw(self.playerSheet, self.playerQuad, x - 4, y - 3) + else + love.graphics.setColor(0, 0, 0, 1) + love.graphics.rectangle("fill", x + 2, y + 2, 4, 4) + love.graphics.setColor(1, 1, 1, 1) + end end -- blinking cursor on the selected location. markerXY is the 8x8 cell's -- top-left; the cursor asset is a 16x16 hollow frame centered on its own @@ -389,11 +401,16 @@ function TownMap:draw() drawSquare(loc) end if self.playerLoc and self.blink < 20 then - -- palette-safe dark, same red-channel shade-remap reason as the primary - -- grid path above (#152); stale-asset builds hit this fallback square - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("fill", self.playerLoc.x * 8 + 2, - self.playerLoc.y * 8 + 2, 4, 4) + -- engine/items/town_map.asm:347; fallback dot stays red 0 for PaletteFX (#152) + if self.playerSheet then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(self.playerSheet, self.playerQuad, + self.playerLoc.x * 8 - 4, self.playerLoc.y * 8 - 3) + else + love.graphics.setColor(0, 0, 0, 1) + love.graphics.rectangle("fill", self.playerLoc.x * 8 + 2, + self.playerLoc.y * 8 + 2, 4, 4) + end end if selected and self.blink % 16 < 10 then love.graphics.setColor(0, 0, 0, 1) diff --git a/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index 57f055e6..67d534c8 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -49,6 +49,9 @@ BattleState.isOpaque = true -- the victory jingle can keep looping through the post-win prompts. local MESSAGE_FRAMES = 48 +-- engine/battle/effect_commands.asm:6661 +local MOVE_DELAY_FRAMES = 40 + -- home/hm_moves.asm:17-25 IsHMMove's .HMMoves. local HM_MOVES = { CUT = true, FLY = true, SURF = true, STRENGTH = true, FLASH = true, @@ -239,15 +242,8 @@ function BattleState.new(game, opts) self.menuIndex = 1 self.moveIndex = 1 self.picCache = {} - -- Which side's pic box the tilemap has been left EMPTY in. BattleBGEffect_ - -- ReturnMon's last row (what swallows a mon into a thrown ball) and - -- MonFaintedAnimation both clear the box and neither puts anything back: it - -- stays blank until something DRAWS a pic into it, which on the cart is only - -- ever a send-out (ShowSetEnemyMonAndSendOutAnimation / SendOutPlayerMon). - -- Without this latch the pic came back the instant the animation let go of - -- the screen, so a caught mon stood there through "Gotcha!" and a fainted one - -- popped back up for its own faint line. See stepAnim for why it is latched - -- at those two moments rather than off the runner's own last frame. + -- Which side's pic box stays EMPTY until a send-out redraws it: a catch + -- latches from stepAnim (data/moves/animations.asm:379), a faint from the slide. self.picHidden = { player = false, enemy = false } -- engine/battle/sliding_intro.asm: 72 frames of the two halves sliding in -- from opposite sides before the first message. @@ -1050,10 +1046,10 @@ function BattleState:afterAnimFor(side) return "ANIM_PLAYER_DAMAGE" end -function BattleState:animForMove(moveId, side) +function BattleState:animForMove(moveId, side, param) local key = self.anims and self.anims.moves and self.anims.moves[moveId] local started = self:startAnim(key, { - turn = self:turnFor(side), animId = moveId, isMove = true, + turn = self:turnFor(side), animId = moveId, isMove = true, param = param, }) if started then -- BattleAnimRunScript (anim_commands.asm:55-72): after the move script @@ -1091,14 +1087,22 @@ function BattleState:animForId(idName, side, param) }) end +-- data/moves/animations.asm:379 +function BattleState:latchCaughtPic() + local anim = self.anim + if anim and anim.animId == "ANIM_THROW_POKE_BALL" + and self.ballThrow and self.ballThrow.caught then + self.picHidden.enemy = true + end +end + -- One logic frame of a running animation. B cuts it short, the way holding B -- pages a text box. function BattleState:stepAnim(input) if not self.anim then return end if input and (input:wasPressed("b") or input:wasPressed("start")) then - -- Cut short: the BG effects never reached their own last step, so the - -- tilemap is whatever they had got to and nothing is latched -- the - -- explicit latches (a catch) are the only ones that survive a skip. + -- Cut short: only the explicit latches (a caught mon) survive a skip. + self:latchCaughtPic() self.anim = nil -- Cart still reaches the after-anim arm after a move script ends; a skip -- of the move should not drop the hit shake that follows it. @@ -1106,6 +1110,7 @@ function BattleState:stepAnim(input) return self:endSendOutAnim() end if not self.anim:step() then + self:latchCaughtPic() -- pokegold data/moves/animations.asm .Click: anim_keepsprites means -- the OAM outlives the script, so keep the runner for drawing too. if not self.anim.keepSprites then self.anim = nil end @@ -1391,16 +1396,14 @@ function BattleState:advanceQueue() end if event.text then self.message = event.text - -- Lines that must not hold the queue for A/B: - -- move UsedMoveText -> text_end, then moveanim - -- level GrewToLevel is text_end (battle.asm:336-343), then the stats - -- box's WaitPressAorB is the real hold - -- experience keeps the wait: _ExpPointsText ends in `prompt` - -- (common_1.asm:1660-1665). update() runs stepExpAnim before that wait, - -- so the bar crawls under the line and A dismisses it before the battle - -- can end. + -- move/level lines do not hold for A/B (battle.asm:336-343); experience + -- keeps the wait (common_1.asm:1660-1665). if event.kind == "move" or event.kind == "level" then self.messageTimer = 0 + -- engine/battle/effect_commands.asm:1958-1961 + if event.kind == "move" and event.missed then + self.messageDelay = MOVE_DELAY_FRAMES + end else self.messageTimer = MESSAGE_FRAMES end @@ -1422,17 +1425,12 @@ function BattleState:advanceQueue() if event.waitSfx then self.waitSfx = event.sfx end end end - -- The move's own animation plays over its "used X!" line, which is where - -- PlayBattleAnim sits in the effect command list. Its after-anim (the hit - -- shake) is chained by animForMove / stepAnim, matching BattleAnimRunScript. - -- BattleCommand_MoveAnimNoSub (engine/battle/effect_commands.asm:1958) opens - -- with `ld a, [wAttackMissed] / and a / jp nz, BattleCommand_MoveDelay`: a - -- move that missed burns the delay and plays nothing. Battle:markMissed sets - -- event.missed on every wAttackMissed path. + -- engine/battle/effect_commands.asm:1958: a missed move burns the delay + -- and plays nothing; the after-anim chain is animForMove / stepAnim's. if event.kind == "move" and not event.missed then self.afterAnimPlayed = nil self.pendingAfterAnim = nil - if not self:animForMove(event.move, event.side) then + if not self:animForMove(event.move, event.side, event.animParam) then -- BATTLE SCENE off skips the move script but still runs wBattleAfterAnim -- (anim_commands.asm:55-72 .disabled fallthrough). local options = self.game and self.game.options @@ -1854,6 +1852,11 @@ function BattleState:update(_dt) -- (core.asm:6881-6888), with that line still on screen. Run the crawl -- before any PromptButton wait so the bar does not sit frozen until A. if self:stepExpAnim() then return end + -- engine/battle/effect_commands.asm:6661 + if (self.messageDelay or 0) > 0 then + self.messageDelay = self.messageDelay - 1 + return + end if self.messageTimer > 0 then if self.tutorial then -- PromptButton waits for the button; the tutorial cannot press it, so @@ -2463,14 +2466,6 @@ function BattleState:pushCaught(enemy, itemId) local save = self.save self.battle.over = true self.battle.outcome = "caught" - -- The mon is INSIDE the ball from here on. BattleAnim_ThrowPokeBall's caught - -- arm ends on the return-mon BG effect, which leaves the enemy pic box - -- cleared, and PokeBallEffect never draws a frontpic again -- there is no - -- send-out left in a battle that is already over. Latched here as well as - -- from the animation's own last step so that a throw the player skipped with - -- B (BattleAnimRunScript has no such skip; this port does) cannot put the - -- caught mon back on the field for the "Gotcha!" line. - self.picHidden.enemy = true -- PokeBallEffect's FRIEND_BALL arm: the caught mon's happiness is set to -- FRIEND_BALL_HAPPINESS (200) instead of the base 70. That is the ball's -- whole effect; its catch rate is a plain ball's. It applies on the box @@ -2932,6 +2927,7 @@ function BattleState:useItem(itemId) -- wThrownBallWobbleCount 0, then `predef PlayBattleAnim`. Everything -- pushed above is drained only once the ball has finished wobbling. self:startBallAnim(self:ballAnimParam(itemId), itemId) + if caught and not self.anim then self.picHidden.enemy = true end self.message = nil self.messageTimer = 0 self.phase = "resolving" diff --git a/src/ui/gen2/PokedexMenu.lua b/src/ui/gen2/PokedexMenu.lua index 53487347..480bd9ef 100644 --- a/src/ui/gen2/PokedexMenu.lua +++ b/src/ui/gen2/PokedexMenu.lua @@ -144,12 +144,7 @@ function PokedexMenu.new(game, opts) self.game = game self.save = opts.save or (game and game.save) local data = game and game.data or {} - -- Held, not just read into the fields below. CRY resolves its sample - -- through data.audio.cries and AREA resolves nests and landmark names - -- through data.maps / data.landmarks, and every one of those reads - -- `self.data` -- which nothing assigned, so `cries` folded to nil, playCry - -- returned before it reached Sound, and the button did nothing at all. - -- Taken by reference so a mod's merged cry or landmark is the one used. + -- engine/pokedex/pokedex.asm:447 self.data = data self.dex = opts.pokedex or data.gen2Pokedex self.pokemon = opts.pokemon or data.pokemon @@ -866,15 +861,6 @@ function PokedexMenu:drawArea() self:text(region == "kanto" and "KANTO" or "JOHTO", 1, 1) local G = love.graphics - local table_ = self.data and self.data.landmarks - local byIndex = self.landmarkByIndex - if not byIndex then - byIndex = {} - for _, entry in pairs((table_ and table_.landmarks) or {}) do - if entry and entry.index then byIndex[entry.index] = entry end - end - self.landmarkByIndex = byIndex - end if #nests == 0 then -- A species with no grass, water or roamer entry in this region. The cart @@ -883,11 +869,11 @@ function PokedexMenu:drawArea() return end - -- Blinking markers, the way the cart flashes its OBJs. + -- engine/pokegear/pokegear.asm:2427 local on = ((self.areaBlink or 0) % 32) < 20 if cells and on then for _, index in ipairs(nests) do - local mark = byIndex[index] + local mark = Nests.landmark(self.data, index) if mark and mark.x and mark.y then G.setColor(0, 0, 0, 1) G.rectangle("fill", mark.x - 2, mark.y - 2, 5, 5) @@ -900,7 +886,7 @@ function PokedexMenu:drawArea() -- Name the first one in words as well as on the map: the flashing dot is -- unreadable at this size on a modern display, and the landmark name is what -- a player actually wants off this screen. - local first = byIndex[nests[1]] + local first = Nests.landmark(self.data, nests[1]) if first and first.name then local name = tostring(first.name):gsub("\n", " ") self:text(name, 1, 16) diff --git a/src/world/NPC.lua b/src/world/NPC.lua index 14b48378..0c926faf 100644 --- a/src/world/NPC.lua +++ b/src/world/NPC.lua @@ -33,6 +33,7 @@ function NPC.new(data, mapId, objDef) self.facing = FACING_FROM_RANGE[objDef.range] or "down" self.moving = false self.progress = 0 + self.animClock = 0 self.stepFlip = false self.frozen = false -- scripts freeze NPCs while talking self.wanders = objDef.movement == "WALK" @@ -52,31 +53,15 @@ function NPC:facePlayer(player) end function NPC:update(map, entities) - -- An NPC tile is 32 frames, half the player's rate: TryWalking loads - -- WALKANIMATIONCOUNTER with $10 and UpdateSpriteInWalkingAnimation adds - -- the 1px step vector once per call (engine/overworld/movement.asm), but - -- UpdateSprites runs once per OverworldLoop pass and every pass opens - -- with two DelayFrame calls (home/overworld.asm) -- so those 16 ticks - -- cost 32 frames for one 16px cell, against AdvancePlayerSprite's 8 - -- ticks of 2px. That halving is why pokeyellow's NormalPikachuFollow - -- needs TryDoubleAddPikachuStepVectorToScreenPixelCoords to keep up. - -- - -- self.stepFrames overrides the shared walk for an object whose - -- step has to stay in phase with something else: Yellow's follower - -- Pikachu takes the player's own step length, halved while it is more - -- than a cell behind (FastPikachuFollow, engine/pikachu/ - -- pikachu_follow.asm). self.hopStep is the same file's $5-$8 hop - -- command: two cells of travel inside one step's frames - -- (DoubleAddPikachuStepVectorToScreenPixelCoords), which is why the - -- pixel span doubles while the frame count does not. Nothing else sets - -- either field, so every other object keeps the constant (#410, #409). + -- engine/overworld/movement.asm:301, 32 frames per NPC cell; stepFrames is + -- the follower's own step length (#410, #409). local stepLen = self.stepFrames or STEP_FRAMES local span = self.hopStep and 2 or 1 if self.moving then self.progress = self.progress + 1 - -- NPC_CHANGE_FACING: animate the walk cycle in place, no translation - -- (movement.asm ChangeFacingDirection zeroes the delta); px/py stay - -- pinned to the current cell while walkPhase() cycles. + self.animClock = (self.animClock or 0) + 1 + -- NPC_CHANGE_FACING (movement.asm ChangeFacingDirection): walk cycle in + -- place, no translation. if self.marching then if self.progress >= stepLen then self.progress = 0 @@ -122,18 +107,20 @@ end function NPC:walkPhase() if not self.moving then return 0 end - local stepLen = self.stepFrames or STEP_FRAMES - local p = self.progress % stepLen - return (p >= stepLen / 4 and p < stepLen * 3 / 4) and 1 or 0 + -- engine/overworld/movement.asm:301 + local p = (self.animClock or 0) % 16 + return (p >= 4 and p < 12) and 1 or 0 end --- Same contract as Player:pose -- the sheet, position, facing and step --- phase this frame renders to -- so a render pipeline can pose an NPC --- without caring which kind of entity it is. An NPC never hops, so the --- trailing hop flag is always false. +-- Same contract as Player:pose; an NPC never hops, so the trailing hop +-- flag is always false. function NPC:pose() + local flip = self.stepFlip + if self.moving then + flip = math.floor((self.animClock or 0) / 16) % 2 == 1 + end return self.sprite, self.px, self.py, self.facing, - self:walkPhase(), self.stepFlip, false + self:walkPhase(), flip, false end function NPC:draw(camX, camY) diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index be17de27..6037233a 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -3090,9 +3090,12 @@ function OverworldState:finishNurseHeal(bye, onDone, npc) end)) end if not npc then farewell() return end - npc.frameOverride = 3 + -- engine/events/pokecenter.asm:36-39; Yellow's walk-down pose when the + -- sheet has it (pokeyellow engine/events/pokecenter.asm:82-88) + local yellow = GameVersion.isYellow() + npc.frameOverride = (yellow and npc.sprite.frames[3]) and 3 or 1 -- bubble = false is the silent world hold, this port's DelayFrames - self.emote = { npc = npc, frames = 20, bubble = false, onDone = function() + self.emote = { npc = npc, frames = yellow and 40 or 20, bubble = false, onDone = function() npc.frameOverride = nil npc:facePlayer(self.player) farewell() diff --git a/src/world/gen2/World.lua b/src/world/gen2/World.lua index fd1fc787..46aca5d5 100644 --- a/src/world/gen2/World.lua +++ b/src/world/gen2/World.lua @@ -1074,11 +1074,12 @@ function World:load() save.pokedex = save.pokedex or { seen = {}, caught = {} } save.pokedex.seen[mon.species] = true save.pokedex.caught[mon.species] = true - -- AddPartyMon's `.registerunowndex` runs on the same path, so a gifted - -- Unown lands in the form list too (nothing in Gold gives one, but the - -- cart's check is on the species, not on where it came from). + -- AddPartyMon's `.registerunowndex` runs on the same path, so a + -- gifted Unown lands in the form list too (move_mon.asm:347). Unown.registerCatch(save, mon) end + -- engine/pokemon/move_mon.asm:1632-1645 + return mon end, giveItem = function(itemIndex, qty) local data = self.game and self.game.data @@ -2402,12 +2403,43 @@ function World:rollWild() return { species = def.index, level = roll.level } end +-- GetMapMusic (home/map.asm:2550) +function World.mapMusicLabel(audio, musicByte, rocketsMahogany, rocketsRadioTower) + if type(musicByte) ~= "number" then return nil end + local MUSIC_MAHOGANY_MART = 100 -- constants/music_constants.asm:100 + local RADIO_TOWER_MUSIC = 0x80 -- constants/music_constants.asm:109 + local order = audio and audio.musicOrder + local songs = audio and audio.songs + local label + if musicByte == MUSIC_MAHOGANY_MART then + label = rocketsMahogany and "Music_RocketHideout" or "Music_CherrygroveCity" + elseif musicByte >= RADIO_TOWER_MUSIC then + label = rocketsRadioTower and "Music_RocketTheme" + or (order and order[(musicByte - RADIO_TOWER_MUSIC) + 1]) + else + return nil + end + if label and label ~= "Music_Nothing" and songs and songs[label] then + return label + end + return nil +end + +function World:mapMusicSong(mapId) + local audio = self.game and self.game.data and self.game.data.audio + local def = self.maps and self.maps[mapId] + -- ENGINE_ROCKETS_IN_MAHOGANY / _RADIO_TOWER (data/events/engine_flags.asm:40,:36) + return World.mapMusicLabel(audio, def and def.music, + self:engineFlag(22), self:engineFlag(18)) +end + function World:playMapMusic() local data = self.game and self.game.data if data and data.audio and data.audio.runtime and self.map then -- SpecialMapMusic (home/audio.asm:397) Music.playMap(data, self.map.id, nil, - FieldMoves.isSurfing(self.playerState)) + FieldMoves.isSurfing(self.playerState), nil, + self:mapMusicSong(self.map.id)) end end @@ -3255,7 +3287,8 @@ function World:surfStartStep(mon) if audio and audio.runtime and self.map then -- SpecialMapMusic (home/audio.asm:397) Music.playMap(self.game.data, self.map.id, nil, - FieldMoves.isSurfing(self.playerState)) + FieldMoves.isSurfing(self.playerState), nil, + self:mapMusicSong(self.map.id)) end if p.scriptStep then p:scriptStep(p.facing) end self.fieldMove = { phase = "step" } @@ -3565,7 +3598,7 @@ function World:rareWildMon() local entry = self.encounters and self.encounters.grass and map and self.encounters.grass[map.id] if not entry or not entry.slots then return nil end - local key = (self.daytime == "DARK") and "NITE" or (self.daytime or "DAY") + local key = self.tod or "DAY" local slots = entry.slots[key] or entry.slots.DAY if not slots then return nil end local rare = slots[4 + math.random(3)] @@ -3958,7 +3991,7 @@ function World:rollEncounter(kind, terrain, tables, vanilla) -- Same guard World:rockRandom uses: a headless suite has no love global. rng = (love and love.math and love.math.random) or math.random, kind = kind, - daytime = self.daytime, + daytime = self.tod, environment = map and map.def and map.def.environment, tables = tables, data = self.game and self.game.data, @@ -4021,7 +4054,8 @@ function World:tryWildEncounter() if onWater then rate = Encounter.waterRate(tables, map.id) else - rate = Encounter.grassRate(tables, map.id, self.daytime) + -- engine/overworld/wildmons.asm:283 + rate = Encounter.grassRate(tables, map.id, self.tod) end -- ApplyMusicEffectOnEncounterRate runs first (wildmons.asm:213-215). rate = World.musicEncounterRate(rate, Music.mapSong()) @@ -4315,6 +4349,14 @@ function World:rollFishing(rod) -- the flag and nothing about the map changes. Roamers.Swarm.fishing is the -- same store CheckSwarmFlag clears when the swarm expires. local swarm = Roamers.Swarm.fishing(game.save) + -- engine/events/fish.asm:24-30 + local groupRow = self.encounters.fishGroups + and self.encounters.fishGroups[ + Encounter.fishGroupFor(self.encounters, group, swarm)] + if groupRow and groupRow.chance + and not Encounter.triggers(groupRow.chance, nil) then + return "nibble" + end local roll if Runtime.wantsHook("encounter.fishing") then -- Gen 1's three arguments, in Gen 1's order: the rod, the map, and the @@ -5100,7 +5142,7 @@ function World:sweetScentEncounter() local tables = self:wildTables() local onWater = FieldMoves.encounterTable(collision) == "water" local rate = onWater and Encounter.waterRate(tables, map.id) - or Encounter.grassRate(tables, map.id, self.daytime) + or Encounter.grassRate(tables, map.id, self.tod) if not (rate and rate > 0) then return false end -- CheckEncounterRoamMon, the first thing ChooseWildEncounter itself does: -- a beast REPLACES the map's own slot rather than adding to it. @@ -5374,7 +5416,8 @@ function World:runSurf(result) if audio and audio.runtime and self.map then -- SpecialMapMusic (home/audio.asm:397) Music.playMap(self.game.data, self.map.id, nil, - FieldMoves.isSurfing(self.playerState)) + FieldMoves.isSurfing(self.playerState), nil, + self:mapMusicSong(self.map.id)) end if self.player and self.player.scriptStep then self.player:scriptStep(self.player.facing) @@ -5886,6 +5929,8 @@ function World:startBattle(opts, onDone) -- World:startCatchTutorial sets it. tutorial = opts.tutorial, onDone = function(outcome) + -- WildBattleScript's reloadmapafterbattle (engine/overworld/events.asm:1158-1162) + self.wildCooldown = 5 self.battleActive = nil game.stack:pop() -- wBattleResult (constants/battle_constants.asm): WIN 0, LOSE 1, DRAW 2. @@ -8540,7 +8585,8 @@ function World:setMap(mapId, cx, cy, facing, opts) if audio and audio.runtime then if not (FieldMoves.isBiking(self.playerState) and self:playBikeMusic()) then Music.playMap(self.game.data, mapId, nil, - FieldMoves.isSurfing(self.playerState)) + FieldMoves.isSurfing(self.playerState), nil, + self:mapMusicSong(mapId)) end end -- Fires with the map fully built and BEFORE the map's own scene script, so a @@ -8928,7 +8974,8 @@ function World:movePlayer(dir) local audio = self.game and self.game.data and self.game.data.audio if audio and audio.runtime then Music.playMap(self.game.data, map.id, nil, - FieldMoves.isSurfing(self.playerState)) + FieldMoves.isSurfing(self.playerState), nil, + self:mapMusicSong(map.id)) end end end diff --git a/tests/engine/battle_move_slot_dashes_bug1343.lua b/tests/engine/battle_move_slot_dashes_bug1343.lua new file mode 100644 index 00000000..9c292062 --- /dev/null +++ b/tests/engine/battle_move_slot_dashes_bug1343.lua @@ -0,0 +1,102 @@ +-- engine/battle/misc.asm:37 FormatMovesString .printDashLoop + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local BattleState = require("src.battle.BattleState") +local Font = require("src.render.Font") + +local realDraw, realDrawCode, realDrawBox = Font.draw, Font.drawCode, Font.drawBox +local drawn + +local function stubFont() + drawn = {} + Font.draw = function(text, x, y) drawn[#drawn + 1] = { text = text, x = x, y = y } end + Font.drawCode = function() end + Font.drawBox = function() end +end + +local function unstubFont() + Font.draw, Font.drawCode, Font.drawBox = realDraw, realDrawCode, realDrawBox +end + +-- a mon with fewer than four moves: the remaining rows must be dashes, not +-- simply absent (ipairs used to stop at the last known move). +do + stubFont() + local screen = setmetatable({ + phase = "moveSelect", + player = { curMoves = { { id = "TACKLE", pp = 35 } } }, + data = { moves = { TACKLE = { name = "TACKLE", pp = 35, type = "NORMAL" } } }, + moveIndex = 1, frame = 0, + }, { __index = BattleState }) + local ok, err = pcall(function() screen:drawTextArea() end) + T.check(ok, "moveSelect draws without error (" .. tostring(err) .. ")") + + local rows = {} + for _, d in ipairs(drawn) do + if d.x == 48 and d.y >= 104 and d.y <= 128 then rows[#rows + 1] = d.text end + end + T.eq(#rows, 4, "all four move rows are drawn, even the unused ones") + T.eq(rows[1], "TACKLE", "the one real move prints its name") + T.eq(rows[2], "-", "an empty slot is a dash") + T.eq(rows[3], "-", "so is the next one") + T.eq(rows[4], "-", "and the last one") + unstubFont() +end + +-- a full four-move mon: no dashes anywhere. +do + stubFont() + local screen = setmetatable({ + phase = "moveSelect", + player = { curMoves = { + { id = "TACKLE", pp = 35 }, { id = "GROWL", pp = 40 }, + { id = "TACKLE", pp = 35 }, { id = "GROWL", pp = 40 }, + } }, + data = { moves = { + TACKLE = { name = "TACKLE", pp = 35, type = "NORMAL" }, + GROWL = { name = "GROWL", pp = 40, type = "NORMAL" }, + } }, + moveIndex = 1, frame = 0, + }, { __index = BattleState }) + screen:drawTextArea() + local rows = {} + for _, d in ipairs(drawn) do + if d.x == 48 and d.y >= 104 and d.y <= 128 then rows[#rows + 1] = d.text end + end + T.eq(#rows, 4, "still exactly four rows") + for i, want in ipairs({ "TACKLE", "GROWL", "TACKLE", "GROWL" }) do + T.eq(rows[i], want, "row " .. i .. " keeps its own move name") + end + unstubFont() +end + +-- the Mimic menu shares FormatMovesString on the cart, so it gets the same +-- dash treatment. +do + stubFont() + local screen = setmetatable({ + phase = "mimicSelect", + mimicMoves = { { id = "TACKLE" }, { id = "GROWL" } }, + data = { moves = { + TACKLE = { name = "TACKLE" }, GROWL = { name = "GROWL" }, + } }, + mimicIndex = 1, frame = 0, + }, { __index = BattleState }) + local ok = pcall(function() screen:drawTextArea() end) + T.check(ok, "mimicSelect draws without error") + local rows = {} + for _, d in ipairs(drawn) do + if d.x == 16 and d.y >= 64 and d.y <= 88 then rows[#rows + 1] = d.text end + end + T.eq(rows[1], "TACKLE", "mimic row 1 is the enemy's first move") + T.eq(rows[2], "GROWL", "mimic row 2 is its second") + T.eq(rows[3], "-", "an enemy with fewer than four moves dashes out the rest") + T.eq(rows[4], "-", "including the last row") + unstubFont() +end + +T.finish("battle move slot dashes bug 1343") diff --git a/tests/engine/blues_house_daisy_walking_bug1338.lua b/tests/engine/blues_house_daisy_walking_bug1338.lua new file mode 100644 index 00000000..f66aa591 --- /dev/null +++ b/tests/engine/blues_house_daisy_walking_bug1338.lua @@ -0,0 +1,80 @@ +-- #1338: after the TOWN MAP, Daisy has to swap from the sitting object to +-- the walking one -- PalletTownDaisyScript, gated on both +-- EVENT_GOT_TOWN_MAP and EVENT_ENTERED_BLUES_HOUSE. +-- scripts/BluesHouse.asm:12-16; scripts/PalletTown.asm:133-144 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local Story = assert(loadfile("data/scripts/story.lua"))() +local Story2 = assert(loadfile("data/scripts/story2.lua"))() + +T.check(type(Story.BLUES_HOUSE.onEnter) == "function", + "M.BLUES_HOUSE.onEnter exists") +T.check(type(Story2.PALLET_TOWN.onEnter) == "function", + "M.PALLET_TOWN.onEnter exists") + +-- Entering Blue's House alone must set EVENT_ENTERED_BLUES_HOUSE and touch +-- nothing else: BluesHouseDefaultScript is a plain SetEvent, no swap here. +do + local game = { save = { flags = {} } } + Story.BLUES_HOUSE.onEnter(game, {}) + T.check(game.save.flags.EVENT_ENTERED_BLUES_HOUSE == true, + "onEnter sets EVENT_ENTERED_BLUES_HOUSE") + T.check(game.save.flags.EVENT_DAISY_WALKING == nil, + "and does not itself start the swap") +end + +-- PALLET_TOWN's onEnter is the swap: both events must be set, and it must +-- write the toggle even though BLUES_HOUSE is not the live map (the fix's +-- own precondition, verified against src/script/Commands.lua's toggleObject +-- writing save.objectToggles before its live-NPC early return). +do + local game = { + save = { + flags = { EVENT_GOT_TOWN_MAP = true, EVENT_ENTERED_BLUES_HOUSE = true }, + }, + } + local ow = { map = { id = "PALLET_TOWN" } } + Story2.PALLET_TOWN.onEnter(game, ow) + T.check(game.save.flags.EVENT_DAISY_WALKING == true, + "both prerequisites set: EVENT_DAISY_WALKING fires") + local toggles = game.save.objectToggles and game.save.objectToggles.BLUES_HOUSE + T.check(toggles ~= nil, "the swap reaches BLUES_HOUSE's toggle table") + T.eq(toggles and toggles.BLUESHOUSE_DAISY1, false, + "the sitting Daisy (DAISY1) is hidden") + T.eq(toggles and toggles.BLUESHOUSE_DAISY2, true, + "the walking Daisy (DAISY2) is shown") +end + +-- Only having the map, without ever entering the house, must not swap her: +-- EVENT_ENTERED_BLUES_HOUSE is a real gate, not a formality. +do + local game = { + save = { flags = { EVENT_GOT_TOWN_MAP = true } }, + } + Story2.PALLET_TOWN.onEnter(game, { map = { id = "PALLET_TOWN" } }) + T.check(game.save.flags.EVENT_DAISY_WALKING == nil, + "without EVENT_ENTERED_BLUES_HOUSE the swap does not fire") +end + +-- Re-entering Pallet Town after she has already swapped must not re-run +-- the toggle writes (EVENT_DAISY_WALKING itself is the guard). +do + local game = { + save = { + flags = { + EVENT_GOT_TOWN_MAP = true, + EVENT_ENTERED_BLUES_HOUSE = true, + EVENT_DAISY_WALKING = true, + }, + objectToggles = {}, + }, + } + local ow = { map = { id = "PALLET_TOWN" } } + Story2.PALLET_TOWN.onEnter(game, ow) + T.check(next(game.save.objectToggles) == nil, + "already-walking Daisy: onEnter writes no toggle a second time") +end + +T.finish("blues_house_daisy_walking_bug1338") diff --git a/tests/engine/exp_traded_ot_survives_reload_bug1265.lua b/tests/engine/exp_traded_ot_survives_reload_bug1265.lua new file mode 100644 index 00000000..26619855 --- /dev/null +++ b/tests/engine/exp_traded_ot_survives_reload_bug1265.lua @@ -0,0 +1,49 @@ +-- engine/battle/experience.asm:69 + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local BattleState = require("src.battle.BattleState") + +local function newSave() + return { player = { id = 12345, name = "RED" } } +end + +do + local save = newSave() + local homegrown = {} + BattleState.stampOT(save, homegrown) + T.eq(homegrown.otId, 12345, "a home-grown mon is still stamped with the player id") + T.eq(homegrown.ot, "RED", "and the player's own name") +end + +-- The bug: a mon that arrived traded (traded = true) but whose OT id was +-- never recorded (a legacy peer, a link mon with no otId in its packet) used +-- to get save.player.id written into otId on the very first load, which then +-- reads identically to a mon the player caught -- awardExp's OT-id compare +-- (BattleState.lua ~4009) permanently loses the 1.5x boost. +do + local save = newSave() + local tradedNoId = { traded = true } + BattleState.stampOT(save, tradedNoId) + T.eq(tradedNoId.otId, nil, + "a traded mon with no OT id is left unstamped, not silently adopted") + T.eq(tradedNoId.ot, "RED", + "the OT NAME fill still happens (cosmetic, not the boost gate)") + -- a second stampOT pass (a second save/load cycle) must not adopt it either + BattleState.stampOT(save, tradedNoId) + T.eq(tradedNoId.otId, nil, "repeated reloads do not eventually stamp it") +end + +-- A mon with its own foreign OT id (the ordinary traded-in case) is untouched +-- either way; this is the arm the regression never broke. +do + local save = newSave() + local tradedWithId = { traded = true, otId = 777 } + BattleState.stampOT(save, tradedWithId) + T.eq(tradedWithId.otId, 777, "a recorded foreign OT id is never overwritten") +end + +T.finish("exp traded ot survives reload bug 1265") diff --git a/tests/engine/gen2_ball_throw_pic_latch_bug1232.lua b/tests/engine/gen2_ball_throw_pic_latch_bug1232.lua new file mode 100644 index 00000000..6d8685d3 --- /dev/null +++ b/tests/engine/gen2_ball_throw_pic_latch_bug1232.lua @@ -0,0 +1,82 @@ +-- data/moves/animations.asm:379 + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local UI = require("src.ui.gen2.BattleState") + +local function newSelf(opts) + opts = opts or {} + return setmetatable({ + anim = opts.anim, + ballThrow = opts.ballThrow, + picHidden = { player = false, enemy = false }, + pendingAfterAnim = nil, + afterSendOut = nil, + }, { __index = UI }) +end + +-- pushCaught itself must never touch picHidden: only the animation's own +-- steps (stepAnim) are allowed to latch the enemy pic box. +do + local self1 = setmetatable({ + battle = {}, tutorial = true, save = nil, + queue = {}, picHidden = { player = false, enemy = false }, + }, { __index = UI }) + self1:pushCaught({ species = "RATTATA" }, "POKE_BALL") + T.eq(self1.picHidden.enemy, false, + "pushCaught alone does not hide the enemy pic") + T.eq(self1.battle.outcome, "caught", "pushCaught still marks the battle caught") +end + +-- stepAnim, natural end (anim:step() returns false): a caught ball throw +-- latches, everything else does not. +do + local caughtAnim = { animId = "ANIM_THROW_POKE_BALL", + step = function() return false end, keepSprites = false } + local s = newSelf({ anim = caughtAnim, ballThrow = { caught = true } }) + s:stepAnim(nil) + T.eq(s.picHidden.enemy, true, "caught ball throw latches at the natural end") + T.eq(s.anim, nil, "the finished runner is cleared") +end + +do + local breakFreeAnim = { animId = "ANIM_THROW_POKE_BALL", + step = function() return false end, keepSprites = false } + local s = newSelf({ anim = breakFreeAnim, ballThrow = { caught = false } }) + s:stepAnim(nil) + T.eq(s.picHidden.enemy, false, "a break-free throw does not latch") +end + +do + local otherAnim = { animId = "ANIM_HYDRO_PUMP", + step = function() return false end, keepSprites = false } + local s = newSelf({ anim = otherAnim, ballThrow = { caught = true } }) + s:stepAnim(nil) + T.eq(s.picHidden.enemy, false, "an unrelated animation never latches") +end + +-- stepAnim, cut short with B: the property the latch exists for -- a caught +-- mon must not reappear even if the player skips past "Gotcha!". +do + local caughtAnim = { animId = "ANIM_THROW_POKE_BALL", + step = function() return true end, keepSprites = false } + local s = newSelf({ anim = caughtAnim, ballThrow = { caught = true } }) + local input = { wasPressed = function(_, key) return key == "b" end } + s:stepAnim(input) + T.eq(s.picHidden.enemy, true, "a B-skipped catch still latches") + T.eq(s.anim, nil, "B cuts the runner short") +end + +do + local breakFreeAnim = { animId = "ANIM_THROW_POKE_BALL", + step = function() return true end, keepSprites = false } + local s = newSelf({ anim = breakFreeAnim, ballThrow = { caught = false } }) + local input = { wasPressed = function(_, key) return key == "b" end } + s:stepAnim(input) + T.eq(s.picHidden.enemy, false, "a B-skipped break-free does not latch") +end + +T.finish("gen2 ball throw pic latch bug 1232") diff --git a/tests/engine/gen2_battler_gfx_rows_bug1231.lua b/tests/engine/gen2_battler_gfx_rows_bug1231.lua new file mode 100644 index 00000000..e74a036a --- /dev/null +++ b/tests/engine/gen2_battler_gfx_rows_bug1231.lua @@ -0,0 +1,58 @@ +-- engine/battle_anims/anim_commands.asm:755 BattleAnimCmd_BattlerGFX_1Row + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local AnimRunner = require("src.battle.gen2.AnimRunner") + +local function findLoaded(runner, gfx) + for _, entry in ipairs(runner.loaded) do + if entry.gfx == gfx then return entry end + end + return nil +end + +do + local runner = AnimRunner.new({}) + runner:start(nil) + runner:loadBattlerGfx(1) + local head = findLoaded(runner, "BATTLE_ANIM_GFX_PLAYERHEAD") + local feet = findLoaded(runner, "BATTLE_ANIM_GFX_ENEMYFEET") + T.check(head and feet, "both pseudo-sheets registered") + T.eq(head.battler, "enemy", + "GFX_PLAYERHEAD's tiles are the ENEMY's feet row") + T.eq(head.tiles, 7, "seven tiles, the enemy pic's width") + T.eq(head.tile, (0x80 - 6 - 7) - 49, "at the asm's fixed base") + T.eq(feet.battler, "player", + "GFX_ENEMYFEET's tiles are the PLAYER's head row") + T.eq(feet.tiles, 6, "six tiles, the backpic's width") + T.eq(feet.tile, (0x80 - 6) - 49, "at the asm's fixed base") + T.eq(head.rows, 1, "one row each") + T.eq(feet.rows, 1, "on both sheets") +end + +do + local runner = AnimRunner.new({}) + runner:start(nil) + runner:loadBattlerGfx(2) + local head = findLoaded(runner, "BATTLE_ANIM_GFX_PLAYERHEAD") + local feet = findLoaded(runner, "BATTLE_ANIM_GFX_ENEMYFEET") + T.eq(head.battler, "enemy", "2ROW keeps the same crossing") + T.eq(head.tiles, 14, "two enemy rows") + T.eq(feet.battler, "player", "on both sides") + T.eq(feet.tiles, 12, "two player rows") + T.eq(head.tile, (0x80 - 6 * 2 - 7 * 2) - 49, "2ROW base") + T.eq(feet.tile, (0x80 - 6 * 2) - 49, "2ROW base") +end + +do + local runner = AnimRunner.new({}) + runner:start(nil) + AnimRunner.COMMANDS.battlergfx_1row(runner) + local head = findLoaded(runner, "BATTLE_ANIM_GFX_PLAYERHEAD") + T.eq(head and head.battler, "enemy", "the script command routes the same way") +end + +T.finish("gen2 battler gfx row attribution bug 1231") diff --git a/tests/engine/gen2_charge_move_anim_param_bug1293.lua b/tests/engine/gen2_charge_move_anim_param_bug1293.lua new file mode 100644 index 00000000..5619fcf5 --- /dev/null +++ b/tests/engine/gen2_charge_move_anim_param_bug1293.lua @@ -0,0 +1,94 @@ +-- engine/battle/effect_commands.asm:5458 BattleCommand_Charge + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local Battle = require("src.battle.gen2.Battle") +local Mon = require("src.battle.gen2.Mon") + +local TYPES = { + NORMAL = { id = "NORMAL", index = 0, category = "physical" }, + GROUND = { id = "GROUND", index = 1, category = "physical" }, + FLYING = { id = "FLYING", index = 2, category = "physical" }, +} + +local MOVES = { + TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL", + accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" }, + DIG = { id = "DIG", name = "DIG", power = 60, type = "GROUND", + accuracy = 100, pp = 10, effect = "EFFECT_FLY" }, + FLY = { id = "FLY", name = "FLY", power = 70, type = "FLYING", + accuracy = 95, pp = 15, effect = "EFFECT_FLY" }, +} + +local POKEMON = { + growthRates = { + GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0, + linear = 0, constant = 0 }, + }, + MACHOP = { id = "MACHOP", index = 66, name = "MACHOP", + baseStats = { hp = 70, attack = 80, defense = 50, speed = 35, + specialAttack = 35, specialDefense = 35 }, + types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63, + levelMoves = {}, evolutions = {} }, +} + +local DATA = { pokemon = POKEMON, moves = MOVES, + type_chart = { types = TYPES, matchups = {} }, items = {} } + +local perfect = { attack = 15, defense = 15, speed = 15, special = 15 } +perfect.hp = Mon.hpDV(perfect) + +local function highRoll(n) return (n or 1) - 1 end + +local function newBattle(moveId) + local player = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + player.moves = { { id = moveId, pp = 20, maxPp = 20 } } + local wild = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } + return Battle.new({ data = DATA, party = { player }, wild = wild, + random = highRoll }), player, wild +end + +local function moveEvent(events) + for _, e in ipairs(events or {}) do + if e.kind == "move" then return e end + end +end + +do + local battle, player, wild = newBattle("DIG") + battle.events = {} + battle:useMove(player, wild, "DIG") + local ev = moveEvent(battle.events) + T.check(ev ~= nil, "the charge turn queues a move event") + T.eq(ev and ev.animParam, 1, + "DIG's charge (burrow) turn carries animParam 1, the take-cover script arm") + battle.events = {} + battle:useMove(player, wild, "DIG") + local ev2 = moveEvent(battle.events) + T.check(ev2 ~= nil, "the strike turn also queues a move event") + T.eq(ev2 and ev2.animParam, nil, + "DIG's strike turn leaves animParam nil, the hit script arm") +end + +do + local battle, player, wild = newBattle("FLY") + battle.events = {} + battle:useMove(player, wild, "FLY") + local ev = moveEvent(battle.events) + T.eq(ev and ev.animParam, 1, "FLY's take-off turn also carries animParam 1") +end + +-- a plain hit-and-run move never sets a parameter at all +do + local battle, player, wild = newBattle("DIG") + player.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } + battle.events = {} + battle:useMove(player, wild, "TACKLE") + local ev = moveEvent(battle.events) + T.eq(ev and ev.animParam, nil, "a non-charge move never carries animParam") +end + +T.finish("gen2 charge move anim param bug 1293") diff --git a/tests/engine/gen2_coin_case_bug1251.lua b/tests/engine/gen2_coin_case_bug1251.lua new file mode 100644 index 00000000..4ce1278b --- /dev/null +++ b/tests/engine/gen2_coin_case_bug1251.lua @@ -0,0 +1,83 @@ +-- #1251: the Game Corner's `CheckCoinsAndCoinCase` transcription must ask +-- the bag about the real COIN_CASE item id, not SILVER_WING. +-- constants/item_constants.asm:62 (COIN_CASE = $36); SILVER_WING is $47. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local Vm = require("src.script.gen2.Vm") +local Specials = require("src.script.gen2.Specials") +local Events = require("src.world.gen2.Events") + +local COIN_CASE = 0x36 +local SILVER_WING = 0x47 + +-- 1) the id the handler actually queries the bag with +local seenId +local vm = Vm.new({ generation = 2 }, {}, Events.new(), { + specials = { + coins = function() return 100 end, + hasItem = function(id) seenId = id return true end, + gameCornerGame = function(_, done) done() end, + }, +}) +vm.showTextFn = function() end +vm.co = coroutine.create(function() Specials.HANDLERS.SlotMachine(vm) end) +coroutine.resume(vm.co) +T.eq(seenId, COIN_CASE, + "SlotMachine's CheckItem call carries the real COIN_CASE id ($36)") +T.check(seenId ~= SILVER_WING, + "and specifically not SILVER_WING ($47), the pre-fix value") + +-- 2) a bag holding ONLY the real coin case (not the silver wing) must be +-- enough to open both machines: this is what would still fail if the id +-- above were merely logged and not actually used to gate the machine. +local bag = { [COIN_CASE] = true } +local slotsOpened, flipOpened +local vm2 = Vm.new({ generation = 2 }, {}, Events.new(), { + specials = { + coins = function() return 50 end, + hasItem = function(id) return bag[id] == true end, + gameCornerGame = function(kind, done) slotsOpened = kind done() end, + }, +}) +vm2.showTextFn = function() end +vm2.co = coroutine.create(function() Specials.HANDLERS.SlotMachine(vm2) end) +coroutine.resume(vm2.co) +T.eq(slotsOpened, "slots", + "a bag with the real COIN CASE and coins opens the slot machine") + +local vm3 = Vm.new({ generation = 2 }, {}, Events.new(), { + specials = { + coins = function() return 50 end, + hasItem = function(id) return bag[id] == true end, + gameCornerGame = function(kind, done) flipOpened = kind done() end, + }, +}) +vm3.showTextFn = function() end +vm3.co = coroutine.create(function() Specials.HANDLERS.CardFlip(vm3) end) +coroutine.resume(vm3.co) +T.eq(flipOpened, "cardflip", + "and the same bag opens card flip too") + +-- 3) the mirror case: SILVER_WING in the bag, no coin case, must still +-- refuse with _NoCoinCaseText (this is the exact symptom in #1251, and it +-- is the yield the coroutine parks on, not a call, so opening never runs +-- behind it). +local wrongBag = { [SILVER_WING] = true } +local opened = false +local vm4 = Vm.new({ generation = 2 }, {}, Events.new(), { + specials = { + coins = function() return 50 end, + hasItem = function(id) return wrongBag[id] == true end, + gameCornerGame = function() opened = true end, + }, +}) +vm4.showTextFn = function() end +vm4.co = coroutine.create(function() Specials.HANDLERS.SlotMachine(vm4) end) +local _, refusal = coroutine.resume(vm4.co) +T.eq(refusal and refusal.text, "You don't have a\nCOIN CASE.", + "holding only SILVER_WING gets the real _NoCoinCaseText refusal") +T.check(not opened, "and the machine never opens behind it") + +T.finish("gen2_coin_case_bug1251") diff --git a/tests/engine/gen2_enemy_move_fail_text_bug1296.lua b/tests/engine/gen2_enemy_move_fail_text_bug1296.lua new file mode 100644 index 00000000..b100c061 --- /dev/null +++ b/tests/engine/gen2_enemy_move_fail_text_bug1296.lua @@ -0,0 +1,166 @@ +-- engine/battle/effect_commands.asm:1958-1961 (the 40 frame hold), +-- engine/battle/effect_commands.asm:3615 (.CheckAIRandomFail, the 25% roll) + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local Battle = require("src.battle.gen2.Battle") +local Mon = require("src.battle.gen2.Mon") +local UI = require("src.ui.gen2.BattleState") + +local TYPES = { + NORMAL = { id = "NORMAL", index = 0, category = "physical" }, + ELECTRIC = { id = "ELECTRIC", index = 1, category = "special" }, +} + +local MOVES = { + TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL", + accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" }, + GROWL = { id = "GROWL", name = "GROWL", power = 0, type = "NORMAL", + accuracy = 100, pp = 40, effect = "EFFECT_ATTACK_DOWN" }, + THUNDER_WAVE = { id = "THUNDER_WAVE", name = "THUNDER WAVE", power = 0, + type = "ELECTRIC", accuracy = 100, pp = 20, effect = "EFFECT_PARALYZE" }, +} + +local POKEMON = { + growthRates = { + GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0, + linear = 0, constant = 0 }, + }, + MACHOP = { id = "MACHOP", index = 66, name = "MACHOP", + baseStats = { hp = 70, attack = 80, defense = 50, speed = 35, + specialAttack = 35, specialDefense = 35 }, + types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63, + levelMoves = {}, evolutions = {} }, +} + +local DATA = { pokemon = POKEMON, moves = MOVES, + type_chart = { types = TYPES, matchups = {} }, items = {} } + +local perfect = { attack = 15, defense = 15, speed = 15, special = 15 } +perfect.hp = Mon.hpDV(perfect) + +-- a controllable roll queue; falls back to a high roll (never fails an AI +-- check) once drained +local rolls +local function rng(n) + if rolls and #rolls > 0 then return table.remove(rolls, 1) % math.max(1, n) end + return (n or 1) - 1 +end + +local function newBattle(pmoves, emoves) + local player = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + player.moves = pmoves + local wild = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + wild.moves = emoves + return Battle.new({ data = DATA, party = { player }, wild = wild, + random = rng }), player, wild +end + +local function findText(events, sub) + for _, e in ipairs(events or {}) do + if e.kind == "message" and e.text and e.text:find(sub, 1, true) then + return true + end + end + return false +end +local function moveEvent(events) + for _, e in ipairs(events or {}) do + if e.kind == "move" then return e end + end +end + +-- ---------------------------------------------------------------- gap 2: +-- the AI's 25% "miss" on a support move, and who is exempt from the roll. +do + local battle, player, wild = newBattle( + { { id = "TACKLE", pp = 35, maxPp = 35 } }, + { { id = "GROWL", pp = 40, maxPp = 40 } }) + battle.events = {} + rolls = { 0, 10 } -- accuracy roll, then the AI roll (10 < 64: fails) + battle:useMove(wild, player, "GROWL") + T.check(findText(battle.events, "But it failed!"), + "enemy GROWL fails 25% of the time with the specific line") + T.eq(moveEvent(battle.events) and moveEvent(battle.events).missed, true, + "an AI-failed move is marked missed (feeds the 40 frame hold)") +end + +do + local battle, player, wild = newBattle( + { { id = "TACKLE", pp = 35, maxPp = 35 } }, + { { id = "GROWL", pp = 40, maxPp = 40 } }) + battle.events = {} + rolls = { 0, 200 } -- AI roll passes (>=64): lands + battle:useMove(wild, player, "GROWL") + T.check(not findText(battle.events, "But it failed!"), + "the same move lands when the AI roll passes") +end + +do + local battle, player, wild = newBattle( + { { id = "GROWL", pp = 40, maxPp = 40 } }, + { { id = "TACKLE", pp = 35, maxPp = 35 } }) + battle.events = {} + rolls = { 0, 10 } -- if the player rolled too, 10 would fail it + battle:useMove(player, wild, "GROWL") + T.check(not findText(battle.events, "But it failed!"), + "the player's own GROWL is exempt from the AI roll") +end + +-- --------------------------------------------------------------- gap 1: +-- the reported symptom -- the "used X!" line must hold before a failure. +do + local input = { wasPressed = function() return false end } + local ui = setmetatable({ + game = { input = input }, + phase = "resolving", slideFrame = 999, messageTimer = 0, + picHidden = { player = false, enemy = false }, + queue = { + { kind = "move", side = "enemy", move = "GROWL", + text = "Enemy MACHOP used GROWL!", missed = true }, + { kind = "message", text = "But it failed!" }, + }, + updateAlarm = function() end, + stepHpAnim = function() return false end, + stepExpAnim = function() return false end, + }, { __index = UI }) + + ui:advanceQueue() + T.eq(ui.message, "Enemy MACHOP used GROWL!", "the used-move line is shown first") + T.eq(ui.messageDelay, 40, + "a missed move arms the 40 frame delay (effect_commands.asm's MoveDelay)") + T.eq(ui.messageTimer, 0, "no separate A/B hold on the move line itself") + + local frames = 0 + for _ = 1, 100 do + if ui.message == "But it failed!" then break end + ui:update(1 / 60) + frames = frames + 1 + end + T.eq(ui.message, "But it failed!", "the queue eventually reaches the failure line") + T.eq(frames, 41, "the used line held for exactly the 40 delay frames") + T.check(ui.messageTimer > 0, "the failure line itself still holds for A/B") +end + +do + local input = { wasPressed = function() return false end } + local ui2 = setmetatable({ + game = { input = input }, + phase = "resolving", slideFrame = 999, messageTimer = 0, + picHidden = { player = false, enemy = false }, + queue = { { kind = "move", side = "player", move = "TACKLE", + text = "MACHOP used TACKLE!" } }, + updateAlarm = function() end, + stepHpAnim = function() return false end, + stepExpAnim = function() return false end, + animForMove = function() return false end, + }, { __index = UI }) + ui2:advanceQueue() + T.eq(ui2.messageDelay or 0, 0, "a move that lands arms no delay at all") +end + +T.finish("gen2 enemy move fail text bug 1296") diff --git a/tests/engine/gen2_fishing_bite_gate_bug1368.lua b/tests/engine/gen2_fishing_bite_gate_bug1368.lua new file mode 100644 index 00000000..8594b4eb --- /dev/null +++ b/tests/engine/gen2_fishing_bite_gate_bug1368.lua @@ -0,0 +1,92 @@ +-- The fishgroup bite roll, missing entirely before #1368: .Fish rolls the +-- group's OWN chance byte before the rod's cumulative list even runs +-- (engine/events/fish.asm:24-30), so every rod bites at whatever that byte +-- says (vanilla Gold is 50 percent + 1 for every group, not 2/3 or 1/2 by +-- rod). A cache built before the extractor carried the byte has no +-- `chance` field on the group row at all and must keep fishing unconditionally. +-- luajit tests/engine/gen2_fishing_bite_gate_bug1368.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq + +local World = require("src.world.gen2.World") + +local DATA = { + pokemon = { + MAGIKARP = { + name = "MAGIKARP", types = { "WATER", "WATER" }, + baseStats = { hp = 20, attack = 10, defense = 55, speed = 80, + specialAttack = 15, specialDefense = 20 }, + levelMoves = {}, + }, + }, +} + +local COLL_FLOOR, COLL_WATER = 0x00, 0x29 + +local function fakeMap(waterCell) + return { + id = "TEST_MAP", + def = { fishGroup = "FISHGROUP_POND" }, + cellCollision = function(_, x, y) + return (x == waterCell[1] and y == waterCell[2]) + and COLL_WATER or COLL_FLOOR + end, + } +end + +-- Rod lists that always hand back a species once a bite happens, so only the +-- group gate (or its absence) decides the outcome below. +local function fishGroups(chance) + return { + FISHGROUP_POND = { + chance = chance, + old = { { chance = 256, species = "MAGIKARP", level = 10 } }, + good = { { chance = 256, species = "MAGIKARP", level = 20 } }, + super = { { chance = 256, species = "MAGIKARP", level = 40 } }, + }, + } +end + +local function fakeWorld(chance) + local game = { data = DATA, save = { party = {} } } + local world = World.new(game) + world.map = fakeMap({ 5, 4 }) + world.maps = { TEST_MAP = world.map.def } + world.encounters = { fishGroups = fishGroups(chance) } + world.player = { cellX = 5, cellY = 5, facing = "up" } + return world +end + +-- ---- chance 0: the group byte fails Random every time, always a nibble --- +-- engine/events/fish.asm:24-30 +do + local world = fakeWorld(0) + for rod = 1, 3 do + local outcome = world:rollFishing(({ "OLD_ROD", "GOOD_ROD", "SUPER_ROD" })[rod]) + eq(outcome, "nibble", + "chance 0 nibbles on " .. ({ "OLD_ROD", "GOOD_ROD", "SUPER_ROD" })[rod]) + end +end + +-- ---- chance 256: the group byte always passes, every rod finds the mon --- +do + local world = fakeWorld(256) + for _, rod in ipairs({ "OLD_ROD", "GOOD_ROD", "SUPER_ROD" }) do + local outcome, wild = world:rollFishing(rod) + eq(outcome, "battle", "chance 256 always bites on " .. rod) + check(wild and wild.species == "MAGIKARP", + "and the rod's own list still resolves a species") + end +end + +-- ---- no chance field at all: an old cache keeps fishing unconditionally -- +do + local world = fakeWorld(nil) + local outcome = world:rollFishing("OLD_ROD") + eq(outcome, "battle", + "a cache with no group chance byte is not gated at all") +end + +T.finish("gen2 fishing bite gate bug1368") diff --git a/tests/engine/gen2_grass_encounter_tod_bug1389.lua b/tests/engine/gen2_grass_encounter_tod_bug1389.lua new file mode 100644 index 00000000..05adce91 --- /dev/null +++ b/tests/engine/gen2_grass_encounter_tod_bug1389.lua @@ -0,0 +1,115 @@ +-- Grass encounters must key off the CLOCK (wTimeOfDay), never the palette +-- set a map header pins (wTimeOfDayPal): engine/overworld/wildmons.asm:283 +-- reads wTimeOfDay for both the rate (GetMapEncounterRate) and the slot list +-- (ChooseWildEncounter). A PALETTE_DAY tower like Sprout Tower must still +-- roll its night table after dark (#1389, Gastly unobtainable), and a +-- PALETTE_NITE cave must still roll its morning/day table at noon. +-- luajit tests/engine/gen2_grass_encounter_tod_bug1389.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq + +local World = require("src.world.gen2.World") + +local DATA = { + pokemon = { + RATTATA = { + name = "RATTATA", types = { "NORMAL", "NORMAL" }, + baseStats = { hp = 30, attack = 56, defense = 35, speed = 72, + specialAttack = 25, specialDefense = 35 }, + levelMoves = {}, + }, + GASTLY = { + name = "GASTLY", types = { "GHOST", "POISON" }, + baseStats = { hp = 30, attack = 35, defense = 30, speed = 80, + specialAttack = 100, specialDefense = 35 }, + levelMoves = {}, + }, + }, +} + +local function fullList(species) + local slots = {} + for i = 1, 7 do slots[i] = { species = species, level = 10 } end + return slots +end + +-- data/wild/johto_grass.asm's own shape for a pinned tower: day is common +-- and worthless, night is the whole reason the room exists. A second, +-- separate map stands in for a PALETTE_NITE dungeon (Ilex Forest, Mt Moon): +-- the rates are flipped so the two fixtures cannot agree by accident. +local ENCOUNTERS = { + grass = { + SPROUT_TOWER_2F = { + rates = { MORN = 0, DAY = 0, NITE = 256 }, + slots = { + MORN = fullList("RATTATA"), + DAY = fullList("RATTATA"), + NITE = fullList("GASTLY"), + }, + }, + ILEX_FOREST = { + rates = { MORN = 256, DAY = 256, NITE = 0 }, + slots = { + MORN = fullList("RATTATA"), + DAY = fullList("RATTATA"), + NITE = fullList("GASTLY"), + }, + }, + }, +} + +local COLL_FLOOR = 0x00 + +local function fakeWorld(mapId, tod, daytime) + local game = { data = DATA, + save = { party = { { species = "RATTATA", level = 5 } } } } + local world = World.new(game) + world.map = { + id = mapId, + def = { environment = "DUNGEON" }, + cellCollision = function() return COLL_FLOOR end, + } + world.maps = { [mapId] = world.map.def } + world.encounters = ENCOUNTERS + world.player = { cellX = 5, cellY = 5, facing = "down" } + -- World:applyPalettes writes both fields every load; a PALETTE_DAY tower + -- pins `daytime` to DAY no matter the hour, while `tod` keeps tracking the + -- clock (src/world/gen2/World.lua:8271-8326). + world.tod = tod + world.daytime = daytime + local battled + world.startBattle = function(_, opts) + battled = opts.wild and opts.wild.species + return true + end + return world, function() return battled end +end + +-- ---- PALETTE_DAY tower, at night: the clock says NITE, the pin says DAY -- +do + local world, battled = fakeWorld("SPROUT_TOWER_2F", "NITE", "DAY") + check(world:tryWildEncounter(), "the tower rolls at night despite the pin") + eq(battled(), "GASTLY", + "the night list wins because the lookup is the clock, not the pin") +end + +-- ---- the same tower at actual daytime: the clock and the pin now agree --- +do + local world, battled = fakeWorld("SPROUT_TOWER_2F", "DAY", "DAY") + check(not world:tryWildEncounter(), + "DAY's rate is zero, so a daytime step in the tower rolls nothing") + eq(battled(), nil, "and nothing battled") +end + +-- ---- a PALETTE_NITE dungeon at actual noon: the pin says NITE, clock DAY -- +do + local world, battled = fakeWorld("ILEX_FOREST", "DAY", "NITE") + check(world:tryWildEncounter(), + "a pinned-night map still rolls its day table at the clock's noon") + eq(battled(), "RATTATA", + "the day list wins because the lookup ignores the palette pin") +end + +T.finish("gen2 grass encounter tod bug1389") diff --git a/tests/engine/gen2_pokedex_area_landmark_bug1267.lua b/tests/engine/gen2_pokedex_area_landmark_bug1267.lua new file mode 100644 index 00000000..8791c325 --- /dev/null +++ b/tests/engine/gen2_pokedex_area_landmark_bug1267.lua @@ -0,0 +1,88 @@ +-- Gold #DEX AREA page drew no nest markers or landmark name because +-- PokedexMenu:drawArea read the non-existent self.data.landmarks instead of +-- the gen2Landmarks table Nests already resolves through (#1267). +-- engine/pokegear/pokegear.asm:2427 +-- luajit tests/engine/gen2_pokedex_area_landmark_bug1267.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local PokedexMenu = require("src.ui.gen2.PokedexMenu") +local Nests = require("src.core.gen2.Nests") + +-- one Johto landmark (index 5), one species that nests there +local data = { + gen2Encounters = { + grass = { + ROUTE_30 = { slots = { day = { { species = "RATTATA" } } } }, + }, + }, + gen2Maps = { + ROUTE_30 = { landmark = 5 }, + }, + gen2Landmarks = { + landmarks = { + LANDMARK_ROUTE_30 = { index = 5, x = 40, y = 60, name = "ROUTE 30" }, + }, + }, +} + +-- sanity: Nests.landmark itself resolves the index (never broken, per the +-- verifier) so a failure below is isolated to drawArea's own lookup +eq(Nests.landmark(data, 5) and Nests.landmark(data, 5).name, "ROUTE 30", + "Nests.landmark resolves index 5 to the ROUTE 30 record") + +-- capture what drawArea actually paints, without needing a real tile sheet +-- or font: fill/blank/current/monName are stubbed on the instance, which +-- Lua resolves before the PokedexMenu metatable's own methods. +local function newSelf() + local texts = {} + local rects = {} + local self = setmetatable({ + game = { save = {} }, + data = data, + mapGfx = { maps = { johto = { 1 } } }, -- non-nil `cells`, no real sheet + areaRegion = "johto", + areaBlink = 0, -- (0 % 32) < 20, so markers are in their "on" phase + current = function() return { species = "RATTATA" } end, + monName = function() return "RATTATA" end, + fill = function() end, + blank = function() end, + text = function(_, str, tx, ty) + texts[#texts + 1] = { str = str, tx = tx, ty = ty } + end, + }, { __index = PokedexMenu }) + return self, texts, rects +end + +local realRect = love.graphics.rectangle +local self, texts, rects +do + self, texts, rects = newSelf() + love.graphics.rectangle = function(mode, x, y, w, h) + rects[#rects + 1] = { mode = mode, x = x, y = y, w = w, h = h } + end + self:drawArea() + love.graphics.rectangle = realRect +end + +local function hasRect(x, y) + for _, r in ipairs(rects) do + if r.x == x and r.y == y then return true end + end + return false +end +check(hasRect(40 - 2, 60 - 2), "the nest marker is drawn at the landmark's x-2,y-2") + +local function hasText(str) + for _, t in ipairs(texts) do + if t.str == str then return true end + end + return false +end +check(hasText("ROUTE 30"), "the landmark name is printed on row 16") + +T.finish("gen2 pokedex area landmark bug 1267") diff --git a/tests/engine/gen2_rocket_map_music_bug1385.lua b/tests/engine/gen2_rocket_map_music_bug1385.lua new file mode 100644 index 00000000..fb6d638b --- /dev/null +++ b/tests/engine/gen2_rocket_map_music_bug1385.lua @@ -0,0 +1,93 @@ +-- GetMapMusic (pokegold home/map.asm:2550), #1385 +-- luajit tests/engine/gen2_rocket_map_music_bug1385.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq + +love = require("tests.love_stub") + +local Source = {} +Source.__index = Source +function Source:play() self.playing = true end +function Source:stop() self.playing = false end +function Source:pause() self.playing = false end +function Source:isPlaying() return self.playing end +function Source:setLooping() end +function Source:setVolume(v) self.volume = v end +function Source:setPitch() end +function Source:setFilter() end +function Source:getDuration() return 1 end + +local made = {} +love.audio = { + newSource = function(file, mode) + made[file] = setmetatable({ file = file, mode = mode }, Source) + return made[file] + end, +} + +local World = require("src.world.gen2.World") +local Music = require("src.core.Music") + +-- constants/music_constants.asm:100,:109 +local MUSIC_MAHOGANY_MART = 100 +local RADIO_TOWER_SENTINEL = 0x80 + 61 + +local order = {} +order[61 + 1] = "Music_GoldenrodCity" +local audio = { + musicOrder = order, + songs = { + Music_RocketHideout = { file = "rocket_hideout.wav" }, + Music_CherrygroveCity = { file = "cherrygrove.wav" }, + Music_RocketTheme = { file = "rocket_theme.wav" }, + Music_GoldenrodCity = { file = "goldenrod.wav" }, + }, +} + +eq(World.mapMusicLabel(audio, MUSIC_MAHOGANY_MART, true, false), + "Music_RocketHideout", + "MAHOGANY_MART_1F with rockets in the mart plays the hideout theme") +eq(World.mapMusicLabel(audio, MUSIC_MAHOGANY_MART, false, false), + "Music_CherrygroveCity", + "MAHOGANY_MART_1F after the hideout is cleared plays Cherrygrove") +eq(World.mapMusicLabel(audio, RADIO_TOWER_SENTINEL, false, true), + "Music_RocketTheme", + "RADIO_TOWER floors during the takeover play the rocket theme") +eq(World.mapMusicLabel(audio, RADIO_TOWER_SENTINEL, false, false), + "Music_GoldenrodCity", + "RADIO_TOWER floors otherwise fall back to the low bits of the byte") +eq(World.mapMusicLabel(audio, 72, true, true), nil, + "a plain song id stays with the mapSongs table") +eq(World.mapMusicLabel(audio, nil, true, true), nil, + "a missing music byte resolves to nothing") + +local data = { audio = { + songs = { + Music_RocketHideout = { file = "rocket_hideout.wav" }, + Music_Victory = { file = "victory.wav" }, + }, + mapSongs = {}, +} } + +local function playing() + for file, src in pairs(made) do + if src.playing then return file end + end + return "(silence)" +end + +Music.stop() +Music.playMap(data, "MAHOGANY_MART_1F", false, false, nil, + "Music_RocketHideout") +eq(playing(), "rocket_hideout.wav", + "the resolved song overrides the empty mapSongs table") + +Music.play(data, "Music_Victory", nil, { reason = "battle" }) +eq(playing(), "victory.wav", "the battle result theme takes over") +Music.restoreMap(data) +eq(playing(), "rocket_hideout.wav", + "restoreMap replays the resolved song, ending the victory loop") + +T.finish("gen2_rocket_map_music_bug1385") diff --git a/tests/engine/gen2_safeguard_bug1388.lua b/tests/engine/gen2_safeguard_bug1388.lua new file mode 100644 index 00000000..9d3bc5e8 --- /dev/null +++ b/tests/engine/gen2_safeguard_bug1388.lua @@ -0,0 +1,171 @@ +-- engine/battle/move_effects/safeguard.asm:1, engine/battle/effect_commands.asm:6325 + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local Battle = require("src.battle.gen2.Battle") +local Mon = require("src.battle.gen2.Mon") + +local TYPES = { + NORMAL = { id = "NORMAL", index = 0, category = "physical" }, + ELECTRIC = { id = "ELECTRIC", index = 1, category = "special" }, + FIRE = { id = "FIRE", index = 2, category = "special" }, +} + +local MOVES = { + TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL", + accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" }, + SAFEGUARD = { id = "SAFEGUARD", name = "SAFEGUARD", power = 0, + type = "NORMAL", accuracy = 100, pp = 25, effect = "EFFECT_SAFEGUARD" }, + THUNDER_WAVE = { id = "THUNDER_WAVE", name = "THUNDER WAVE", power = 0, + type = "ELECTRIC", accuracy = 100, pp = 20, effect = "EFFECT_PARALYZE" }, + SACRED_FIRE = { id = "SACRED_FIRE", name = "SACRED FIRE", power = 100, + type = "FIRE", accuracy = 95, pp = 5, effect = "EFFECT_SACRED_FIRE", + effectChance = 50 }, +} + +local POKEMON = { + growthRates = { + GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0, + linear = 0, constant = 0 }, + }, + MACHOP = { id = "MACHOP", index = 66, name = "MACHOP", + baseStats = { hp = 70, attack = 80, defense = 50, speed = 35, + specialAttack = 35, specialDefense = 35 }, + types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75, + growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63, + levelMoves = {}, evolutions = {} }, +} + +local DATA = { pokemon = POKEMON, moves = MOVES, + type_chart = { types = TYPES, matchups = {} }, items = {} } + +local perfect = { attack = 15, defense = 15, speed = 15, special = 15 } +perfect.hp = Mon.hpDV(perfect) + +local rolls +local function rng(n) + if rolls and #rolls > 0 then return table.remove(rolls, 1) % math.max(1, n) end + return (n or 1) - 1 +end + +local function newBattle(pmoves, emoves) + local player = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + player.moves = pmoves + local wild = Mon.new(DATA, "MACHOP", 50, { dvs = perfect }) + wild.moves = emoves + return Battle.new({ data = DATA, party = { player }, wild = wild, + random = rng }), player, wild +end + +local function findText(events, sub) + for _, e in ipairs(events or {}) do + if e.kind == "message" and e.text and e.text:find(sub, 1, true) then + return true + end + end + return false +end +local function moveEvent(events) + for _, e in ipairs(events or {}) do + if e.kind == "move" then return e end + end +end + +-- ------------------------------------------------------------- 1388a +-- Safeguard sets the USER's own side, not the target's. +do + local battle, player, wild = newBattle( + { { id = "SAFEGUARD", pp = 25, maxPp = 25 } }, + { { id = "THUNDER_WAVE", pp = 20, maxPp = 20 } }) + battle.events = {} + battle:useMove(player, wild, "SAFEGUARD") + T.eq(battle.screens.player.safeguard, 5, "Safeguard sets the CASTER's side for 5 turns") + T.check((battle.screens.enemy.safeguard or 0) == 0, + "and never touches the opposing side") + T.check(findText(battle.events, "covered by a veil"), "the veil line is emitted") +end + +do + local battle, player, wild = newBattle( + { { id = "SAFEGUARD", pp = 25, maxPp = 25 } }, + { { id = "THUNDER_WAVE", pp = 20, maxPp = 20 } }) + battle.events = {} + battle:useMove(player, wild, "SAFEGUARD") + battle.events = {} + rolls = { 200 } -- force the enemy's AI roll not to fail on its own + battle:useMove(wild, player, "THUNDER_WAVE") + T.check(findText(battle.events, "protected by SAFEGUARD"), + "an incoming status move from the OTHER side is blocked, loudly") + T.eq(player.status, nil, "and the paralysis never lands") + local ev = moveEvent(battle.events) + T.eq(ev and ev.missed, true, "the blocked move is marked missed") +end + +do + local battle, player, wild = newBattle( + { { id = "SAFEGUARD", pp = 25, maxPp = 25 } }, + { { id = "TACKLE", pp = 35, maxPp = 35 } }) + battle.events = {} + battle:useMove(player, wild, "SAFEGUARD") + battle.events = {} + battle:useMove(player, wild, "SAFEGUARD") + T.check(findText(battle.events, "But it failed!"), + "using it again while it is already up simply fails") +end + +do + -- the player's OWN status move against a safeguarded enemy is blocked too: + -- the effect reads whichever side is being TARGETED, not just "the enemy". + local battle, player, wild = newBattle( + { { id = "THUNDER_WAVE", pp = 20, maxPp = 20 } }, + { { id = "TACKLE", pp = 35, maxPp = 35 } }) + battle.screens.enemy.safeguard = 5 + battle.events = {} + battle:useMove(player, wild, "THUNDER_WAVE") + T.check(findText(battle.events, "protected by SAFEGUARD"), + "the player's status move is blocked by the enemy's own safeguard") + T.eq(wild.status, nil, "the enemy stays unstatused under its own screen") +end + +do + local battle = newBattle( + { { id = "TACKLE", pp = 35, maxPp = 35 } }, + { { id = "TACKLE", pp = 35, maxPp = 35 } }) + battle.screens.player.safeguard = 1 + battle.events = {} + battle:tickScreens() + T.check(findText(battle.events, "SAFEGUARD faded"), "it fades after its five turns") + T.eq(battle.screens.player.safeguard, nil, "and clears off the side entirely") +end + +-- ------------------------------------------------------------- 1388b +-- Sacred Fire's burn was never implemented at all. +do + local battle, player, wild = newBattle( + { { id = "TACKLE", pp = 35, maxPp = 35 } }, + { { id = "SACRED_FIRE", pp = 5, maxPp = 5 } }) + battle.events = {} + rolls = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } -- everything rolls low: hit, no + -- crit, and the 50% secondary effect chance all pass + battle:useMove(wild, player, "SACRED_FIRE") + T.eq(player.status, "burn", "Sacred Fire can now burn its target") +end + +do + local battle, player, wild = newBattle( + { { id = "TACKLE", pp = 35, maxPp = 35 } }, + { { id = "SACRED_FIRE", pp = 5, maxPp = 5 } }) + battle.screens.player.safeguard = 5 + battle.events = {} + rolls = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + battle:useMove(wild, player, "SACRED_FIRE") + T.eq(player.status, nil, "Safeguard blocks the burn a damaging move carries") + T.check(not findText(battle.events, "protected by SAFEGUARD"), + "the secondary block is silent (SafeCheckSafeguard, not CheckSafeguard)") + local ev = moveEvent(battle.events) + T.check(ev and not ev.missed, + "the hit itself still lands and is not marked missed") +end + +T.finish("gen2 safeguard bug 1388") diff --git a/tests/engine/gen2_shadow_ball_bgp_bug1269.lua b/tests/engine/gen2_shadow_ball_bgp_bug1269.lua new file mode 100644 index 00000000..e1287c1b --- /dev/null +++ b/tests/engine/gen2_shadow_ball_bgp_bug1269.lua @@ -0,0 +1,47 @@ +-- engine/battle_anims/anim_commands.asm:603 BattleAnimCmd_BGP + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local AnimRunner = require("src.battle.gen2.AnimRunner") +local BgEffects = require("src.battle.gen2.BgEffects") +local GbcPalette = require("src.render.GbcPalette") + +-- data/moves/animations.asm:4509 BattleAnim_ShadowBall +local SHADOW_BALL = { + { "2gfx", "BATTLE_ANIM_GFX_EGG", "BATTLE_ANIM_GFX_SMOKE" }, + { "bgp", 0x1b }, + { "sound", 6 * 4 + 2, 0 }, + { "obj", "BATTLE_ANIM_OBJ_SHADOW_BALL", 64, 92, 0x2 }, + { "wait", 32 }, +} + +do + local runner = AnimRunner.new({ + data = { scripts = { SHADOW_BALL = SHADOW_BALL } }, + }) + runner:start("SHADOW_BALL") + T.eq(runner.bg.bgp, BgEffects.NORMAL_PAL, "identity ramp before the script") + T.check(runner:step(), "the script is still running after frame one") + T.eq(runner.bg.bgp, 0x1b, + "anim_bgp $1b lands in wBGP: the inverted ramp the view must apply") + runner.bg:reset() + T.eq(runner.bg.bgp, BgEffects.NORMAL_PAL, + "BattleAnim_RevertPals puts the identity back") +end + +do + T.eq(GbcPalette.BGP_IDENTITY, 0xe4, "dc 3, 2, 1, 0") + local colors = { "c0", "c1", "c2", "c3" } + local out = GbcPalette.remap(colors, 0x1b) + T.eq(out[1], "c3", "$1b is dc 0, 1, 2, 3: colour 0 shows shade 3") + T.eq(out[2], "c2", "colour 1 shows shade 2") + T.eq(out[3], "c1", "colour 2 shows shade 1") + T.eq(out[4], "c0", "colour 3 shows shade 0") + T.check(GbcPalette.remap(colors, 0xe4) == colors, + "the identity byte returns the palette untouched") +end + +T.finish("gen2 shadow ball bgp bug 1269") diff --git a/tests/engine/gen2_starter_nickname_bug1228.lua b/tests/engine/gen2_starter_nickname_bug1228.lua new file mode 100644 index 00000000..59bcb293 --- /dev/null +++ b/tests/engine/gen2_starter_nickname_bug1228.lua @@ -0,0 +1,90 @@ +-- #1228: `givepoke` with the 3-argument (untrained) form must run +-- GiveANickname_YesNo, the same as a wild catch does. +-- engine/pokemon/move_mon.asm:1632-1645, 1753-1757, 1787 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local Vm = require("src.script.gen2.Vm") +local Events = require("src.world.gen2.Events") + +-- Elm's `givepoke CYNDAQUIL, 5, BERRY` shape: no trainer operand, so +-- Vm.lua's arg4 fallback reads it as 0 (untrained). +local scripts = { + generation = 2, + ["s:give"] = { + { op = "givepoke", species = 155, level = 5, item = 0, trainer = 0 }, + { op = "end" }, + }, +} + +local mon = { species = 155, level = 5 } +local renamed +local vm = Vm.new(scripts, {}, Events.new(), { + givePoke = function() return mon end, + yesorno = function(onChoose) onChoose(true) end, + showText = function(_, onDone) onDone() end, + specials = { + monName = function(species) return species == 155 and "CYNDAQUIL" or "?" end, + renameMon = function(m, done, opts) + renamed = { mon = m, blank = opts and opts.blank } + done("SPARKY") + end, + }, +}) + +T.check(vm:start("s:give"), "starter script starts") +for _ = 1, 10 do vm:update() end +T.check(not vm:running(), "script finished") +T.eq(mon.nickname, "SPARKY", + "givepoke with trainer=0 runs the nickname prompt and stores the answer") +T.check(renamed ~= nil and renamed.mon == mon, + "renameMon opened on the mon givepoke just handed over") +T.check(renamed.blank == true, + "the keyboard opens blank, same as InitNickname on a fresh catch") + +-- The trade-gift form (trainer ~= 0, e.g. GiftSpearowName's 8-byte +-- Route35GoldenrodGate.asm:30 shape) must never open the keyboard. +local mon2 = { species = 155, level = 5 } +local renamed2 = false +local scripts2 = { + generation = 2, + ["s:give2"] = { + { op = "givepoke", species = 155, level = 5, item = 0, trainer = 1 }, + { op = "end" }, + }, +} +local vm2 = Vm.new(scripts2, {}, Events.new(), { + givePoke = function() return mon2 end, + yesorno = function() error("a trainer-labelled gift must never prompt") end, + showText = function(_, onDone) onDone() end, + specials = { renameMon = function() renamed2 = true end }, +}) +T.check(vm2:start("s:give2"), "trainer-gift script starts") +for _ = 1, 10 do vm2:update() end +T.check(mon2.nickname == nil, "trainer arm leaves the nickname untouched") +T.check(not renamed2, "and never opens the keyboard") + +-- A NO answer, and an all-spaces keyboard entry, both leave the species +-- name standing (_InitString's blank test, home/string.asm:6-30). +local mon3 = { species = 155, level = 5 } +local scripts3 = { + generation = 2, + ["s:give3"] = { + { op = "givepoke", species = 155, level = 5, item = 0, trainer = 0 }, + { op = "end" }, + }, +} +local vm3 = Vm.new(scripts3, {}, Events.new(), { + givePoke = function() return mon3 end, + yesorno = function(onChoose) onChoose(false) end, + showText = function(_, onDone) onDone() end, + specials = { + renameMon = function() error("NO must not open the keyboard") end, + }, +}) +T.check(vm3:start("s:give3"), "NO-answer script starts") +for _ = 1, 10 do vm3:update() end +T.check(mon3.nickname == nil, "answering NO leaves the nickname unset") + +T.finish("gen2_starter_nickname_bug1228") diff --git a/tests/engine/league_pc_bug1282.lua b/tests/engine/league_pc_bug1282.lua new file mode 100644 index 00000000..40a2eeb3 --- /dev/null +++ b/tests/engine/league_pc_bug1282.lua @@ -0,0 +1,94 @@ +-- PKMN LEAGUE (the post-E4 Hall of Fame viewer) never had a screen backing +-- it, so a PC row wired up to open it would have had nowhere to go (#1282). +-- src/ui/LeaguePC.lua is the missing viewer; it resolves through the +-- registry's builtin fallback with no id table edit needed, because every +-- unregistered id falls through to `require("src.ui." .. id)`. +-- engine/menus/league_pc.asm:1, constants/pokemon_data_constants.asm:65 (cap 50) +-- luajit tests/engine/league_pc_bug1282.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local Screens = require("src.ui.Screens") +local LeaguePC = require("src.ui.LeaguePC") + +local function team(species, level) + return { { species = species, level = level, nickname = nil } } +end + +local function newGame(teamCount) + local teams = {} + for i = 1, teamCount do teams[i] = team("RATTATA", i) end + local popped = false + local game = { + data = { pokemon = {}, text = {} }, + save = { hallOfFame = teams }, + stack = { pop = function() popped = true end }, + } + return game, function() return popped end +end + +-- the registry needs no LeaguePC id entry: an unregistered id falls +-- through resolve()'s builtin path straight to src/ui/LeaguePC.lua +local factory = Screens.get({ data = {} }, "LeaguePC") +check(factory == LeaguePC, "Screens.get(\"LeaguePC\") resolves to this module") +check(type(factory.new) == "function", "the resolved factory is constructible") + +-- 60 recorded teams: HOF_TEAM_CAPACITY (50) means the oldest 10 are gone, +-- so the viewer opens on the OLDEST STILL-RECORDED team, index 11 +do + local game = newGame(60) + local pc = LeaguePC.new(game) + eq(pc.teamIndex, 11, "60 teams over a cap of 50 starts at team 11 (60-50+1)") + eq(pc.monIndex, 1, "starts on the first mon of that team") + check(pc:currentMon() ~= nil, "a current mon is resolved") +end + +-- A steps through every remaining team (49 more presses, 11 -> 60), then +-- one more A on the last team's only mon closes the whole viewer +do + local game, wasPopped = newGame(60) + local pc = LeaguePC.new(game) + local doneCalled = false + pc.onDone = function() doneCalled = true end + for _ = 1, 49 do + game.input = { wasPressed = function(_, b) return b == "a" end } + pc:update(0) + end + eq(pc.teamIndex, 60, "49 A-presses walk from team 11 to team 60") + check(not wasPopped(), "the viewer is still open on the last team") + game.input = { wasPressed = function(_, b) return b == "a" end } + pc:update(0) + check(wasPopped(), "one more A on the last team's last mon closes the viewer") + check(doneCalled, "onDone fires on close") +end + +-- B always closes immediately, from any position +do + local game, wasPopped = newGame(3) + local pc = LeaguePC.new(game) + game.input = { wasPressed = function(_, b) return b == "b" end } + pc:update(0) + check(wasPopped(), "B closes the viewer") +end + +-- an empty Hall of Fame (no wins recorded yet, or the extreme edge case of +-- a save with the row reachable but no completed run) must not crash: A on +-- a nil current mon closes cleanly instead of indexing into nothing +do + local game, wasPopped = newGame(0) + local pc = LeaguePC.new(game) + eq(pc.teamIndex, 1, "an empty roster clamps teamIndex to 1, not 0 or negative") + check(pc:currentMon() == nil, "there is no current mon") + local ok = pcall(function() + game.input = { wasPressed = function(_, b) return b == "a" end } + pc:update(0) + end) + check(ok, "A on an empty Hall of Fame does not raise") + check(wasPopped(), "...and closes the viewer instead") +end + +T.finish("league pc bug 1282") diff --git a/tests/engine/naming_screen_opacity_bug1329.lua b/tests/engine/naming_screen_opacity_bug1329.lua new file mode 100644 index 00000000..f34efc49 --- /dev/null +++ b/tests/engine/naming_screen_opacity_bug1329.lua @@ -0,0 +1,86 @@ +-- The NEW NAME / preset box drew on top of a full letter grid because +-- NamingScreen stayed isOpaque while the preset Menu was up, so the stack's +-- visibleBase never fell through to the screen underneath (#1329). +-- engine/movie/oak_speech/oak_speech2.asm:1 +-- luajit tests/engine/naming_screen_opacity_bug1329.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +package.loaded["src.core.Sound"] = { play = function() end } + +local StateStack = require("src.core.StateStack") +local NamingScreen = require("src.ui.NamingScreen") + +local function newGame() + local stack = setmetatable({}, { __index = StateStack }) + stack:init() + local game = { data = {} } + game.stack = stack + game.input = { + queue = {}, + wasPressed = function(self, btn) return self.queue[btn] or false end, + isDown = function() return false end, + } + return game, stack +end + +local game, stack = newGame() + +-- stand-in for OakSpeech: opaque, and the state a fixed background lives on +local backgroundDraws = 0 +local background = { isOpaque = true, draw = function() backgroundDraws = backgroundDraws + 1 end } +stack:push(background) + +local result = { name = nil } +local ns = NamingScreen.new(game, + { presets = { "RED", "ASH", "JACK" }, onDone = function(n) result.name = n end }) +stack:push(ns) -- StateStack:push calls ns:enter(), which pushes the preset Menu + +eq(ns.choosing, true, "the screen marks itself as choosing a preset") +eq(ns.isOpaque, false, "isOpaque is shadowed false while the preset box is up") + +local menu = stack:top() +check(menu ~= nil and menu ~= ns, "the preset Menu is on top of the naming screen") +eq(#menu.items, 4, "NEW NAME plus the three presets") +eq(menu.items[1].label, "NEW NAME", "row 1 is NEW NAME") + +eq(stack:visibleBase(), 1, "the background (index 1) is visible, not the naming screen") + +backgroundDraws = 0 +stack:draw() +eq(backgroundDraws, 1, "the background actually got a draw call this frame") + +-- picking NEW NAME (row 1) restores the grid's normal opacity +menu.index = 1 +menu.game.input.queue.a = true +menu:update(0) +menu.game.input.queue.a = false + +check(stack:top() == ns, "the Menu popped itself, the naming screen is back on top") +eq(ns.choosing, nil, "choosing cleared") +eq(rawget(ns, "isOpaque"), nil, "the instance field is cleared, unshadowing the class default") +eq(ns.isOpaque, true, "isOpaque now reads true again, through the class default") +eq(stack:visibleBase(), 2, "the naming screen itself is now the opaque base") + +-- a preset pick instead closes the whole naming flow with that name +local game2, stack2 = newGame() +local background2 = { isOpaque = true, draw = function() end } +stack2:push(background2) +local result2 = { name = nil } +local ns2 = NamingScreen.new(game2, + { presets = { "RED", "ASH", "JACK" }, onDone = function(n) result2.name = n end }) +stack2:push(ns2) +local menu2 = stack2:top() +eq(menu2.items[4].label, "JACK", "row 4 is the third preset") +menu2.index = 4 -- "JACK" +menu2.game.input.queue.a = true +menu2:update(0) + +eq(result2.name, "JACK", "selecting a preset pops the whole flow with that name") +eq(#stack2.states, 1, "only the background remains on the stack") + +T.finish("naming screen opacity bug 1329") diff --git a/tests/engine/npc_walk_cadence_bug1303.lua b/tests/engine/npc_walk_cadence_bug1303.lua new file mode 100644 index 00000000..6a547cfa --- /dev/null +++ b/tests/engine/npc_walk_cadence_bug1303.lua @@ -0,0 +1,96 @@ +-- The walking-NPC animation cadence, which the port ran at half the cart's +-- rate (#1303). UpdateSpriteInWalkingAnimation advances one animation frame +-- every 4 fixed steps regardless of how long the whole cell takes +-- (engine/overworld/movement.asm:301), so a 32-frame NPC cell must show the +-- same two-pulse cadence Player:pose already shows across its own 16. +-- luajit tests/engine/npc_walk_cadence_bug1303.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq + +love = require("tests.love_stub") + +local NPC = require("src.world.NPC") + +local DATA = { + sprites = { + SPRITE_TEST_NPC = { image = "fixture_npc.png", frames = 6, walker = true }, + }, +} + +local function newNpc() + return NPC.new(DATA, "TEST_MAP", { + index = 1, x = 5, y = 5, sprite = "SPRITE_TEST_NPC", range = "ANY_DIR", + movement = "STAY", + }) +end + +-- ---- the pure cadence: two walk pulses across one 32-frame NPC step ------ +do + local npc = newNpc() + npc.moving = true + local phases = {} + for clock = 0, 31 do + npc.animClock = clock + phases[clock] = npc:walkPhase() + end + local risingEdges = 0 + for clock = 1, 31 do + if phases[clock] == 1 and phases[clock - 1] == 0 then + risingEdges = risingEdges + 1 + end + end + eq(risingEdges, 2, "a 32-frame NPC cell shows two walk pulses, not one") + eq(phases[0], 0, "frame 0 stands") + eq(phases[4], 1, "frame 4 opens the first walk pulse") + eq(phases[11], 1, "frame 11 is still the first pulse") + eq(phases[12], 0, "frame 12 closes it back to standing") + eq(phases[20], 1, "frame 20 opens the second walk pulse") + eq(phases[27], 1, "frame 27 is still the second pulse") + eq(phases[28], 0, "frame 28 closes the cycle back to standing") +end + +-- ---- the flip half-cycle: one flip per 16-frame half, not per whole cell - +do + local npc = newNpc() + npc.moving = true + npc.animClock = 0 + local _, _, _, _, _, flip0 = npc:pose() + npc.animClock = 15 + local _, _, _, _, _, flip15 = npc:pose() + npc.animClock = 16 + local _, _, _, _, _, flip16 = npc:pose() + npc.animClock = 31 + local _, _, _, _, _, flip31 = npc:pose() + check(not flip0, "the first half of the cell is unflipped") + check(not flip15, "still unflipped just before frame 16") + check(flip16, "frame 16 flips, matching Player:pose's own half-cycle") + check(flip31, "and stays flipped through the second half") +end + +-- ---- standing keeps the externally-written flip (Pikachu idle contract) -- +do + local npc = newNpc() + npc.moving = false + npc.stepFlip = true + local _, _, _, _, phase, flip = npc:pose() + eq(phase, 0, "a standing NPC has no walk phase") + check(flip, "and pose() reads stepFlip back exactly, not the moving formula") +end + +-- ---- the wiring: NPC:update advances animClock alongside progress ------- +do + local npc = newNpc() + npc.facing = "down" + npc.moving = true + npc.targetX, npc.targetY = npc.cellX, npc.cellY + 1 + local map = {} + for i = 1, 16 do + npc:update(map, {}) + eq(npc.animClock, i, "animClock ticks once per update, step " .. i) + end + check(npc.moving, "still mid-cell at 16 of the 32 ticks") +end + +T.finish("npc walk cadence bug1303") diff --git a/tests/engine/nurse_bow_bug995.lua b/tests/engine/nurse_bow_bug995.lua index f4cd69be..845443b2 100644 --- a/tests/engine/nurse_bow_bug995.lua +++ b/tests/engine/nurse_bow_bug995.lua @@ -65,7 +65,7 @@ T.check(pushed[1].text:find(BYE, 1, true) == nil, pushed[1].onDone() T.eq(#pushed, 1, "the farewell waits for the bow") -T.eq(nurse.frameOverride, 3, "image index $1: the nurse bows") +T.eq(nurse.frameOverride, 1, "image index $14: the nurse bows") T.check(fakeSelf.emote ~= nil, "the bow is a world hold, not a text pause") local hold = fakeSelf.emote or {} T.eq(hold.npc, nurse, "the hold is anchored on the nurse") diff --git a/tests/engine/oaks_lab_rival_faces_down_bug1279.lua b/tests/engine/oaks_lab_rival_faces_down_bug1279.lua new file mode 100644 index 00000000..f10e2ceb --- /dev/null +++ b/tests/engine/oaks_lab_rival_faces_down_bug1279.lua @@ -0,0 +1,69 @@ +-- #1279: the rival must face DOWN at his own table cell before the taunt, +-- same as SetSpriteFacingDirectionAndDelay does before DisplayTextID -- not +-- just the player turning to face him. +-- scripts/OaksLab.asm:347-351 (Red/Blue); pokeyellow scripts/OaksLab.asm:311-315 +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local function fakeOw(rival) + return { + npcByIndex = function(_, i) return i == 1 and rival or nil end, + map = { + inBounds = function() return true end, + isWalkableCell = function() return true end, + }, + runner = { run = function(_, rows, opts) return rows, opts end }, + } +end + +-- capture the rows the runner was handed, since `run` is only a stub +local function captureRun(ow) + local captured + ow.runner.run = function(_, rows, opts) captured = rows return true end + return function() return captured end +end + +local function baseGame() + return { + save = { + flags = { + EVENT_GOT_STARTER = true, + EVENT_BATTLED_RIVAL_IN_OAKS_LAB = false, + EVENT_CHOSE_BULBASAUR = true, + }, + }, + } +end + +-- Red/Blue +do + local M = assert(loadfile("data/scripts/oaks_lab.lua"))() + local rival = { id = "rival" } + local ow = fakeOw(rival) + local getRows = captureRun(ow) + local ok = M.onStep(baseGame(), ow, 4, 6) + T.check(ok == true, "onStep claims the rival-challenge step") + local rows = getRows() + T.check(rows ~= nil, "the challenge rows reached the runner") + T.same(rows[1], { "face_object", 1, "down" }, + "the FIRST row faces the rival down at his table (#1279)") + T.eq(rows[2][1], "face_player_dir", "the player-facing row still follows it") + T.eq(rows[2][2], "up", "and still turns the player up, unchanged") +end + +-- Yellow +do + local M = assert(loadfile("data/scripts/oaks_lab_yellow.lua"))() + local rival = { id = "rival" } + local ow = fakeOw(rival) + local getRows = captureRun(ow) + local ok = M.onStep(baseGame(), ow, 4, 6) + T.check(ok == true, "yellow onStep claims the rival-challenge step") + local rows = getRows() + T.check(rows ~= nil, "the yellow challenge rows reached the runner") + T.same(rows[1], { "face_object", 1, "down" }, + "yellow's first row faces the rival (object 1) down too (#1279)") +end + +T.finish("oaks_lab_rival_faces_down_bug1279") diff --git a/tests/engine/pewter_museum_escort_bug1391.lua b/tests/engine/pewter_museum_escort_bug1391.lua new file mode 100644 index 00000000..ba99ebdc --- /dev/null +++ b/tests/engine/pewter_museum_escort_bug1391.lua @@ -0,0 +1,73 @@ +-- #1391: the Pewter museum guide's lockstep walk, exercised from all four +-- trigger cells around him, the way PewterGuys (engine/events/ +-- pewter_guys.asm:1-49) builds the preamble and PewterCitySuperNerd1Shows +-- PlayerMuseumScript (scripts/PewterCity.asm:47-113) walks it out. +-- +-- `museumEscort` had zero consumers and zero test coverage before this: a +-- future edit to the RLE tables or the preamble map could silently break +-- the walk and nothing would fail. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") + +local M = assert(loadfile("data/scripts/flavor/pewter_city.lua"))() +local esc = M.PEWTER_CITY.museumEscort +T.check(type(esc) == "table", "museumEscort is exported") +T.check(type(esc.plan) == "function", "museumEscort.plan is exported") +T.check(type(esc.guySteps) == "table", "museumEscort.guySteps is exported") + +-- RLEList_PewterMuseumGuy (engine/overworld/auto_movement.asm:199-204): +-- UP 6, LEFT 13, UP 3, LEFT 1 -- 23 steps by content, not just by count. +local wantGuySteps = {} +for _ = 1, 6 do wantGuySteps[#wantGuySteps + 1] = "up" end +for _ = 1, 13 do wantGuySteps[#wantGuySteps + 1] = "left" end +for _ = 1, 3 do wantGuySteps[#wantGuySteps + 1] = "up" end +wantGuySteps[#wantGuySteps + 1] = "left" +T.same(esc.guySteps, wantGuySteps, + "guySteps is exactly UP x6, LEFT x13, UP x3, LEFT x1") + +local function apply(x, y, dirs) + for _, d in ipairs(dirs) do + if d == "up" then y = y - 1 + elseif d == "down" then y = y + 1 + elseif d == "left" then x = x - 1 + elseif d == "right" then x = x + 1 end + end + return x, y +end + +-- PewterMuseumGuyCoords (engine/events/pewter_guys.asm:58-75): the four +-- cells adjacent to the guy's spawn (27,17), each with its own preamble. +local triggers = { { 27, 18 }, { 27, 16 }, { 26, 17 }, { 28, 17 } } +for _, c in ipairs(triggers) do + local plan = esc.plan(c[1], c[2]) + T.check(plan ~= nil, + ("(%d,%d) is a real trigger cell and must produce a plan"):format(c[1], c[2])) + T.eq(#plan.steps, 23, + ("(%d,%d): 23-step walk, same length regardless of approach side") + :format(c[1], c[2])) + T.eq(plan.guyHeadStart, 0, + ("(%d,%d): no NO_INPUT head padding in the museum preamble") + :format(c[1], c[2])) + + local px, py = apply(c[1], c[2], plan.steps) + T.check(px == 14 and py == 8, + ("(%d,%d): player's walk ends at (14,8), by the museum door") + :format(c[1], c[2])) + + local guySub = {} + for i = plan.guyHeadStart + 1, plan.guyHeadStart + #plan.steps do + guySub[#guySub + 1] = esc.guySteps[i] + end + local gx, gy = apply(27, 17, guySub) + T.check(gx == 13 and gy == 8, + ("(%d,%d): guy's walk ends at (13,8), beside the player") + :format(c[1], c[2])) +end + +-- Any cell that is not one of the four adjacent trigger cells must not +-- start the escort at all. +T.check(esc.plan(10, 10) == nil, "a non-adjacent cell returns no plan") +T.check(esc.plan(27, 17) == nil, "the guy's own cell is not a trigger either") + +T.finish("pewter_museum_escort_bug1391") diff --git a/tests/engine/pokedex_entry_layout_bug1341.lua b/tests/engine/pokedex_entry_layout_bug1341.lua new file mode 100644 index 00000000..f208bdb3 --- /dev/null +++ b/tests/engine/pokedex_entry_layout_bug1341.lua @@ -0,0 +1,95 @@ +-- The Pokedex entry page laid out its fields at the port's own invented +-- coordinates instead of the cart's, ran the whole description together on +-- one page with no trailing full stop, and never let A/B advance past page +-- one (#1341). +-- engine/menus/pokedex.asm:399, home/text.asm:245 (<PAGE>), :204 (<DEXEND>) +-- luajit tests/engine/pokedex_entry_layout_bug1341.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- stub Font: DexEntryMenu draws through Font.draw/Font.drawCode only, and +-- the real Font needs loaded page images this suite has no reason to touch. +local calls = {} +package.loaded["src.render.Font"] = { + draw = function(text, x, y) calls[#calls + 1] = { text = text, x = x, y = y } end, + drawCode = function(code, x, y) calls[#calls + 1] = { code = code, x = x, y = y } end, +} + +local DexEntryMenu = require("src.ui.DexEntryMenu") + +local function hasText(text, x, y) + for _, c in ipairs(calls) do + if c.text == text and c.x == x and c.y == y then return true end + end + return false +end +local function hasCode(code, x, y) + for _, c in ipairs(calls) do + if c.code == code and c.x == x and c.y == y then return true end + end + return false +end + +local game = { + data = { + pokemon = { + BULBASAUR = { + id = "BULBASAUR", + name = "BULBASAUR", + dex = 1, + dexEntry = { + kind = "SEED POKEMON", + heightFt = 2, heightIn = 4, weight = 69, + text = "_BulbasaurDexEntry", + }, + }, + }, + text = { + -- \f is the extractor's <PAGE> break; two pages, three lines each + _BulbasaurDexEntry = "A strange seed was\nplanted on its\nback at birth\f" + .. "The plant sprouts\nand grows with\nthis POKEMON", + }, + constants = { dexDigits = 3 }, + }, + save = { pokedex = { owned = { BULBASAUR = true } } }, +} + +local ns = DexEntryMenu.new(game, "BULBASAUR") +eq(ns.pageCount, 2, "the entry has two <PAGE>-separated pages") +eq(ns.page, 1, "starts on page 1") + +ns:draw() +check(hasText("BULBASAUR", 72, 16), "name at (72,16), not the port's old (72,8)") +check(hasText("SEED POKEMON", 72, 32), "kind at (72,32)") +check(hasText("No.001", 16, 64), "dex number under the pic, at (16,64)") +check(hasText("HT 2\226\128\178" .. "04\226\128\179", 72, 48), "HT at (72,48)") +check(hasText("WT 6.9lb", 72, 64), "WT at (72,64)") +check(hasText("A strange seed was", 8, 88), "page 1 line 1 at y=88 (row 11)") +check(hasText("planted on its", 8, 104), "page 1 line 2 at y=104") +check(hasText("back at birth", 8, 120), "page 1 line 3, unmodified: not the last page") +check(hasCode(0xEE, 144, 128), "the more-below arrow shows on a non-final page") + +-- A on a non-final page turns it, it does not close the screen +local popped, doneCalled = false, false +game.stack = { pop = function() popped = true end } +game.input = { wasPressed = function(_, b) return b == "a" end } +ns.onDone = function() doneCalled = true end +ns:update(0) +eq(ns.page, 2, "A advances to page 2 instead of closing") +check(not popped, "the screen did not pop on a non-final page") + +calls = {} +ns:draw() +check(hasText("this POKEMON.", 8, 120), "the final page's last line gets the trailing full stop") +check(not hasCode(0xEE, 144, 128), "no more-below arrow on the last page") + +-- A on the final page closes the screen +ns:update(0) +check(popped, "A on the final page pops the screen") +check(doneCalled, "onDone fires once the last page closes") + +T.finish("pokedex entry layout bug 1341") diff --git a/tests/engine/town_map_player_sprite_bug1344.lua b/tests/engine/town_map_player_sprite_bug1344.lua new file mode 100644 index 00000000..e41dea43 --- /dev/null +++ b/tests/engine/town_map_player_sprite_bug1344.lua @@ -0,0 +1,86 @@ +-- The TOWN MAP drew a blinking black square for the player instead of their +-- walk sprite, because the marker was a placeholder rectangle instead of the +-- OAM sprite the cart draws (#1344). +-- engine/items/town_map.asm:347 +-- luajit tests/engine/town_map_player_sprite_bug1344.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local TownMap = require("src.ui.TownMap") + +local function newGame() + return { + data = { + field = { + townMap = { PALLET_TOWN = { x = 1, y = 2, name = "PALLET TOWN" } }, + playerSprites = { walk = "SPRITE_RED" }, + -- no townMap.background: forces the stale-asset fallback draw path, + -- which is the one #1344's repro screenshot came from + }, + sprites = { SPRITE_RED = { image = "assets/generated/sprites/red_walk.png" } }, + maps = {}, + }, + save = {}, + overworld = { map = { id = "PALLET_TOWN" } }, + } +end + +local game = newGame() +local tm = TownMap.new(game, {}) + +eq(tm.mode, "grid", "one located entry puts the screen in grid mode") +check(tm.bg == nil, "no background.map means the stale-asset fallback draws") +check(tm.playerLoc ~= nil, "the player's map resolved to a location") +check(tm.playerSheet ~= nil, + "TownMap.new resolved the walk sheet (the fix this test guards)") + +-- capture what :draw() actually paints, without a real screen +local draws, rects = {}, {} +local realDraw, realRect = love.graphics.draw, love.graphics.rectangle +love.graphics.draw = function(img, quadOrX, x, y) + draws[#draws + 1] = { img = img, quad = quadOrX, x = x, y = y } +end +love.graphics.rectangle = function(mode, x, y, w, h) + rects[#rects + 1] = { mode = mode, x = x, y = y, w = w, h = h } +end + +tm.blink = 0 -- (0 < 20): the player marker is in its "on" blink phase +tm:draw() + +love.graphics.draw = realDraw +love.graphics.rectangle = realRect + +local function drewSprite() + for _, d in ipairs(draws) do + if d.img == tm.playerSheet and d.quad == tm.playerQuad then + return d + end + end + return nil +end + +local spriteDraw = drewSprite() +check(spriteDraw ~= nil, "the player's walk sprite was drawn, not a placeholder") +if spriteDraw then + -- engine/items/town_map.asm:449 WriteTownMapSpriteOAM's -4,-3 carry quirk + eq(spriteDraw.x, tm.playerLoc.x * 8 - 4, "sprite x is markerXY - 4") + eq(spriteDraw.y, tm.playerLoc.y * 8 - 3, "sprite y is markerXY - 3") +end + +local function blackDotAtPlayer() + for _, r in ipairs(rects) do + if r.mode == "fill" and r.w == 4 and r.h == 4 + and r.x == tm.playerLoc.x * 8 + 2 and r.y == tm.playerLoc.y * 8 + 2 then + return true + end + end + return false +end +check(not blackDotAtPlayer(), + "the old 4x4 placeholder square is not drawn once a sprite is available") + +T.finish("town map player sprite bug 1344") diff --git a/tests/gen2_wild_cooldown_bug1229_test.lua b/tests/gen2_wild_cooldown_bug1229_test.lua new file mode 100644 index 00000000..139a163f --- /dev/null +++ b/tests/gen2_wild_cooldown_bug1229_test.lua @@ -0,0 +1,116 @@ +-- The post-battle grace period, which the port never re-armed after an +-- UNSCRIPTED wild battle (#1229): World:tryWildEncounter's own +-- self:startBattle({ wild = wild }) carries no onDone, so no VM resume ever +-- ran the script's own `reloadmapafterbattle` (which is where the cart's +-- SetUpFiveStepWildEncounterCooldown lives, engine/overworld/events.asm:1158- +-- 1162), and the counter sat at zero for the very next step. What is +-- asserted here is the real World:startBattle -> onDone chain, same as +-- tests/gen2_canlose_test.lua exercises for the loss arm. +-- +-- GOLD_CACHE=".../gold" luajit tests/gen2_wild_cooldown_bug1229_test.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local S = require("tests.harness").suite("gen2 wild cooldown") +local check, eq = S.check, S.eq + +local World = require("src.world.gen2.World") +local Mon = require("src.battle.gen2.Mon") +local Screens = require("src.ui.Screens") + +local cache = os.getenv("GOLD_CACHE") +if not cache then + local home = os.getenv("HOME") or "" + cache = home .. "/Library/Application Support/LOVE/gold-dev/gold" +end +local probe = io.open(cache .. "/data/generated/pokemon.lua", "r") +if not probe then + check(true, "gold cache absent (SKIP)") + S.finish() + return +end +probe:close() + +local function loadLua(rel) return assert(loadfile(cache .. "/" .. rel))() end +local pokemon = loadLua("data/generated/pokemon.lua") +local moves = loadLua("data/generated/moves.lua") + +-- The battle screen as a registry fake, same shape gen2_canlose_test uses: +-- the real World:startBattle pushes it and parks its onDone for the test. +-- Screens.get caches by id process-wide, so a suite dofile'd earlier in +-- tests/run_tests.lua can leave a stale "Gen2BattleState" behind. +Screens.invalidate() +local battleDone +local registry = { + Gen2BattleState = { new = function(_g, opts) + battleDone = opts.onDone + return { screenId = "Gen2BattleState" } + end }, +} + +local function makeStack() + local stack = { items = {} } + function stack:push(inst) self.items[#self.items + 1] = inst end + function stack:pop() + local top = self.items[#self.items] + self.items[#self.items] = nil + return top + end + return stack +end + +local function makeWorld() + battleDone = nil + local game = { + data = { audio = {}, screens = registry, pokemon = pokemon, moves = moves }, + -- No roamer state and no map at all: World:pushBattleTransition needs + -- self.map to push a wipe and finds none, so pushBattle() runs straight + -- away, exactly like a headless battle would. World:restoreMapMusic and + -- World:battleMusicContext both already guard a nil self.map. + save = { player = { name = "GOLD", money = 3000 }, party = {} }, + stack = makeStack(), + } + local mon = Mon.new(game.data, "CYNDAQUIL", 5) + check(mon ~= nil, "the cache can build the player's starter") + game.save.party[1] = mon + local w = World.new(game) + return w, game +end + +-- ---- the unscripted path itself: World:tryWildEncounter's own call ------- +do + local w, game = makeWorld() + local wild = Mon.new(game.data, "MAGIKARP", 10) + check(wild ~= nil, "the cache can build the wild mon") + -- A grace period already partway spent, so a re-arm is the only way to + -- land back on 5. + w.wildCooldown = 2 + + check(w:startBattle({ wild = wild }), + "the unscripted wild path starts a battle") + check(battleDone ~= nil, "the battle screen is up") + + battleDone("win") + eq(w.wildCooldown, 5, + "reloadmapafterbattle's SetUpFiveStepWildEncounterCooldown re-arms " .. + "the counter (events.asm:1158-1162), even with no script waiting") +end + +-- ---- the counter itself, once re-armed: four blocked steps then a roll -- +do + local w = makeWorld() + w.wildCooldown = 5 + for step = 1, 4 do + check(w:wildCooldownStep(), + "step " .. step .. " of the grace period is still blocked") + end + check(not w:wildCooldownStep(), "the fifth step may roll") +end + +-- ---- a battle NOTHING started re-arms nothing: only startBattle's onDone - +do + local w = makeWorld() + w.wildCooldown = 0 + check(w.wildCooldown == 0, "a fresh world never re-arms on its own") +end + +S.finish() diff --git a/tests/parity_ss_anne_departure.lua b/tests/parity_ss_anne_departure.lua index b5ac20d1..f7a196d3 100644 --- a/tests/parity_ss_anne_departure.lua +++ b/tests/parity_ss_anne_departure.lua @@ -183,32 +183,24 @@ do end end check(dirsEqual(bow, { 4, 3, 2, 1 }), "the bow sails west a column a step") - -- 7 is missing because that is the block the player is stood on - check(dirsEqual(wake, { 8, 6, 5, 4, 3, 2, 1 }), + check(dirsEqual(wake, { 8, 7, 6, 5, 4, 3, 2, 1 }), "water closes in astern, stern column first") - -- EraseSSAnne leaves the player's own block alone ("south of the player - -- and won't be redrawn"), so he never stands on water on the way out - local pbx, pby = 7, 1 for _, r in ipairs(rowsOfKind(rows, "replace_block")) do - check(not (r[2] == pbx and r[3] == pby), - "the block under the player is never rewritten") check(r[2] >= 1 and r[2] <= DOCK_HULL.x1, "the slide stays inside the dock's water, off the pier column 0") end - -- she has to end up gone: every hull block bar the player's is water by - -- the last edit that touches it + -- scripts/VermilionDock.asm:182-203: the tile fill covers the whole ship, + -- the gangway block under the player included (#1211) local final = {} for _, r in ipairs(rowsOfKind(rows, "replace_block")) do final[r[2] .. "," .. r[3]] = r[4] end for bx = DOCK_HULL.x0, DOCK_HULL.x1 do for by = DOCK_HULL.y0, DOCK_HULL.y1 do - if not (bx == pbx and by == pby) then - check(WATER[final[bx .. "," .. by]], - ("hull block (%d,%d) ends as open water"):format(bx, by)) - end + check(WATER[final[bx .. "," .. by]], + ("hull block (%d,%d) ends as open water"):format(bx, by)) end end diff --git a/tests/parity_ss_anne_guard.lua b/tests/parity_ss_anne_guard.lua index 9700e9ff..8ad004c5 100644 --- a/tests/parity_ss_anne_guard.lua +++ b/tests/parity_ss_anne_guard.lua @@ -178,8 +178,8 @@ do end check(sawSurf, "departure plays Music_Surfing") check(ow._queued ~= nil, "departure queues the sail-away script") - -- #360: the surf override must NOT ride into Vermilion City, she has to - -- sail west block by block, and the block under the player stays dry + -- #360: the surf override must NOT ride into Vermilion City, and she + -- sails west block by block local kept, horns, slid, underPlayer = false, 0, 0, false for _, row in ipairs(ow._queued or {}) do if row[1] == "play_music" and row[3] and row[3].keep then kept = true end @@ -194,7 +194,8 @@ do eq(kept, false, "departure lets VERMILION_CITY's own theme take the warp") eq(horns, 2, "the horn blows before and after she sails") check(slid > 8, "she sails west block by block instead of vanishing") - eq(underPlayer, false, "the block under the player is never watered over") + -- scripts/VermilionDock.asm:182-203 (#1211) + eq(underPlayer, true, "the gangway block under the player is erased too") end -- Captain rub jingle: play_once Music_PkmnHealed sits after the rub text. diff --git a/tests/parity_true_color_ui.lua b/tests/parity_true_color_ui.lua index 0bfa5015..627cabb5 100644 --- a/tests/parity_true_color_ui.lua +++ b/tests/parity_true_color_ui.lua @@ -49,7 +49,10 @@ local dex = DexEntryMenu.new(game, "PIKACHU") check(dex.spriteTrueColor == true, "Pokedex keeps a Pokemon sprite's trueColor flag") local dexRects = uiRects(function() dex:draw() end) -local dx, dy = 8, math.max(0, 60 - dex.sprite:getHeight()) +-- engine/menus/pokedex.asm:503: the pic sits in the 7x7 window at (8,8) +local dw, dh = dex.sprite:getDimensions() +local dx = 8 + math.floor((8 - dw / 8) / 2) * 8 +local dy = 8 + (7 - dh / 8) * 8 check(#dexRects == 1 and dexRects[1].x == dx and dexRects[1].y == dy and dexRects[1].w == dex.sprite:getWidth() and dexRects[1].h == dex.sprite:getHeight(), diff --git a/tests/run_tests.lua b/tests/run_tests.lua index 6e2ad123..24b6a5fe 100644 --- a/tests/run_tests.lua +++ b/tests/run_tests.lua @@ -3559,6 +3559,7 @@ runSuites({ "tests/gen2_hof_continue_test.lua", "tests/gen2_pokecenter_stairs_test.lua", "tests/gen2_canlose_test.lua", + "tests/gen2_wild_cooldown_bug1229_test.lua", "tests/gen2_pc_screens_test.lua", "tests/gen2_badge_boosts_test.lua", "tests/gen2_held_items_test.lua", From a910b654342f7afa991fd1b36ba78df35f6782f8 Mon Sep 17 00:00:00 2001 From: bryanthaboi <bryanthaboi@theboisclub.com> Date: Sun, 16 Aug 2026 10:04:54 -0400 Subject: [PATCH 5/5] CLOSES #1231, CLOSES #1269, CLOSES #1301 --- src/battle/gen2/BgEffects.lua | 22 ++--- src/ui/gen2/BattleAnimView.lua | 70 +++++++++++---- src/ui/gen2/BattleState.lua | 72 ++++++++++++++-- src/world/gen2/MapPreview.lua | 4 +- src/world/gen2/World.lua | 12 +-- .../engine/gen2_battler_row_lift_bug1231.lua | 68 +++++++++++++++ tests/engine/gen2_map_bake_dpi.lua | 54 ++++++++++++ tests/engine/gen2_shadow_ball_bgp_bug1269.lua | 42 +++++++++ .../gen2_shadow_ball_bgp_view_bug1269.lua | 86 +++++++++++++++++++ 9 files changed, 390 insertions(+), 40 deletions(-) create mode 100644 tests/engine/gen2_battler_row_lift_bug1231.lua create mode 100644 tests/engine/gen2_map_bake_dpi.lua create mode 100644 tests/engine/gen2_shadow_ball_bgp_view_bug1269.lua diff --git a/src/battle/gen2/BgEffects.lua b/src/battle/gen2/BgEffects.lua index 6a90356b..f4b6f1a2 100644 --- a/src/battle/gen2/BgEffects.lua +++ b/src/battle/gen2/BgEffects.lua @@ -72,11 +72,11 @@ function Pool:reset() for row = 0, SCREEN_ROWS do self.lyBackup[row] = 0 end -- wBGP / wOBP0 / wOBP1, as DMG palette bytes. self.bgp, self.obp0, self.obp1 = NORMAL_PAL, NORMAL_PAL, NORMAL_PAL - -- Per-battler state the CGB paths write instead of touching wBGP: a DMG - -- shade byte the view remaps that battler's pic through, whether the pic is - -- hidden outright, and which of the six BG squares it is drawn at. + -- Per-battler state the CGB paths write instead of touching wBGP: shade + -- byte, hidden flag, lifted tile rows, and which BG square it is drawn at. self.monShade = { player = NORMAL_PAL, enemy = NORMAL_PAL } self.hidden = { player = false, enemy = false } + self.liftedRows = { player = nil, enemy = nil } self.picSize = { player = nil, enemy = nil } self.slide = { player = 0, enemy = 0 } -- wSurfWaveBGEffect: the $40-byte rolling wave Surf keeps beside the @@ -488,6 +488,7 @@ local function runPicResize(self, st, script) self.picSize[side] = step self.hidden[side] = false end + self.liftedRows[side] = nil incJt(st) elseif jt >= 1 and jt <= 2 then incJt(st) @@ -545,7 +546,7 @@ end -- The two battler-pic objects: the animation borrows the mon's own tiles as -- an OBJ so it can be moved without touching the tilemap. -local function battlerObj(self, st, objectPlayer, objectEnemy, clearRows) +local function battlerObj(self, st, objectPlayer, objectEnemy, rows) local jt = st.jt if jt == 0 then if self:flyDig(st) then @@ -562,25 +563,26 @@ local function battlerObj(self, st, objectPlayer, objectEnemy, clearRows) } elseif jt == 1 then incJt(st) - -- The rows the OBJ now covers are cleared out of the tilemap so the mon - -- is not drawn twice. - self.hidden[self:sideKey(st)] = clearRows + -- engine/battle_anims/bg_effects.asm:448-465: the rows the OBJ now covers + -- come out of the tilemap, and .five never puts them back. + self.liftedRows[self:sideKey(st)] = rows[self:sideKey(st)] elseif jt >= 2 and jt <= 4 then incJt(st) elseif jt == 5 then - self.hidden[self:sideKey(st)] = false endEffect(st) end end E.BATTLE_BG_EFFECT_BATTLEROBJ_1ROW = function(self, st) battlerObj(self, st, "BATTLE_ANIM_OBJ_PLAYERHEAD_1ROW", - "BATTLE_ANIM_OBJ_ENEMYFEET_1ROW", true) + "BATTLE_ANIM_OBJ_ENEMYFEET_1ROW", + { player = { 0, 1 }, enemy = { 6, 1 } }) end E.BATTLE_BG_EFFECT_BATTLEROBJ_2ROW = function(self, st) battlerObj(self, st, "BATTLE_ANIM_OBJ_PLAYERHEAD_2ROW", - "BATTLE_ANIM_OBJ_ENEMYFEET_2ROW", true) + "BATTLE_ANIM_OBJ_ENEMYFEET_2ROW", + { player = { 0, 2 }, enemy = { 5, 2 } }) end -- BGEffect_RapidCyclePals. On a CGB the palette is applied to ONE battler diff --git a/src/ui/gen2/BattleAnimView.lua b/src/ui/gen2/BattleAnimView.lua index 38f848dd..13991c53 100644 --- a/src/ui/gen2/BattleAnimView.lua +++ b/src/ui/gen2/BattleAnimView.lua @@ -150,6 +150,10 @@ end -- animation (most of them) skips the canvas entirely. local function needsCanvas(runner) local bg = runner.bg + -- engine/battle_anims/bg_effects.asm:448-465: a lifted battler row stays + -- out of the BG until the next pic redraw, so those frames stay baked too. + local lifted = bg.liftedRows + if lifted and (lifted.player or lifted.enemy) then return true end if bg.scx ~= 0 or bg.scy ~= 0 then return true end if not bg.lcdc then return false end if bg.lyEnd <= bg.lyStart then return false end @@ -229,13 +233,12 @@ end -- times, and the grouping is by VALUE so a table that happens to repeat costs -- nothing extra. local function bgpBands(bg) + local base = bg.bgp or GbcPalette.BGP_IDENTITY local order, bands = {}, {} for row = 0, SCREEN_H - 1 do local inWindow = row >= bg.lyStart and row < bg.lyEnd - -- Outside the window the register still reads whatever wBGP holds, which - -- for every effect that aims hLCDCPointer at rBGP is the identity. - local byte = inWindow and (bg.lyBackup[row] or GbcPalette.BGP_IDENTITY) - or GbcPalette.BGP_IDENTITY + -- Outside the window the register still reads whatever wBGP holds. + local byte = inWindow and (bg.lyBackup[row] or base) or base local band = bands[byte] if not band then band = { byte = byte, rows = {} } @@ -244,24 +247,56 @@ local function bgpBands(bg) end band.rows[#band.rows + 1] = row end - -- Identity first so the fillBackground below it happens before any blit and - -- the common band is the one drawn from the first bake. + -- The base band first so the fillBackground below it happens before any blit + -- and the common band is the one drawn from the first bake. table.sort(order, function(a, b) if a.byte == b.byte then return false end - if a.byte == GbcPalette.BGP_IDENTITY then return true end - if b.byte == GbcPalette.BGP_IDENTITY then return false end + if a.byte == base then return true end + if b.byte == base then return false end return a.rows[1] < b.rows[1] end) return order end --- Runs `drawBg` (the battle panel) and then puts it on screen through the --- animation's BG registers. Returns without a canvas when nothing is --- displacing anything, which is the common case and costs nothing. -function BattleAnimView:present(runner, drawBg) +-- engine/battle_anims/anim_commands.asm:1293 BattleAnim_SetBGPals +function BattleAnimView:panelPalettes(battle) + local list = {} + local shades = {} + for index = 1, 4 do shades[index] = GbcPalette.color(nil, index) end + list[#list + 1] = shades + local function bracket(pair) + if not (pair and pair[1] and pair[2]) then return end + list[#list + 1] = { + { 255, 255, 255 }, + { pair[1][1], pair[1][2], pair[1][3] }, + { pair[2][1], pair[2][2], pair[2][3] }, + { 0, 0, 0 }, + } + end + for _, side in ipairs({ "player", "enemy" }) do + local mon = battle and battle[side] + local colors = mon + and Palettes.monColors(self.palettes, mon.species, mon.shiny) + if colors then list[#list + 1] = colors end + end + local hpBar = self.palettes and self.palettes.hpBar + if hpBar then + bracket(hpBar.green) + bracket(hpBar.yellow) + bracket(hpBar.red) + end + bracket(self.palettes and self.palettes.expBar) + return list +end + +-- Runs `drawBg` (the battle panel) and puts it on screen through the +-- animation's BG registers; skips the canvas when nothing needs one. +function BattleAnimView:present(runner, drawBg, battle) if not (love and love.graphics) then return end local bg = runner.bg - if not needsCanvas(runner) then + local invert = bg.bgp and bg.bgp ~= GbcPalette.BGP_IDENTITY + and bg.lcdc ~= "BGP" and GbcPalette.remapShader() ~= nil + if not invert and not needsCanvas(runner) then drawBg() return end @@ -292,9 +327,10 @@ function BattleAnimView:present(runner, drawBg) self:bake(drawBg, nil) - -- A shifted scanline exposes whatever the BG map holds beside the pic, which - -- outside the two pic boxes is the blank tile. Without this the exposed - -- strip is the canvas's own transparency and every shake shows a seam. + local remapped = invert + and GbcPalette.useRemap(self:panelPalettes(battle), bg.bgp) + -- A shifted scanline exposes the blank tile beside the pic boxes; without + -- this the exposed strip is the canvas's own transparency. self:fillBackground() G.setColor(1, 1, 1, 1) -- hSCX / hSCY move the whole background; the per-scanline overrides only @@ -314,6 +350,7 @@ function BattleAnimView:present(runner, drawBg) self:blitRow(row, dx, dy) end end + if remapped then GbcPalette.clear() end -- Shaderless boot: the panel is raw grayscale, so there are no palettes to -- permute and the entry's BRIGHTNESS is the only thing left to reproduce. if bg.lcdc == "BGP" then @@ -417,5 +454,6 @@ end BattleAnimView.SCREEN_W = SCREEN_W BattleAnimView.SCREEN_H = SCREEN_H +BattleAnimView.needsCanvas = needsCanvas return BattleAnimView diff --git a/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index 67d534c8..1db75a55 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -661,7 +661,7 @@ function BattleState:drawPic(mon, back) -- the mon drawn at this frame. local scale = self:picScale(path, mon, back) if anim then - px = px + (anim.slide or 0) + if not self.liftedPass then px = px + (anim.slide or 0) end local resized = anim.size and PIC_RESIZE_TILES[anim.size] if resized then scale = scale * (resized / boxTiles) end end @@ -714,11 +714,39 @@ function BattleState:drawPic(mon, back) -- A mod-supplied pic that says it is already coloured is drawn as it is: -- pokemon.sprite's ctx.trueColor, the same flag Gen 1's Sprites.path hands -- back to its own draw site. - if colors and not trueColor and GbcPalette.available() then - GbcPalette.with(colors, body) - else - body() + local function paint() + if colors and not trueColor and GbcPalette.available() then + GbcPalette.with(colors, body) + else + body() + end end + local lifted = anim and anim.lifted + if not lifted then + paint() + return + end + -- engine/battle_anims/bg_effects.asm:448-465: the ClearBoxed band is off the BG. + local bandY = (back and BattleState.PLAYER_PIC_TILE_Y + or BattleState.ENEMY_PIC_TILE_Y) * 8 + lifted[1] * 8 + local bandH = lifted[2] * 8 + local psx, psy, psw, psh + if G.getScissor then psx, psy, psw, psh = G.getScissor() end + if self.liftedPass then + G.setScissor(0, bandY, 160, bandH) + paint() + else + if bandY > 0 then + G.setScissor(0, 0, 160, bandY) + paint() + end + local below = 144 - bandY - bandH + if below > 0 then + G.setScissor(0, bandY + bandH, 160, below) + paint() + end + end + if psx then G.setScissor(psx, psy, psw, psh) else G.setScissor() end end -- MonsterSpriteGFX (gfx/sprites.asm:82): the facing-DOWN 16x16 frame for the @@ -1137,6 +1165,7 @@ function BattleState:animPicState(side) local bg = self.anim.bg return { hidden = bg.hidden[side], + lifted = bg.liftedRows and bg.liftedRows[side] or nil, size = bg.picSize[side], slide = bg.slide[side] or 0, shade = bg.monShade[side], @@ -3436,6 +3465,36 @@ function BattleState:drawScene() end end +-- data/battle_anims/objects.asm:390-397: the lifted band rides at ABSOLUTE_X, +-- outside the scanline blit, so the attacker's SCX never moves it. +function BattleState:drawLiftedRows() + local battle = self.battle + if not battle then return end + local enemy = self:animPicState("enemy") + local player = self:animPicState("player") + local enemyLift = enemy and enemy.lifted + local playerLift = player and player.lifted + if not (enemyLift or playerLift) then return end + local G = love.graphics + if not self.liftCanvas then + self.liftCanvas = G.newCanvas(160, 144) + self.liftCanvas:setFilter("nearest", "nearest") + end + local previous = G.getCanvas() + G.setCanvas(self.liftCanvas) + G.clear(0, 0, 0, 0) + G.push() + G.origin() + self.liftedPass = true + if enemyLift then self:drawPic(battle.enemy, false) end + if playerLift then self:drawPic(battle.player, true) end + self.liftedPass = nil + G.pop() + G.setCanvas(previous) + G.setColor(1, 1, 1, 1) + G.draw(self.liftCanvas, 0, 0) +end + function BattleState:drawSceneBody() local panel = function() self:drawPanel() end if self.animView and self.slideFrame < BattleAnimView.SLIDE_FRAMES then @@ -3456,7 +3515,8 @@ function BattleState:drawSceneBody() return end if self.anim and self.animView then - self.animView:present(self.anim, panel) + self.animView:present(self.anim, panel, self.battle) + self:drawLiftedRows() self.animView:drawObjects(self.anim, self.battle) return end diff --git a/src/world/gen2/MapPreview.lua b/src/world/gen2/MapPreview.lua index 27847d45..d02411ed 100644 --- a/src/world/gen2/MapPreview.lua +++ b/src/world/gen2/MapPreview.lua @@ -6,6 +6,7 @@ local Assets = require("src.render.Assets") local BorderFill = require("src.world.gen2.BorderFill") local GbcPalette = require("src.render.GbcPalette") local Palettes = require("src.world.gen2.Palettes") +local PixelCanvas = require("src.render.PixelCanvas") local MapPreview = {} @@ -96,9 +97,8 @@ function MapPreview.bake(baker, map, daytime) local blocks = tileset.blocks local tilesPerRow = tileset.tilesPerRow or 16 local pw, ph = map.width * 32, map.height * 32 - local okCanvas, canvas = pcall(love.graphics.newCanvas, pw, ph) + local okCanvas, canvas = pcall(PixelCanvas.new, pw, ph, "nearest") if not okCanvas or not canvas then return nil end - if canvas.setFilter then canvas:setFilter("nearest", "nearest") end local quads = {} local function quadFor(tile) local q = quads[tile] diff --git a/src/world/gen2/World.lua b/src/world/gen2/World.lua index 46aca5d5..a62f677f 100644 --- a/src/world/gen2/World.lua +++ b/src/world/gen2/World.lua @@ -47,6 +47,7 @@ local NPC = require("src.world.gen2.Npc") local Party = require("src.pokemon.Party") local Permissions = require("src.world.gen2.Permissions") local Pipelines = require("src.render.Pipelines") +local PixelCanvas = require("src.render.PixelCanvas") local Player = require("src.world.gen2.Player") local Pokerus = require("src.core.gen2.Pokerus") local Roamers = require("src.core.gen2.Roamers") @@ -7612,8 +7613,9 @@ function World:bakeMapImage(map, daytime, flicker) local blocks = tileset.blocks local tilesPerRow = tileset.tilesPerRow or 16 local pw, ph = map.width * 32, map.height * 32 - local canvas = love.graphics.newCanvas(pw, ph) - canvas:setFilter("nearest", "nearest") + -- Map pixels, not the screen's: a DPI-scaled canvas bakes them non-square + -- (#208, see src/render/PixelCanvas.lua). + local canvas = PixelCanvas.new(pw, ph, "nearest") local quads = {} local function quadFor(tile) local q = quads[tile] @@ -8140,12 +8142,11 @@ function World:scrollStrip(mapDef, tileset, tile, scroll) local cached = self.scrollStrips[key] if cached ~= nil then return cached or nil end local atlas = self:atlasFor(mapDef) - local ok, canvas = pcall(love.graphics.newCanvas, 8, 8 * 8) + local ok, canvas = pcall(PixelCanvas.new, 8, 8 * 8, "nearest") if not (atlas and ok and canvas) then self.scrollStrips[key] = false return nil end - canvas:setFilter("nearest", "nearest") local perRow = tileset.tilesPerRow or 16 local sx, sy = (tile % perRow) * 8, math.floor(tile / perRow) * 8 local aw, ah = atlas:getDimensions() @@ -9945,8 +9946,7 @@ function World:drawTilted(w, h, s, gw, gh) if self.tiltCanvas and self.tiltCanvas.release then self.tiltCanvas:release() end - self.tiltCanvas = G.newCanvas(gw, gh) - self.tiltCanvas:setFilter("linear", "linear") + self.tiltCanvas = PixelCanvas.new(gw, gh, "linear") end local previous = G.getCanvas() diff --git a/tests/engine/gen2_battler_row_lift_bug1231.lua b/tests/engine/gen2_battler_row_lift_bug1231.lua new file mode 100644 index 00000000..687be2ba --- /dev/null +++ b/tests/engine/gen2_battler_row_lift_bug1231.lua @@ -0,0 +1,68 @@ +-- engine/battle_anims/bg_effects.asm:406-471 BattleBGEffect_BattlerObj_1Row + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +local T = require("tests.harness") +local BgEffects = require("src.battle.gen2.BgEffects") +local BattleAnimView = require("src.ui.gen2.BattleAnimView") + +do + local bg = BgEffects.new(nil, { battleTurn = 0 }) + bg:queue("BATTLE_BG_EFFECT_BATTLEROBJ_1ROW", 0, 0, 0) + bg:playFrame() + local spawns = bg:takeSpawns() + T.eq(spawns[1] and spawns[1].object, "BATTLE_ANIM_OBJ_ENEMYFEET_1ROW", + "player attacking: the enemy's feet row becomes an OBJ") + T.eq(spawns[1] and spawns[1].x, 16 * 8 + 4, "at the asm's fixed x") + T.eq(bg.liftedRows.enemy, nil, "the tilemap row is intact on frame one") + T.eq(BattleAnimView.needsCanvas({ bg = bg }), false, + "an intact tilemap with no scroll skips the bake canvas") + bg:playFrame() + local lifted = bg.liftedRows.enemy + T.check(lifted and lifted[1] == 6 and lifted[2] == 1, + "frame two ClearBoxes row 6 of the enemy box (hlcoord 12, 6)") + T.eq(bg.hidden.enemy, false, "the rest of the pic stays on the BG") + T.eq(BattleAnimView.needsCanvas({ bg = bg }), true, + "a lifted row keeps the panel on the bake canvas even with scx 0 and no" + .. " lcdc pointer, so drawPic's 160x144 scissor stays in canvas space") + for _ = 1, 4 do bg:playFrame() end + T.eq(bg:activeCount(), 0, ".five ends the effect") + lifted = bg.liftedRows.enemy + T.check(lifted and lifted[1] == 6 and lifted[2] == 1, + ".five never restores the row") + T.eq(BattleAnimView.needsCanvas({ bg = bg }), true, + "and the wait frames after .five stay baked as well") + bg:queue("BATTLE_BG_EFFECT_SHOW_MON", 0, 0, 0) + bg:playFrame() + T.eq(bg.liftedRows.enemy, nil, "SHOW_MON's box redraw puts the row back") + T.eq(BattleAnimView.needsCanvas({ bg = bg }), false, + "after which the plain no-canvas path returns") +end + +do + local bg = BgEffects.new(nil, { battleTurn = 1 }) + bg:queue("BATTLE_BG_EFFECT_BATTLEROBJ_2ROW", 0, 0, 0) + bg:playFrame() + local spawns = bg:takeSpawns() + T.eq(spawns[1] and spawns[1].object, "BATTLE_ANIM_OBJ_PLAYERHEAD_2ROW", + "enemy attacking: the player's head rows become an OBJ") + T.eq(spawns[1] and spawns[1].x, 6 * 8, "at the asm's fixed x") + bg:playFrame() + local lifted = bg.liftedRows.player + T.check(lifted and lifted[1] == 0 and lifted[2] == 2, + "rows 0-1 of the player box (hlcoord 2, 6, two rows)") + T.eq(bg.liftedRows.enemy, nil, "the attacker keeps its own rows") +end + +do + local bg = BgEffects.new(nil, { battleTurn = 0, flying = { enemy = true } }) + bg:queue("BATTLE_BG_EFFECT_BATTLEROBJ_1ROW", 0, 0, 0) + bg:playFrame() + T.eq(#bg:takeSpawns(), 0, "a flying target spawns nothing") + T.eq(bg:activeCount(), 0, "and the effect ends at once") + T.eq(bg.liftedRows.enemy, nil, "with no row lifted") +end + +T.finish("gen2 battler row lift bug 1231") diff --git a/tests/engine/gen2_map_bake_dpi.lua b/tests/engine/gen2_map_bake_dpi.lua new file mode 100644 index 00000000..b53c2aba --- /dev/null +++ b/tests/engine/gen2_map_bake_dpi.lua @@ -0,0 +1,54 @@ +-- Gen 2 map bakes take dpiscale 1 so map pixels stay square LCD pixels +-- (#208 #1301, constants/hardware.inc:932; see src/render/PixelCanvas.lua). +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +local g = love.graphics +local seen = {} +local realNewCanvas = g.newCanvas +g.newCanvas = function(w, h, settings) + seen[#seen + 1] = { w = w, h = h, dpiscale = settings and settings.dpiscale } + local c = realNewCanvas(w, h) + c.renderTo = function(_, fn) fn() end + c.setFilter = function() end + return c +end + +local World = require("src.world.gen2.World") +local MapPreview = require("src.world.gen2.MapPreview") + +local atlas = { + getDimensions = function() return 128, 128 end, + setFilter = function() end, +} +local tileset = { blocks = { [1] = {} }, tilesPerRow = 16 } +local map = { def = { tileset = "TILESET_JOHTO" }, width = 20, height = 18, + blocks = {}, borderBlock = 0 } + +local world = setmetatable({ + atlasCache = {}, + atlasFor = function() return atlas, tileset end, +}, { __index = World }) + +World.bakeMapImage(world, map, nil, nil) +local bakeCount = #seen +T.check(bakeCount >= 1, "the map bake allocated a canvas") + +World.scrollStrip(world, map.def, tileset, 3, { h = 1, v = 0 }) +T.check(#seen > bakeCount, "the scroll strip allocated a canvas") +local stripCount = #seen + +local origAtlasFor = MapPreview.atlasFor +MapPreview.atlasFor = function() return atlas, tileset end +MapPreview.bake({ tilesets = {}, atlasCache = {}, mapImages = {} }, map, "DAY") +MapPreview.atlasFor = origAtlasFor +T.check(#seen > stripCount, "the save-editor bake allocated a canvas") + +for i, c in ipairs(seen) do + T.eq(c.dpiscale, 1, + ("canvas %d (%dx%d) is allocated at dpiscale 1"):format(i, c.w, c.h)) +end + +g.newCanvas = realNewCanvas +T.finish("gen2 map bake dpi") diff --git a/tests/engine/gen2_shadow_ball_bgp_bug1269.lua b/tests/engine/gen2_shadow_ball_bgp_bug1269.lua index e1287c1b..82b07123 100644 --- a/tests/engine/gen2_shadow_ball_bgp_bug1269.lua +++ b/tests/engine/gen2_shadow_ball_bgp_bug1269.lua @@ -44,4 +44,46 @@ do "the identity byte returns the palette untouched") end +-- engine/battle_anims/anim_commands.asm:1293 BattleAnim_SetBGPals +do + local BattleAnimView = require("src.ui.gen2.BattleAnimView") + local palettes = { + pokemon = { + [6] = { normal = { { 200, 100, 50 }, { 80, 40, 20 } } }, + [25] = { normal = { { 230, 200, 40 }, { 150, 90, 20 } } }, + }, + hpBar = { + green = { { 100, 220, 100 }, { 30, 160, 30 } }, + yellow = { { 230, 220, 90 }, { 180, 150, 20 } }, + red = { { 230, 100, 90 }, { 180, 30, 20 } }, + }, + expBar = { { 120, 140, 230 }, { 40, 60, 160 } }, + } + local view = BattleAnimView.new({}, palettes) + local battle = { player = { species = 6 }, enemy = { species = 25 } } + local list = view:panelPalettes(battle) + T.eq(#list, 7, "shades + two mons + three hp bars + exp bar") + local src, dst, count, ambiguous = GbcPalette.remapTable(list, 0x1b) + T.check(count > 0 and count <= GbcPalette.REMAP_MAX, + "the table fits the shader array") + T.eq(ambiguous, 0, "no colour maps two ways") + local function mapped(from) + for i = 1, count do + if src[i][1] == from[1] and src[i][2] == from[2] + and src[i][3] == from[3] then + return dst[i] + end + end + end + local black = mapped({ 255, 255, 255 }) + T.check(black and black[1] == 0 and black[2] == 0 and black[3] == 0, + "white inverts to black") + local white = mapped({ 0, 0, 0 }) + T.check(white and white[1] == 255 and white[2] == 255 and white[3] == 255, + "black inverts to white") + local mid = mapped({ 200, 100, 50 }) + T.check(mid and mid[1] == 80 and mid[2] == 40 and mid[3] == 20, + "the mon's colour 1 shows its colour 2") +end + T.finish("gen2 shadow ball bgp bug 1269") diff --git a/tests/engine/gen2_shadow_ball_bgp_view_bug1269.lua b/tests/engine/gen2_shadow_ball_bgp_view_bug1269.lua new file mode 100644 index 00000000..132f006f --- /dev/null +++ b/tests/engine/gen2_shadow_ball_bgp_view_bug1269.lua @@ -0,0 +1,86 @@ +-- engine/battle_anims/anim_commands.asm:1293 BattleAnim_SetBGPals +-- +-- gen2_shadow_ball_bgp_bug1269.lua proves the runner lands bg.bgp and that +-- panelPalettes/remapTable would invert correctly; it never calls +-- BattleAnimView:present, which is where #1269 actually lived (the byte +-- was landed but nothing read it). This suite drives present() itself and +-- watches the shader binding around the backdrop draw. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = require("tests.love_stub") + +-- A remap shader that never touches the GPU: present() only needs +-- love.graphics.newShader to succeed so GbcPalette.remapShader() is +-- non-nil, which is the gate `present` checks before it will bake+remap. +local sentShader = { calls = {} } +function sentShader:send(name, ...) self.calls[#self.calls + 1] = name end +love.graphics.newShader = function() return sentShader end + +-- The stub's stand-in Quad has no setViewport/getViewport, which blitRow +-- (the scanline blit present() drives 144 times a frame) needs; the real +-- love.graphics.Quad has both. +love.graphics.newQuad = function(x, y, w, h) + local q = { x = x, y = y, w = w, h = h } + function q:setViewport(x2, y2, w2, h2) self.x, self.y, self.w, self.h = x2, y2, w2, h2 end + function q:getViewport() return self.x, self.y, self.w, self.h end + return q +end + +local T = require("tests.harness") +local GbcPalette = require("src.render.GbcPalette") +local BattleAnimView = require("src.ui.gen2.BattleAnimView") + +local shaderDuringFill = "unset" +local realRectangle = love.graphics.rectangle +love.graphics.rectangle = function(...) + if shaderDuringFill == "unset" then + shaderDuringFill = love.graphics.getShader() + end + return realRectangle(...) +end + +local view = BattleAnimView.new({}, nil) +-- data/moves/animations.asm:4509 BattleAnim_ShadowBall's anim_bgp $1b, with +-- no scroll and no rBGP window queued, is exactly the frame that used to +-- fall through the old `needsCanvas`-only early-out untouched. +local runner = { + bg = { + bgp = 0x1b, + lcdc = nil, + scx = 0, + scy = 0, + lyStart = 0, + lyEnd = 0, + lyBackup = {}, + }, +} + +T.check(love.graphics.getShader() == nil, "no shader bound before present") + +view:present(runner, function() end, nil) + +T.check(shaderDuringFill == sentShader, + "the panel backdrop is drawn through the remap shader, not plainly") +T.check(love.graphics.getShader() == nil, + "the shader is unbound again once present returns, so drawObjects is unaffected") + +local sawRemapSend = false +for _, name in ipairs(sentShader.calls) do + if name == "remapSrc" then sawRemapSend = true end +end +T.check(sawRemapSend, "GbcPalette.useRemap actually sent a remap table, not just bound the shader") + +-- The identity byte must take the plain path: no bake, no shader, ever. +shaderDuringFill = "unset" +local identityRunner = { + bg = { bgp = GbcPalette.BGP_IDENTITY, lcdc = nil, scx = 0, scy = 0, + lyStart = 0, lyEnd = 0, lyBackup = {} }, +} +local plainDrawCalled = false +view:present(identityRunner, function() plainDrawCalled = true end, nil) +T.check(plainDrawCalled, "identity rBGP takes the plain drawBg() path") +T.check(shaderDuringFill == "unset", + "identity rBGP never touches the remap shader") + +T.finish("gen2 shadow ball bgp view bug 1269")