mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
256 lines
9.6 KiB
Lua
256 lines
9.6 KiB
Lua
-- Test game3 TrainerCard and SaveMenu mechanics and state flows.
|
|
|
|
local function test(name, fn)
|
|
local ok, err = pcall(fn)
|
|
if ok then
|
|
print("[ok] " .. name)
|
|
else
|
|
print("[FAIL] " .. name .. ": " .. tostring(err))
|
|
error(err)
|
|
end
|
|
end
|
|
|
|
print("[test] 1. Trainer Card lifecycle and fields")
|
|
local TrainerCard = require("src.ui.game3.trainer_card")
|
|
|
|
local session = {
|
|
name = "RED",
|
|
id = 12345,
|
|
money = 3500,
|
|
dex = { caught = { [1] = true, [4] = true, [7] = true } },
|
|
playTimeHours = 2,
|
|
playTimeMinutes = 45,
|
|
flags = { [0x820] = true, [0x821] = true },
|
|
-- sessions carry the engine's map id; the save screen resolves the place name
|
|
-- from it (see tests/engine/save_menu_location_bug2328.lua)
|
|
map = "FR_PALLET_TOWN",
|
|
}
|
|
|
|
local closed = false
|
|
TrainerCard.show({
|
|
session = session,
|
|
onClose = function() closed = true end,
|
|
})
|
|
|
|
test("TrainerCard is open", function()
|
|
assert(TrainerCard.isOpen() == true, "TrainerCard should be open")
|
|
end)
|
|
|
|
TrainerCard.close()
|
|
|
|
test("TrainerCard closed callback called", function()
|
|
assert(TrainerCard.isOpen() == false, "TrainerCard should be closed")
|
|
assert(closed == true, "onClose should have been called")
|
|
end)
|
|
|
|
test("TrainerCard front and back flip", function()
|
|
TrainerCard.show({ session = session })
|
|
assert(TrainerCard.isOpen() == true, "Should be open")
|
|
assert(TrainerCard.side == "front", "Initial side should be front")
|
|
|
|
local inpA = { wasPressed = function(_, k) return k == "a" end }
|
|
local inpB = { wasPressed = function(_, k) return k == "b" end }
|
|
local function settle()
|
|
for _ = 1, 64 do TrainerCard.update(1 / 60) end
|
|
end
|
|
|
|
-- src/trainer_card.c:558 A on the front starts the flip to the back
|
|
TrainerCard.handleInput(inpA)
|
|
settle()
|
|
assert(TrainerCard.side == "back", "Side should flip to back")
|
|
|
|
-- src/trainer_card.c:588 B on the back flips to the front
|
|
TrainerCard.handleInput(inpB)
|
|
settle()
|
|
assert(TrainerCard.side == "front", "Side should flip back to front")
|
|
|
|
-- src/trainer_card.c:566 B on the front closes the card
|
|
TrainerCard.handleInput(inpB)
|
|
assert(TrainerCard.isOpen() == false, "Should be closed after B")
|
|
end)
|
|
|
|
test("A on the back closes the card", function()
|
|
-- src/trainer_card.c:604 A on the back exits instead of flipping
|
|
TrainerCard.show({ session = session })
|
|
local inpA = { wasPressed = function(_, k) return k == "a" end }
|
|
TrainerCard.handleInput(inpA)
|
|
for _ = 1, 64 do TrainerCard.update(1 / 60) end
|
|
assert(TrainerCard.side == "back", "Should be on the back")
|
|
TrainerCard.handleInput(inpA)
|
|
assert(TrainerCard.isOpen() == false, "A on the back should close the card")
|
|
end)
|
|
|
|
print("[test] 2. SaveMenu lifecycle and state machine")
|
|
local SaveMenu = require("src.ui.game3.save_menu")
|
|
|
|
local Runtime = require("src.core.game3.runtime")
|
|
Runtime._game = { saveGame = function() return true end }
|
|
|
|
local saveClosed = false
|
|
SaveMenu.show({
|
|
session = session,
|
|
onClose = function() saveClosed = true end,
|
|
})
|
|
|
|
test("SaveMenu starts in confirm phase", function()
|
|
assert(SaveMenu.isOpen() == true, "SaveMenu should be open")
|
|
assert(SaveMenu._phase == "confirm", "Initial phase should be confirm")
|
|
assert(SaveMenu.cursor == 1, "Cursor should start on YES (1)")
|
|
end)
|
|
|
|
test("SaveMenu and TrainerCard agree on the badge flags", function()
|
|
assert(SaveMenu.countBadges(session) == 2, "save stats reads 2 badges from session.flags")
|
|
assert(TrainerCard.countBadges(session) == 2, "trainer card reads 2 badges from session.flags")
|
|
end)
|
|
|
|
test("SaveMenu cursor movement", function()
|
|
SaveMenu.move(1)
|
|
assert(SaveMenu.cursor == 2, "Cursor should flip to NO (2)")
|
|
SaveMenu.move(1)
|
|
assert(SaveMenu.cursor == 1, "Cursor should flip back to YES (1)")
|
|
end)
|
|
|
|
test("SaveMenu confirm transitions to overwrite then saved", function()
|
|
SaveMenu.confirm()
|
|
assert(SaveMenu._phase == "overwrite", "Selecting YES in confirm should transition to overwrite")
|
|
SaveMenu.confirm()
|
|
assert(SaveMenu._phase == "saved", "Selecting YES in overwrite should transition to saved")
|
|
SaveMenu.confirm()
|
|
assert(SaveMenu.isOpen() == false, "Confirm in saved phase should close menu")
|
|
assert(saveClosed == true, "Save onClose callback should be called")
|
|
end)
|
|
|
|
print("[test] 3. SaveMenu cancellation")
|
|
test("SaveMenu cancellation", function()
|
|
local noClosed = false
|
|
SaveMenu.show({
|
|
session = session,
|
|
onClose = function() noClosed = true end,
|
|
})
|
|
SaveMenu.move(1) -- Move to NO
|
|
SaveMenu.confirm() -- Press A on NO
|
|
assert(SaveMenu.isOpen() == false, "Selecting NO should close SaveMenu")
|
|
assert(noClosed == true, "onClose should be called on cancel")
|
|
end)
|
|
|
|
print("[test] 4. Trainer Card badge unlocking & multi-format resolution")
|
|
test("Badges from array format", function()
|
|
local s = { badges = { true, true, true, false, false, false, false, false } }
|
|
assert(TrainerCard.countBadges(s) == 3, "Should count 3 badges from array")
|
|
assert(TrainerCard.isBadgeUnlocked(1) == false, "Default closed should be false")
|
|
TrainerCard.show({ session = s })
|
|
assert(TrainerCard.isBadgeUnlocked(1) == true, "Badge 1 unlocked")
|
|
assert(TrainerCard.isBadgeUnlocked(2) == true, "Badge 2 unlocked")
|
|
assert(TrainerCard.isBadgeUnlocked(3) == true, "Badge 3 unlocked")
|
|
assert(TrainerCard.isBadgeUnlocked(4) == false, "Badge 4 locked")
|
|
TrainerCard.close()
|
|
end)
|
|
|
|
test("Badges from FRLG flags format (0x820..0x827)", function()
|
|
local s = {
|
|
flags = {
|
|
[0x820] = true, -- Boulder
|
|
[0x821] = true, -- Cascade
|
|
[0x823] = true, -- Rainbow
|
|
[0x826] = true, -- Volcano
|
|
}
|
|
}
|
|
assert(TrainerCard.countBadges(s) == 4, "Should count 4 badges from flags")
|
|
TrainerCard.show({ session = s })
|
|
assert(TrainerCard.isBadgeUnlocked(1) == true, "Boulder badge unlocked (0x820)")
|
|
assert(TrainerCard.isBadgeUnlocked(2) == true, "Cascade badge unlocked (0x821)")
|
|
assert(TrainerCard.isBadgeUnlocked(3) == false, "Thunder badge locked (0x822)")
|
|
assert(TrainerCard.isBadgeUnlocked(4) == true, "Rainbow badge unlocked (0x823)")
|
|
assert(TrainerCard.isBadgeUnlocked(7) == true, "Volcano badge unlocked (0x826)")
|
|
assert(TrainerCard.isBadgeUnlocked(8) == false, "Earth badge locked (0x827)")
|
|
TrainerCard.close()
|
|
end)
|
|
|
|
test("Badges from scripting store flags", function()
|
|
local s = {
|
|
store = {
|
|
flags = {
|
|
["FLAG_BADGE01_GET"] = true,
|
|
["FLAG_BADGE03_GET"] = true,
|
|
["FLAG_BADGE05_GET"] = true,
|
|
}
|
|
}
|
|
}
|
|
assert(TrainerCard.countBadges(s) == 3, "Should count 3 badges from store.flags")
|
|
TrainerCard.show({ session = s })
|
|
assert(TrainerCard.isBadgeUnlocked(1) == true, "Badge 1 (Boulder)")
|
|
assert(TrainerCard.isBadgeUnlocked(3) == true, "Badge 3 (Thunder)")
|
|
assert(TrainerCard.isBadgeUnlocked(5) == true, "Badge 5 (Soul)")
|
|
assert(TrainerCard.isBadgeUnlocked(2) == false, "Badge 2 (Cascade)")
|
|
TrainerCard.close()
|
|
end)
|
|
|
|
test("Badges from bitmask format (0xFF = all 8 badges)", function()
|
|
local s = { badges = 0xFF }
|
|
assert(TrainerCard.countBadges(s) == 8, "Should count 8 badges from 0xFF bitmask")
|
|
TrainerCard.show({ session = s })
|
|
for i = 1, 8 do
|
|
assert(TrainerCard.isBadgeUnlocked(i) == true, "Badge " .. i .. " unlocked")
|
|
end
|
|
TrainerCard.close()
|
|
end)
|
|
|
|
print("[test] 5. Currency glyph mapping and font measure")
|
|
test("Currency sign maps to Pokédollar 0xB7", function()
|
|
local FrlgFont = require("src.ui.game3.frlg_font")
|
|
assert(FrlgFont.glyphId("$") == 0xB7, "$ must map to Pokédollar glyph 0xB7")
|
|
assert(FrlgFont.glyphId("¥") == 0xB7, "¥ must map to Pokédollar glyph 0xB7")
|
|
assert(FrlgFont.glyphId("\xC2\xA5") == 0xB7, "UTF-8 yen must map to Pokédollar glyph 0xB7")
|
|
local w1 = FrlgFont.measure("$3000")
|
|
local w2 = FrlgFont.measure("¥3000")
|
|
assert(w1 == w2, "Width of $3000 and ¥3000 must match")
|
|
assert(w1 > 0, "Width must be positive")
|
|
end)
|
|
|
|
print("[test] 6. Runtime playtime accumulation and synchronization")
|
|
test("Runtime.pumpRtc increments and synchronizes playtime", function()
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local sess = {
|
|
playtime = { hours = 1, minutes = 59, seconds = 58, vblanks = 0 }
|
|
}
|
|
local game = { session = sess, save = {} }
|
|
Runtime.session = sess
|
|
Runtime._playTimeAcc = 0
|
|
|
|
-- Tick 2 seconds (120 frames at 1/60s)
|
|
for _ = 1, 120 do
|
|
Runtime.pumpRtc(game, 1 / 60)
|
|
end
|
|
|
|
assert(sess.playTimeHours == 2, "Hours should roll over to 2 (was " .. tostring(sess.playTimeHours) .. ")")
|
|
assert(sess.playTimeMinutes == 0, "Minutes should roll over to 0 (was " .. tostring(sess.playTimeMinutes) .. ")")
|
|
assert(sess.playTimeSeconds == 0, "Seconds should roll over to 0 (was " .. tostring(sess.playTimeSeconds) .. ")")
|
|
assert(game.save.playTimeHours == 2, "Save hours should synchronize to 2")
|
|
assert(game.save.playTimeMinutes == 0, "Save minutes should synchronize to 0")
|
|
end)
|
|
|
|
if not require("tests.game3_cache").mount() then
|
|
print("[skip] test 7 reads ROM map section names: " .. tostring(require("tests.game3_cache").reason))
|
|
print("[test] all passed")
|
|
os.exit(0)
|
|
end
|
|
print("[test] 7. SaveMenu location header resolution from ROM mapsec")
|
|
test("SaveMenu location resolution does not print engine internal map IDs", function()
|
|
local MapSectionsExtract = require("src.import.gba.map_sections_extract")
|
|
local tests = {
|
|
{ map = "FR_ROUTE_22", want = "ROUTE 22" },
|
|
{ map = "FR_PALLET_TOWN", want = "PALLET TOWN" },
|
|
{ map = "FR_VIRIDIAN_CITY", want = "VIRIDIAN CITY" },
|
|
{ map = "FR_CELADON_CITY_DEPARTMENT_STORE_2F", want = "CELADON DEPT." },
|
|
{ map = "FR_POKEMON_TOWER_3F", want = "POKéMON TOWER" },
|
|
{ map = "FR_ROUTE_1", want = "ROUTE 1" },
|
|
}
|
|
|
|
for _, item in ipairs(tests) do
|
|
local place = MapSectionsExtract.getPlaceName(item.map)
|
|
assert(place == item.want, string.format("Expected %s for map %s, got %s", item.want, item.map, tostring(place)))
|
|
end
|
|
end)
|
|
|
|
print("[test] all passed")
|