Files
gen1recomp/tests/game3_teachy_screen_test.lua
2026-09-23 16:34:15 -04:00

599 lines
23 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/teachy_tv.c:450 TeachyTvMainCallback
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function eq(a, b, msg)
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
end
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("items/pack.lua")
if not cacheRoot then
print("[skip] game3_teachy_screen: " .. tostring(Cache.reason))
os.exit(0)
end
print("[info] FireRed cache at " .. cacheRoot)
local TeachyTv = require("src.core.game3.teachy_tv")
local Ui = require("src.ui.game3.teachy_tv")
local Bag = require("src.core.game3.bag")
local Stack = require("src.ui.game3.stack")
local BagMenu = require("src.ui.game3.bag_menu")
local S = TeachyTv.SCRIPT
local T = TeachyTv.TIMING
local function press(key)
return {
wasPressed = function(_, k) return k == key end,
isDown = function() return false end,
}
end
local function newSession()
return { modData = {}, bag = Bag.new(), party = {}, name = "RED" }
end
local Fade = require("src.ui.game3.fade")
local Audio = require("src.core.game3.audio")
local function settleFade()
for _ = 1, 40 do
Fade.tick(1 / 60)
Ui.update(1 / 60)
end
end
local function openWith(session)
Stack.clear()
Ui.show(session, session.bag)
settleFade()
end
local function ticks(n)
for _ = 1, n do Ui.update(1 / 60) end
end
local function runUntil(name, budget)
for _ = 1, budget or 4000 do
if Ui.stepName() == name then return true end
Ui.update(1 / 60)
end
return Ui.stepName() == name
end
print("[test] 1. pokefirered/src/teachy_tv.c:553 the list rows and the cursor")
do
local s = newSession()
openWith(s)
check(Ui.isOpen(), "the TV screen is open")
eq(Ui.state, "list", "it opens on the lesson list")
eq(#Ui.rows(), 5, "five rows without a TM CASE")
eq(Ui.cursor, 1, "the cursor starts on the battle lesson")
Ui.handleInput(press("down"))
Ui.handleInput(press("down"))
eq(Ui.cursor, 3, "down twice walks to the matchups lesson")
Ui.handleInput(press("up"))
eq(Ui.cursor, 2, "up walks back")
local res = TeachyTv.resources(s)
-- pokefirered/src/teachy_tv.c:722 ListMenuGetScrollAndRow
eq(res.selectedRow, 1, "the controller block tracks the row")
for _ = 1, 10 do Ui.handleInput(press("down")) end
eq(Ui.cursor, 5, "the cursor stops on CANCEL")
-- pokefirered/src/teachy_tv.c:734 case -2
Ui.handleInput(press("a"))
-- pokefirered/src/teachy_tv.c:689 TeachyTvQuitBeginFade
check(Ui.isOpen() and Ui.closing, "A on CANCEL starts the quit fade")
settleFade()
check(not Ui.isOpen(), "A on CANCEL closes the TV once the fade ends")
openWith(s)
Ui.handleInput(press("b"))
settleFade()
check(not Ui.isOpen(), "B closes the TV")
Bag.add(s.bag, TeachyTv.ITEM_TM_CASE, 1)
openWith(s)
eq(#Ui.rows(), 7, "six lessons plus CANCEL with a TM CASE")
Ui.handleInput(press("b"))
end
print("[test] 2. pokefirered/src/teachy_tv.c:272 the cluster runs the lesson")
do
local s = newSession()
openWith(s)
for _ = 1, 3 do Ui.handleInput(press("down")) end
eq(Ui.cursor, 4, "the cursor is on the catching lesson")
Ui.handleInput(press("a"))
eq(Ui.state, "lesson", "A starts the lesson")
eq(TeachyTv.whichScript(s), S.CATCHING, "whichScript is TTVSCR_CATCHING")
eq(Ui.stepName(), "transition_render_bg2", "the cluster starts on the transition")
check(#Ui.pages() == 0, "nothing is printed yet")
-- pokefirered/src/teachy_tv.c:759 data[2] > 63
ticks(T.TITLE - 1)
check(not Ui.title, "the title card is not up on frame 63")
check(Ui.static ~= nil, "the static runs under the transition")
ticks(1)
check(Ui.title, "the title card is up on frame 64")
-- pokefirered/src/teachy_tv.c:761 CopyToBgTilemapBufferRect_ChangePalette
check(Ui.static == nil, "the title map replaces the static on BG2")
eq(Ui.stepName(), "clear_bg2", "the cluster moved to the clear step")
check(Ui.hostVisible, "the POKé DUDE is on screen")
eq(Ui.hostX, T.DUDE_X_START, "he starts at x=8")
-- pokefirered/src/teachy_tv.c:773 data[2] == 134
ticks(T.CLEAR)
check(not Ui.title, "the title card is cleared after 134 frames")
eq(Ui.stepName(), "npc_move_and_setup_text_printer", "then the dude walks in")
-- pokefirered/src/teachy_tv.c:786 data[2] != 35
ticks(T.NPC_WAIT)
eq(Ui.hostX, T.DUDE_X_START, "after 35 ticks he has not moved yet")
ticks(1)
eq(Ui.hostX, T.DUDE_X_START + 1, "tick 36 is his first step")
-- pokefirered/src/teachy_tv.c:789 x2 == 0x78
ticks(T.DUDE_X_END - T.DUDE_X_START)
eq(Ui.hostX, T.DUDE_X_END, "he stops at x=0x78")
eq(Ui.phase, "hello", "and says hello")
eq(#Ui.pages(), #TeachyTv.pagesOf(TeachyTv.HELLO), "gTeachyTvText_PokedudeSaysHello is printing")
check(Ui.waitingForKey(), "the prompt is up on a \\p page")
for _ = 1, #Ui.pages() - 1 do Ui.handleInput(press("a")) end
check(not Ui.printerActive(), "the hello printer is done")
ticks(4)
eq(Ui.phase, "intro", "the intro text printer took over")
eq(#Ui.pages(), #TeachyTv.introPages(S.CATCHING), "CatchingScript1 is printing")
eq(Ui.page, 1, "on page 1")
Ui.handleInput(press("a"))
eq(Ui.page, 2, "A pages the body")
for _ = 1, #Ui.pages() - 2 do Ui.handleInput(press("a")) end
ticks(2)
-- pokefirered/src/teachy_tv.c:936 TTVcmd_EraseTextWindowIfKeyPressed
eq(Ui.stepName(), "erase_text_window_if_key_pressed", "the cluster waits for a key press")
check(Ui.waitingForKey(), "the prompt stays up while it waits")
check(#Ui.pages() > 0, "the last page is still on screen")
Ui.handleInput(press("a"))
eq(#Ui.pages(), 0, "the key press erased the window")
eq(Ui.stepName(), "start_anim_npc_walk_into_grass", "and the dude walks into the grass")
ticks(1)
eq(Ui.hostFacing, "up", "he faces up")
eq(Ui.stepName(), "dude_move_up", "moving up")
ticks(T.MOVE_UP)
eq(Ui.stepName(), "dude_move_right", "then right")
ticks(T.MOVE_RIGHT)
-- pokefirered/src/teachy_tv.c:1069 TTVcmd_TaskBattleOrFadeByOptionChosen
eq(Ui.stepName(), "battle_or_fade", "then the demonstration")
check(TeachyTv.endsInBattle(S.CATCHING), "the catching lesson ends in a battle")
local handed
TeachyTv.onDemonstration = function(_, script, opts)
handed = { script = script, transition = opts.transition }
-- pokefirered/include/constants/songs.h:306 MUS_VS_WILD
Audio.playSong(298)
opts.onDone(1)
return true
end
ticks(1)
TeachyTv.onDemonstration = nil
eq(handed and handed.script, S.CATCHING, "the catching battle was handed off")
eq(handed and handed.transition, TeachyTv.TRANSITION.SLICE, "behind B_TRANSITION_SLICE")
-- pokefirered/src/teachy_tv.c:1100 sWhereToReturnToFromBattle
eq(Ui.stepName(), "text_printer_outro", "the battle resumes the cluster at step 12")
-- pokefirered/src/teachy_tv.c:1214 PlayNewMapMusic(MUS_FOLLOW_ME)
eq(Audio.currentSong() and Audio.currentSong().id, 272, "MUS_FOLLOW_ME plays again after the battle")
-- pokefirered/src/teachy_tv.c:660 ChangeBgX(3, 0x3000, 1)
eq(Ui.bg3X, 32, "BG3 X is reset then moved 48 px right")
eq(Ui.bg3Y, -8, "BG3 Y is reset then moved 48 px up")
eq(Ui.grassLo, 3, "grassAnimCounterLo is 0 + 3")
eq(Ui.grassHi, 0, "grassAnimCounterHi is 3 - 3")
ticks(2)
eq(Ui.phase, "outro", "CatchingScript2 is printing")
for _ = 1, #Ui.pages() - 1 do Ui.handleInput(press("a")) end
ticks(2)
eq(Ui.stepName(), "erase_text_window_if_key_pressed", "the outro waits for a key press too")
Ui.handleInput(press("a"))
eq(Ui.stepName(), "dude_turn_left", "then he turns left")
ticks(1)
eq(Ui.stepName(), "dude_move_left", "and walks off")
ticks(T.DUDE_X_END - T.DUDE_X_START + 1)
eq(Ui.stepName(), "render_and_remove_bg1_end_graphic", "the end graphic comes up")
ticks(1)
check(Ui.endCard, "the end card is showing")
ticks(T.END_GRAPHIC - 1)
check(not Ui.endCard, "the end card is gone after 127 frames")
eq(Ui.stepName(), "end", "the cluster is on TTVcmd_End")
check(Ui.static == nil, "no static while the lesson plays")
ticks(T.END)
eq(Ui.state, "list", "the lesson returns to the list")
-- pokefirered/src/teachy_tv.c:1048 TeachyTvBg2AnimController
check(Ui.static ~= nil, "TTVcmd_End brings the static back")
check(Ui.isOpen(), "the TV is still open")
check(TeachyTv.hasWatched(s, S.CATCHING), "the lesson is marked watched")
Ui.handleInput(press("b"))
end
print("[test] 3. pokefirered/src/teachy_tv.c:808 B aborts back to the list")
do
local s = newSession()
openWith(s)
Ui.handleInput(press("a"))
eq(Ui.state, "lesson", "the battle lesson started")
ticks(T.TITLE + 10)
Ui.handleInput(press("b"))
eq(Ui.state, "lesson", "B runs TTVcmd_End rather than snapping back")
eq(Ui.stepName(), "end", "the cluster jumped to the end command")
check(not Ui.hostVisible, "the dude is hidden")
ticks(T.END)
eq(Ui.state, "list", "and 64 frames later the list is back")
check(not TeachyTv.hasWatched(s, S.BATTLE), "an aborted lesson is not watched")
Ui.handleInput(press("b"))
end
print("[test] 4. pokefirered/src/teachy_tv.c:526 the art missing fallback")
do
Ui.reloadAssets()
local path = Ui.artPath("screen")
check(path:match("teachy_tv/screen%.rgba$") ~= nil, "the border art path is " .. path)
check(Ui.titleArt() == nil, "no title art without the importer key")
check(Ui.chrome() == nil, "no border art means the plain chrome draws")
check(Ui.bg3Art() == nil, "no BG3 map art either")
-- pokefirered/src/teachy_tv.c:122
check(Ui.chromeCutOut() == false, "an absent border is never treated as a cut out")
local f = io.open(path, "rb")
if f then
local bytes = f:read("*a")
f:close()
local at = (80 * 240 + 120) * 4 + 4
print(string.format("[info] the cache already carries %s, centre alpha %s",
path, tostring(bytes and #bytes >= at and bytes:byte(at))))
else
print("[info] no " .. path .. " in this cache yet, the fallback path is the live one")
end
end
print("[test] 5. pokefirered/src/item_menu.c:2162 the pokedude bag")
do
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TM_CASE, 1)
Bag.add(s.bag, 13, 3)
Bag.add(s.bag, 4, 2)
s.registeredItem = nil
openWith(s)
for _ = 1, 5 do Ui.handleInput(press("down")) end
local row = Ui.rows()[Ui.cursor]
eq(row and row.index, S.REGISTER, "the cursor is on the register lesson")
Ui.handleInput(press("a"))
check(runUntil("idle_if_text_printer_active", 800), "the dude walked in and said hello")
eq(Ui.phase, "hello", "gTeachyTvText_PokedudeSaysHello is printing")
for _ = 1, #Ui.pages() - 1 do Ui.handleInput(press("a")) end
ticks(4)
eq(Ui.phase, "intro", "RegisterScript1 took over")
for _ = 1, #Ui.pages() - 1 do Ui.handleInput(press("a")) end
ticks(2)
eq(Ui.stepName(), "erase_text_window_if_key_pressed", "the intro waits for a key press")
Ui.handleInput(press("a"))
-- pokefirered/src/teachy_tv.c:364 sTMsScript skips the grass walk
eq(Ui.stepName(), "battle_or_fade", "the bag cluster goes straight to the hand off")
ticks(1)
-- pokefirered/src/item_menu.c:2166 AddBagItem
check(Ui.suspended, "the TV waits while the bag runs")
check(BagMenu.isOpen(), "the pokedude bag is open")
eq(Stack.top() and Stack.top().id, "teachy_pokedude_bag", "the demo drives it")
eq(Bag.get(s.bag, 13), 1, "one POTION, not the player's three")
eq(Bag.get(s.bag, 14), 1, "one ANTIDOTE")
eq(Bag.get(s.bag, 4), 5, "five POKé BALLS")
eq(Bag.get(s.bag, 3), 1, "one GREAT BALL")
eq(Bag.get(s.bag, 8), 1, "one NEST BALL")
eq(Bag.get(s.bag, TeachyTv.ITEM_TEACHY_TV), 1, "the TEACHY TV")
local demo = Ui.bagDemo
for _ = 1, 500 do demo.update(1 / 60) end
-- pokefirered/src/item_menu.c:2232 gSaveBlock1Ptr->registeredItem
eq(s.registeredItem, TeachyTv.ITEM_TEACHY_TV, "the demo registered the TEACHY TV")
for _ = 1, 400 do demo.update(1 / 60) end
check(demo.finished, "the demo ran to its exit step")
check(not BagMenu.isOpen(), "the pokedude bag closed")
-- pokefirered/src/item_menu.c:2082 RestorePlayerBag
eq(Bag.get(s.bag, 13), 3, "the player's three POTIONS came back")
eq(Bag.get(s.bag, 4), 2, "the player's two POKé BALLS came back")
eq(Bag.get(s.bag, 14), 0, "the fake ANTIDOTE is gone")
eq(Bag.get(s.bag, 3), 0, "the fake GREAT BALL is gone")
eq(s.registeredItem, nil, "the registration was a demonstration only")
eq(Bag.get(s.bag, TeachyTv.ITEM_TM_CASE), 1, "the player's TM CASE is back")
-- pokefirered/src/teachy_tv.c:1100 sWhereToReturnToFromBattle
eq(Ui.state, "lesson", "the TV resumed the lesson")
eq(Ui.stepName(), "text_printer_outro", "at step 9, the outro")
check(not Ui.suspended, "and it is ticking again")
ticks(2)
eq(Ui.phase, "outro", "RegisterScript2 is printing")
Stack.clear()
Ui.open = false
end
print("[test] 6. pokefirered/src/item_menu.c:2061 BackUpPlayerBag keeps the bag intact")
do
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TEACHY_TV, 1)
-- pokefirered/include/constants/items.h:300 ITEM_TM01
Bag.add(s.bag, 289, 1)
-- pokefirered/include/constants/items.h:143
Bag.add(s.bag, 139, 2)
Bag.add(s.bag, 13, 3)
eq(Bag.get(s.bag, TeachyTv.ITEM_TM_CASE), 1, "the TM CASE rides the TM pocket")
local pouch = require("src.core.game3.items_data").ITEM_BERRY_POUCH
eq(Bag.get(s.bag, pouch), 1, "and the BERRY POUCH rides the berry pocket")
TeachyTv.initPokedudeBag(s, S.REGISTER)
-- pokefirered/src/item_menu.c:2167 AddBagItem(ITEM_TM_CASE, 1)
eq(Bag.get(s.bag, TeachyTv.ITEM_TM_CASE), 1, "the pokedude bag holds exactly one TM CASE")
eq(Bag.get(s.bag, pouch), 0, "and no BERRY POUCH")
eq(Bag.get(s.bag, 13), 1, "one POTION, not the player's three")
eq(Bag.get(s.bag, 289), 1, "the player's TM stays in the TM pocket")
-- pokefirered/src/item_menu.c:2082 RestorePlayerBag
TeachyTv.restorePlayerBag(s)
eq(Bag.get(s.bag, TeachyTv.ITEM_TM_CASE), 1, "one TM CASE after the restore")
eq(Bag.get(s.bag, pouch), 1, "one BERRY POUCH after the restore")
eq(Bag.get(s.bag, 13), 3, "the player's three POTIONS")
eq(Bag.get(s.bag, 14), 0, "and none of the pokedude's items")
end
print("[test] 7. pokefirered/src/item_menu.c:2069 the bag pocket and cursor survive the demo")
do
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TEACHY_TV, 1)
Bag.add(s.bag, TeachyTv.ITEM_TM_CASE, 1)
for id = 13, 22 do Bag.add(s.bag, id, 2) end
Stack.clear()
BagMenu.show(s.bag, { session = s, pocket = "ITEMS" })
BagMenu.settle()
for _ = 1, 4 do BagMenu.handleInput(press("down")) end
local wantPocket, wantCursor = BagMenu.pocketIdx, BagMenu.cursor
check(wantCursor > 1, "the player left the ITEMS cursor on row " .. tostring(wantCursor))
BagMenu.close()
openWith(s)
for _ = 1, 5 do Ui.handleInput(press("down")) end
eq(Ui.rows()[Ui.cursor].index, S.REGISTER, "the register lesson is selected")
Ui.handleInput(press("a"))
for _ = 1, 3000 do
if Ui.suspended then break end
Ui.update(1 / 60)
Ui.handleInput(press("a"))
end
check(BagMenu.isOpen(), "the pokedude bag opened")
-- pokefirered/src/item_menu.c:2079 ResetBagCursorPositions
eq(BagMenu.cursor, 1, "the pokedude bag starts on row 1")
for _ = 1, 900 do Ui.bagDemo.update(1 / 60) end
check(Ui.bagDemo.finished, "the demo ran out")
BagMenu.show(s.bag, { session = s })
-- pokefirered/src/item_menu.c:2089 gBagMenuState.pocket = sBackupPlayerBag->pocket
eq(BagMenu.pocketIdx, wantPocket, "the player's pocket came back")
eq(BagMenu.cursor, wantCursor, "and the player's cursor")
BagMenu.close()
Stack.clear()
Ui.open = false
end
print("[test] 8. pokefirered/src/teachy_tv.c:723 SELECT only quits when the bag is not under")
do
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TEACHY_TV, 1)
Stack.clear()
BagMenu.show(s.bag, { session = s })
Ui.show(s, s.bag)
settleFade()
Ui.handleInput(press("select"))
settleFade()
check(Ui.isOpen(), "SELECT is ignored when CB2_BagMenuFromStartMenu is the callback")
Ui.handleInput(press("b"))
settleFade()
BagMenu.close()
Stack.clear()
Ui.show(s, s.bag)
settleFade()
Ui.handleInput(press("select"))
settleFade()
check(not Ui.isOpen(), "SELECT quits when the TV came from the field")
Stack.clear()
end
print("[test] 9. pokefirered/src/teachy_tv.c:957 the grass walk scrolls BG3")
do
local s = newSession()
openWith(s)
Ui.handleInput(press("a"))
for _ = 1, 4000 do
if Ui.stepName() == "dude_move_up" then break end
Ui.update(1 / 60)
Ui.handleInput(press("a"))
end
eq(Ui.stepName(), "dude_move_up", "reached TTVcmd_DudeMoveUp")
local y0, hi0 = Ui.bg3Y, Ui.grassHi
ticks(16)
-- pokefirered/src/teachy_tv.c:961 ChangeBgY(3, 0x100, 2)
eq(Ui.bg3Y, y0 - 16, "BG3 scrolled up one pixel a frame")
-- pokefirered/src/teachy_tv.c:963
eq(Ui.grassHi, hi0 - 1, "the grass counter stepped once in 16 frames")
ticks(T.MOVE_UP - 16)
eq(Ui.stepName(), "dude_move_right", "48 frames later he turns right")
local x0, lo0 = Ui.bg3X, Ui.grassLo
ticks(16)
-- pokefirered/src/teachy_tv.c:981 ChangeBgX(3, 0x100, 1)
eq(Ui.bg3X, x0 + 16, "BG3 scrolled right one pixel a frame")
eq(Ui.grassLo, lo0 + 1, "the low grass counter stepped once")
check(#Ui.grass > 0, "tufts are out while he walks")
Ui.handleInput(press("b"))
-- pokefirered/src/teachy_tv.c:813 grassAnimDisabled = 1
eq(#Ui.grass, 0, "B destroys the tufts at once")
ticks(T.END + 2)
-- pokefirered/src/teachy_tv.c:1059 ChangeBgX(3, 0x0, 0)
eq(Ui.bg3X, -16, "TTVcmd_End puts BG3 back at its start offset")
eq(Ui.bg3Y, 40, "on both axes")
eq(#Ui.grass, 0, "and the grass objects are gone")
Ui.handleInput(press("b"))
Stack.clear()
end
print("[test] 10. pokefirered/src/tm_case.c:1322 the pokedude TM CASE")
do
local TmCase = require("src.ui.game3.tm_case")
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TEACHY_TV, 1)
Bag.add(s.bag, TeachyTv.ITEM_TM_CASE, 1)
-- pokefirered/include/constants/items.h:301 ITEM_TM02
Bag.add(s.bag, 290, 1)
Stack.clear()
Ui.show(s, s.bag)
settleFade()
for _ = 1, 4 do Ui.handleInput(press("down")) end
eq(Ui.rows()[Ui.cursor].index, S.TMS, "the TM lesson is selected")
Ui.handleInput(press("a"))
for _ = 1, 3000 do
if Ui.suspended then break end
Ui.update(1 / 60)
Ui.handleInput(press("a"))
end
check(BagMenu.isOpen(), "the pokedude bag opened for the TMs lesson")
for _ = 1, 500 do Ui.bagDemo.update(1 / 60) end
-- pokefirered/src/item_menu.c:2391
check(TmCase.isOpen(), "the bag handed off to the pokedude TM CASE")
check(not BagMenu.isOpen(), "and the bag closed")
eq(Stack.top() and Stack.top().id, "teachy_pokedude_tm_case", "the TM demo drives it")
-- pokefirered/src/tm_case.c:1334 AddBagItem(ITEM_TM01, 1)
eq(#TmCase.list(), #TeachyTv.POKEDUDE_TMS, "it holds the pokedude's four TMs")
eq(Bag.get(s.bag, 290), 0, "the player's own TM is put away")
eq(Bag.get(s.bag, 289), 1, "TM01 is in the case")
local demo = Ui.tmDemo
for _ = 1, 720 do demo.update(1 / 60) end
-- pokefirered/src/tm_case.c:1422
eq(TmCase.mode, "message", "the POKé DUDE starts talking")
eq(TmCase.messageText, TeachyTv.pagesOf(TeachyTv.TM_TYPES)[1], "gPokedudeText_TMTypes page 1")
local pages = #TeachyTv.pagesOf(TeachyTv.TM_TYPES)
for _ = 1, pages do demo.handleInput(press("a")) end
eq(TmCase.mode, "list", "A pages through and hands the list back")
for _ = 1, 720 do demo.update(1 / 60) end
eq(TmCase.mode, "message", "the second lecture starts")
eq(TmCase.messageText, TeachyTv.pagesOf(TeachyTv.TM_DESCRIPTION)[1],
"gPokedudeText_ReadTMDescription page 1")
for _ = 1, #TeachyTv.pagesOf(TeachyTv.TM_DESCRIPTION) do demo.handleInput(press("a")) end
-- pokefirered/src/tm_case.c:1446 tPokedudeState 21
check(demo.finished, "the TM CASE demo ran to its end")
check(not TmCase.isOpen(), "the TM CASE closed")
eq(Bag.get(s.bag, 290), 1, "the player's TM came back")
eq(Bag.get(s.bag, 289), 0, "the pokedude's TMs are gone")
eq(Bag.get(s.bag, TeachyTv.ITEM_TM_CASE), 1, "and still exactly one TM CASE")
-- pokefirered/src/teachy_tv.c:1100 sWhereToReturnToFromBattle
eq(Ui.state, "lesson", "the TV resumed the lesson")
eq(Ui.stepName(), "text_printer_outro", "at step 9, the outro")
Stack.clear()
Ui.open = false
end
print("[test] 11. pokefirered/src/tm_case.c:1357 B aborts the TM CASE demo to the list")
do
local TmCase = require("src.ui.game3.tm_case")
local s = newSession()
Bag.add(s.bag, TeachyTv.ITEM_TEACHY_TV, 1)
Bag.add(s.bag, TeachyTv.ITEM_TM_CASE, 1)
Bag.add(s.bag, 290, 1)
Stack.clear()
Ui.show(s, s.bag)
settleFade()
for _ = 1, 4 do Ui.handleInput(press("down")) end
Ui.handleInput(press("a"))
for _ = 1, 3000 do
if Ui.suspended then break end
Ui.update(1 / 60)
Ui.handleInput(press("a"))
end
for _ = 1, 500 do Ui.bagDemo.update(1 / 60) end
check(TmCase.isOpen(), "the TM CASE is up")
Ui.tmDemo.handleInput(press("b"))
check(not TmCase.isOpen(), "B closed it")
eq(Bag.get(s.bag, 290), 1, "the player's TM came back on the abort path")
-- pokefirered/src/tm_case.c:1361 SetTeachyTvControllerModeToResume
eq(Ui.state, "list", "and the TV is back on its lesson list")
Stack.clear()
Ui.open = false
end
print("[test] 12. pokefirered/src/sound.c:129 PlayNewMapMusic leaves the location song alone")
do
local s = newSession()
-- pokefirered/include/constants/songs.h:308 MUS_PALLET
Audio._mapSong = 300
Audio.playSong(300)
openWith(s)
eq(Audio.currentSong() and Audio.currentSong().id, 346, "MUS_TEACHY_TV_MENU plays on the list")
eq(Audio._mapSong, 300, "the location song is still MUS_PALLET")
Ui.handleInput(press("a"))
ticks(T.TITLE)
eq(Audio.currentSong() and Audio.currentSong().id, 272, "MUS_FOLLOW_ME plays under the title")
eq(Audio._mapSong, 300, "and the location song is untouched")
Ui.handleInput(press("b"))
ticks(T.END + 2)
eq(Ui.state, "list", "back on the list")
Ui.handleInput(press("b"))
settleFade()
check(not Ui.isOpen(), "B closed the TV")
-- pokefirered/src/teachy_tv.c:705 Overworld_PlaySpecialMapMusic
eq(Audio.currentSong() and Audio.currentSong().id, 300, "the field song comes back on close")
eq(Audio._mapSong, 300, "the location song never changed")
Audio._mapSong = nil
Stack.clear()
end
print("[test] 13. pokefirered/src/data/field_effects/field_effect_objects.h:73 sAnim_TallGrass")
do
local want = { 1, 2, 3, 4, 0 }
for cmd = 0, 4 do
eq(Ui.grassFrame({ frames = cmd * 10 }), want[cmd + 1], "anim command " .. cmd .. " shows sheet frame")
eq(Ui.grassFrame({ frames = cmd * 10 + 9 }), want[cmd + 1], "for all ten frames of command " .. cmd)
end
eq(Ui.grassFrame({ frames = 400 }), 0, "an ended tuft holds the full frame 0")
-- pokefirered/src/teachy_tv.c:1120 SeekSpriteAnim(obj, 4)
local s = newSession()
openWith(s)
Ui.handleInput(press("a"))
Ui.resumeFromDemonstration(1)
local g = Ui.grass[1]
check(g ~= nil, "the post-battle tuft spawned under the dude")
eq(g and Ui.grassFrame(g), 0, "and it starts on the full tuft")
eq(g and g.split, false, "drawn whole in front of him")
Stack.clear()
Ui.open = false
end
if failed > 0 then
print(string.format("FAILED %d check(s)", failed))
os.exit(1)
end
print("All teachy tv screen checks passed.")