-- Smoke: the whole Gold boot chain, with no driver shortcut. -- -- POKEPORT_GAME=gold POKEPORT_BOOT_CINEMA=1 \ -- POKEPORT_DRIVER=tests/drivers/gold_boot_smoke.lua love . -- -- copyright -> GameFreak -> GS intro -> title -> intro menu -> NEW GAME -> -- Oak speech -> name pick -> naming screen -> the bedroom. Every step is -- asserted by the class of the state on the stack, so a broken hand-off names -- the screen it stalled on instead of just hanging. local U = require("tests.drivers.util") local CopyrightSplash = require("src.ui.gen2.CopyrightSplash") local GameFreakPresents = require("src.ui.gen2.GameFreakPresents") local GoldSilverIntro = require("src.ui.gen2.GoldSilverIntro") local InitClock = require("src.ui.gen2.InitClock") local MainMenu = require("src.ui.gen2.MainMenu") local NamePick = require("src.ui.gen2.NamePick") local NamingScreen = require("src.ui.gen2.NamingScreen") local OakSpeech = require("src.ui.gen2.OakSpeech") local OptionsMenu = require("src.ui.gen2.OptionsMenu") local StartMenu = require("src.ui.gen2.StartMenu") local TitleState = require("src.ui.gen2.TitleState") return function(game) local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-boot" local function top() return game.stack:top() end local function isA(class) local state = top() return state ~= nil and getmetatable(state) == class end local function tap(button, frames) game.input.pressQueue[#game.input.pressQueue + 1] = button game.input.state[button] = true U.wait(2) game.input.state[button] = false U.wait(frames or 4) end -- Wait until `predicate` holds, or fail naming what was on screen instead. local function waitFor(label, predicate, frames) for _ = 1, frames or 900 do if predicate() then return end U.wait(1) end local state = top() error(("stalled waiting for %s (top is %s)"):format( label, tostring(state))) end -- Skip through anything that just waits for a button. local function press(times, button) for _ = 1, times or 1 do tap(button or "a", 3) end end -- The driver env normally boots straight to the world; this one asked for the -- cinema, so Game2 should have started at the copyright splash. U.wait(10) assert(isA(CopyrightSplash), "boot did not start at the copyright splash (top " .. tostring(top()) .. ")") U.shot(game, out .. "/01-copyright.png") waitFor("GameFreak presents", function() return isA(GameFreakPresents) end) U.shot(game, out .. "/02-gamefreak.png") waitFor("the GS intro", function() return isA(GoldSilverIntro) end) U.wait(240) U.shot(game, out .. "/03-intro.png") tap("start") -- any button skips the movie waitFor("the title screen", function() return isA(TitleState) end) U.wait(60) U.shot(game, out .. "/04-title.png") press(3, "start") waitFor("the intro menu", function() return isA(MainMenu) end) U.shot(game, out .. "/05-mainmenu.png") -- OPTION and back, so the menu's own hand-off is exercised too. Found by -- value, not by position: the port's EXIT GAME row sits after OPTION, so -- "the last item" is the one that quits the game. local menu = top() for i, item in ipairs(menu.list.items) do if item.value == "option" then menu.list.index = i end end tap("a") waitFor("the options screen", function() return isA(OptionsMenu) end) U.shot(game, out .. "/06-options.png") tap("b") waitFor("the intro menu again", function() return isA(MainMenu) end) -- NEW GAME. menu = top() for i, item in ipairs(menu.list.items) do if item.value == "new" then menu.list.index = i end end tap("a") -- `farcall InitClock` opens OakSpeech, so the clock screen is what NEW GAME -- lands on and Oak is underneath it. waitFor("the clock screen", function() return isA(InitClock) end, 300) U.shot(game, out .. "/06b-initclock.png") for _ = 1, 60 do if isA(OakSpeech) then break end tap("a", 2) end waitFor("the Oak speech", function() return isA(OakSpeech) end, 300) -- Page through Oak until the name picker appears. for _ = 1, 400 do if isA(NamePick) then break end tap("a", 2) end assert(isA(NamePick), "Oak speech never reached the name picker") -- NamePlayer walks the player pic across before the menu box goes up, and -- that walk is a blocking DelayFrame loop on the cart -- so both the shot -- and the first button press have to wait it out. waitFor("the name menu to slide in", function() return top().slide == nil end, 120) U.shot(game, out .. "/07-namepick.png") -- NEW NAME opens the real Gen 2 keyboard. local pick = top() pick.cursor = 1 -- "NEW NAME" tap("a") waitFor("the naming screen", function() return isA(NamingScreen) end) local naming = top() -- Type "AB": A is at (0,0), B at (1,0). tap("a") tap("right") tap("a") assert(naming.text == "AB", "typed name is " .. tostring(naming.text) .. ", expected AB") U.shot(game, out .. "/08-naming.png") -- END: bottom row, third target. naming.row = naming:bottomRow() naming.col = 6 tap("a") -- Back into Oak for the last text page and the shrink, then the world. This -- one has to keep pressing A: the remaining pages are text boxes waiting on a -- button, so a passive wait would sit there forever. for _ = 1, 500 do if game.phase == "play" and game.world and game.world.map then break end tap("a", 2) end assert(game.phase == "play" and game.world and game.world.map, "never reached the overworld (top is " .. tostring(top()) .. ")") assert(game.save.player.name == "AB", "player name is " .. tostring(game.save.player.name)) assert(game.world.map.id == "PLAYERS_HOUSE_2F", "new game started on " .. tostring(game.world.map.id) .. ", expected the bedroom") U.wait(20) U.shot(game, out .. "/09-bedroom.png") -- START opens the Gen 2 start menu from the overworld. tap("start") waitFor("the start menu", function() return isA(StartMenu) end) U.shot(game, out .. "/10-startmenu.png") tap("b") waitFor("the overworld again", function() return top() == nil end) print("[driver] PASS gold boot chain in " .. out) end