Files
gen1recomp/tests/gen2_hof_continue_test.lua
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

172 lines
6.3 KiB
Lua

-- The end of the game has to hand the controller back.
--
-- GOLD_CACHE=".../gold" luajit tests/gen2_hof_continue_test.lua
--
-- Script_halloffame ends on ReturnFromCredits (engine/overworld/
-- scripting.asm): Script_endall plus MAPSTATUS_DONE, which returns out of
-- OverworldLoop entirely. FinishContinueFunction (engine/menus/
-- intro_menu.asm) then reads wSpawnAfterChampion:
--
-- SPAWN_RED SpawnAfterRed: wDefaultSpawnpoint = SPAWN_MT_SILVER,
-- clear the byte, MAPSETUP_WARP, loop back into the
-- overworld -- play resumes outside Silver Cave.
-- anything else `jp Reset` -- the champion's credits end on the title
-- screen, and the induction's SaveGameData already put
-- SPAWN_LANCE in the save.
--
-- and Continue's own `cp SPAWN_LANCE / jr z, .SpawnAfterE4` consumes the
-- saved byte BEFORE the saved position is honoured, spawning at New Bark
-- Town. Without either half the champion was left standing in HALL_OF_FAME,
-- whose only exit is the sealed Lance's room door: a soft lock at the moment
-- of victory.
--
-- These drive the shipped World:hallOfFame / World:credits /
-- World:consumePostGameSpawn against the real cache; only the screens are
-- registry fakes (they are pure presentation, and Screens.push resolves the
-- registry first for exactly this reason).
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("gen2 hof continue")
local check, eq = S.check, S.eq
local World = require("src.world.gen2.World")
local HallOfFame = require("src.core.gen2.HallOfFame")
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/landmarks.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 maps = loadLua("data/generated/maps.lua")
local landmarks = loadLua("data/generated/landmarks.lua")
-- One registry serves the whole file; Screens caches factories per id, so the
-- fakes are registered once and the recorder is swapped per test.
local pushes
local registry = {}
for _, id in ipairs({ "Gen2HallOfFame", "Gen2Credits" }) do
registry[id] = { new = function(_game, opts)
pushes[#pushes + 1] = { id = id, opts = opts }
return { screenId = id }
end }
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()
pushes = {}
local game = {
data = { audio = {}, screens = registry },
save = {
player = { name = "GOLD" },
party = { { species = "TYPHLOSION", level = 50, hp = 120,
otId = 33333 } },
},
stack = makeStack(),
titled = 0,
}
game.returnToTitle = function(g) g.titled = g.titled + 1 end
local w = World.new(game)
w.maps = maps
w.landmarks = landmarks
w.loaded = nil
w.setMap = function(self, id, x, y, facing)
self.loaded = { id = id, x = x, y = y, facing = facing }
return true
end
return w, game
end
-- ---- the champion's ending: induct, roll, reset ---------------------------
do
local w, game = makeWorld()
local scriptResumed = false
check(w:hallOfFame(function() scriptResumed = true end),
"the halloffame command takes the screen")
-- The ceremony's bookkeeping ran before the screen came up.
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_LANCE,
"induction writes wSpawnAfterChampion = SPAWN_LANCE")
eq(HallOfFame.count(game.save), 1, "and bumps the win count")
eq(pushes[1].id, "Gen2HallOfFame", "the roster ceremony is up first")
-- `pop af / jp Credits`.
pushes[1].opts.onDone()
eq(pushes[2] and pushes[2].id, "Gen2Credits", "the roll follows the ceremony")
eq(pushes[2].opts.allowSkip, false,
"a first-time champion cannot hurry the roll (the PRE-induction flags)")
-- The credits end: the script resumes (and immediately ends, as
-- ReturnFromCredits' Script_endall does), and then the reset.
eq(game.titled, 0, "no reset while the roll is still up")
pushes[2].opts.onDone()
check(scriptResumed, "the script got its resume before the teardown")
eq(game.titled, 1, "`jp Reset`: the credits end on the title screen")
check(w.loaded == nil, "and no warp happened in this session")
end
-- ---- CONTINUE consumes the saved spawn ------------------------------------
do
local w, game = makeWorld()
HallOfFame.induct(game.save, game.save.party)
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_LANCE, "the save is armed")
local spawn = w:consumePostGameSpawn()
check(spawn ~= nil, "Continue's .SpawnAfterE4 arm fires")
eq(spawn.map, "NEW_BARK_TOWN", "SPAWN_NEW_BARK is New Bark Town")
eq(spawn.x, 13, "at the spawn point's x")
eq(spawn.y, 6, "and y")
eq(game.save.spawnAfterChampion, nil,
"PostCreditsSpawn zeroes the byte on the way")
check(w:consumePostGameSpawn() == nil, "so the next load is ordinary")
end
-- ---- the Red ending: no reset, straight back to Silver Cave ---------------
do
local w, game = makeWorld()
local scriptResumed = false
check(w:credits(function() scriptResumed = true end),
"the credits command takes the screen")
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_RED,
"RedCredits writes wSpawnAfterChampion = SPAWN_RED")
eq(pushes[1].id, "Gen2Credits", "and goes straight to the roll")
pushes[1].opts.onDone()
check(scriptResumed, "the script got its resume")
eq(game.titled, 0, ".AfterRed does not reset")
check(w.loaded ~= nil, "it re-enters the overworld instead")
eq(w.loaded.id, "SILVER_CAVE_OUTSIDE", "outside Silver Cave")
eq(w.loaded.x, 23, "at SPAWN_MT_SILVER's x")
eq(w.loaded.y, 20, "and y")
eq(game.save.spawnAfterChampion, nil, "with the byte consumed in session")
end
-- ---- an ordinary continue is untouched ------------------------------------
do
local w, game = makeWorld()
game.save.position = { map = "GOLDENROD_CITY", x = 5, y = 5 }
check(w:consumePostGameSpawn() == nil,
"no pending spawn means the saved position stays in charge")
eq(game.save.position.map, "GOLDENROD_CITY", "and is not disturbed")
end
S.finish()