mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
190 lines
7.8 KiB
Lua
190 lines
7.8 KiB
Lua
-- The shared POKECENTER_2F staircase must lead back down into whichever
|
|
-- centre it was climbed from.
|
|
--
|
|
-- GOLD_CACHE=".../gold" luajit tests/gen2_pokecenter_stairs_test.lua
|
|
--
|
|
-- maps/Pokecenter2F.asm declares its one staircase as
|
|
--
|
|
-- warp_event 0, 7, POKECENTER_2F, -1
|
|
--
|
|
-- and that -1 is a contract with home/map.asm, in two halves. CopyWarpData
|
|
-- stores the warp stepped ON and the map being left in wPrevWarp /
|
|
-- wPrevMapGroup / wPrevMapNumber on every warp taken; LoadMapAttributes'
|
|
-- warp-coordinate read then copies that triple into wBackupWarpNumber /
|
|
-- wBackupMapGroup / wBackupMapNumber whenever the warp ARRIVED ON declares
|
|
-- destination warp -1. Stepping back onto the -1 warp reads the whole triple
|
|
-- out of the backup (`cp -1 / ld hl, wBackupWarpNumber` in CopyWarpData), so
|
|
-- one second floor serves every Pokemon Center in the game.
|
|
--
|
|
-- The port had only the read half (World:resolveWarp), and its single writer
|
|
-- was the elevator menu -- so the 2F staircase resolved to POKECENTER_2F warp
|
|
-- 255, found nothing, and did nothing: the player was trapped upstairs in
|
|
-- every centre. What is asserted here is the write half riding the REAL
|
|
-- takeWarp, against the real extracted maps.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 pokecenter stairs")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local World = require("src.world.gen2.World")
|
|
local Map = require("src.world.gen2.Map")
|
|
|
|
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/maps.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 tilesets = loadLua("data/generated/tilesets.lua")
|
|
|
|
-- The map data this whole feature hangs on: the 2F staircase really is the -1
|
|
-- sentinel, kept by the extractor as the raw byte.
|
|
eq(maps.POKECENTER_2F.warps[1].destWarp, 0xff,
|
|
"POKECENTER_2F's staircase declares destination warp -1")
|
|
eq(maps.CHERRYGROVE_POKECENTER_1F.warps[3].destMap, "POKECENTER_2F",
|
|
"Cherrygrove's stairs lead up to the shared 2F")
|
|
eq(maps.VIOLET_POKECENTER_1F.warps[3].destMap, "POKECENTER_2F",
|
|
"and so do Violet's")
|
|
|
|
-- A World over the real defs. setMap is recorded rather than run (baking a
|
|
-- map image needs a graphics device); takeWarp's own bookkeeping around it --
|
|
-- resolveWarp, the wPrev capture, recordWarpBackup -- is the shipped code.
|
|
local function world(mapId, x, y)
|
|
local game = { data = { audio = { sfxOrder = {} } }, save = { player = {} } }
|
|
local w = World.new(game)
|
|
w.maps, w.tilesets = maps, tilesets
|
|
w.map = Map.new(maps[mapId], tilesets[maps[mapId].tileset])
|
|
w.player = { cellX = x, cellY = y, facing = "up", moving = false }
|
|
w.loaded = nil
|
|
w.setMap = function(self, id, cx, cy, facing)
|
|
self.loaded = { id = id, x = cx, y = cy, facing = facing }
|
|
return true
|
|
end
|
|
return w
|
|
end
|
|
|
|
-- MAPSETUP_DOOR fades out before the load, so the take is parked in
|
|
-- world.mapSetup; this drains it the way World:step does.
|
|
local function pump(w)
|
|
for _ = 1, 64 do
|
|
if not w.mapSetup then return end
|
|
w:updateMapSetup()
|
|
end
|
|
end
|
|
|
|
-- Climb the stairs from Cherrygrove: the arrival warp is the -1 staircase, so
|
|
-- the take must bank {Cherrygrove 1F, warp 3} as the way back.
|
|
do
|
|
local w = world("CHERRYGROVE_POKECENTER_1F", 0, 7)
|
|
check(w:takeWarp(maps.CHERRYGROVE_POKECENTER_1F.warps[3]),
|
|
"the 1F stairs warp up")
|
|
pump(w)
|
|
eq(w.loaded and w.loaded.id, "POKECENTER_2F", "landing on the shared 2F")
|
|
check(w.backupWarp ~= nil, "arriving on a -1 warp banks the backup triple")
|
|
eq(w.backupWarp.map, "CHERRYGROVE_POKECENTER_1F",
|
|
"the banked map is the centre being left")
|
|
eq(w.backupWarp.warp, 3, "and the banked warp is the stairs stepped on")
|
|
|
|
-- Step back onto the staircase: the -1 destination resolves through the
|
|
-- backup, back into the same centre, onto the same stairs.
|
|
w.map = Map.new(maps.POKECENTER_2F, tilesets[maps.POKECENTER_2F.tileset])
|
|
w.player = { cellX = 0, cellY = 7, facing = "down", moving = false }
|
|
w.loaded = nil
|
|
check(w:takeWarp(maps.POKECENTER_2F.warps[1]), "the 2F stairs warp at all")
|
|
pump(w)
|
|
eq(w.loaded and w.loaded.id, "CHERRYGROVE_POKECENTER_1F",
|
|
"and they lead back to the centre the player came from")
|
|
eq(w.loaded.x, maps.CHERRYGROVE_POKECENTER_1F.warps[3].x,
|
|
"onto that centre's own staircase tile x")
|
|
eq(w.loaded.y, maps.CHERRYGROVE_POKECENTER_1F.warps[3].y, "and y")
|
|
end
|
|
|
|
-- The same 2F reached from Violet leads back to Violet: the backup is
|
|
-- re-banked on every -1 arrival, which is the whole point of the sentinel.
|
|
do
|
|
local w = world("VIOLET_POKECENTER_1F", 0, 7)
|
|
check(w:takeWarp(maps.VIOLET_POKECENTER_1F.warps[3]), "Violet's stairs warp")
|
|
pump(w)
|
|
eq(w.backupWarp.map, "VIOLET_POKECENTER_1F", "the backup now names Violet")
|
|
|
|
w.map = Map.new(maps.POKECENTER_2F, tilesets[maps.POKECENTER_2F.tileset])
|
|
w.player = { cellX = 0, cellY = 7, facing = "down", moving = false }
|
|
w.loaded = nil
|
|
check(w:takeWarp(maps.POKECENTER_2F.warps[1]), "the staircase works here too")
|
|
pump(w)
|
|
eq(w.loaded and w.loaded.id, "VIOLET_POKECENTER_1F",
|
|
"and comes down in Violet, not Cherrygrove")
|
|
end
|
|
|
|
-- An ordinary warp arrival must NOT disturb the backup: the trade corner
|
|
-- doors up on the 2F carry real destinations, and only a -1 arrival re-banks.
|
|
do
|
|
local w = world("CHERRYGROVE_POKECENTER_1F", 0, 7)
|
|
check(w:takeWarp(maps.CHERRYGROVE_POKECENTER_1F.warps[3]), "up the stairs")
|
|
pump(w)
|
|
local banked = w.backupWarp
|
|
w.map = Map.new(maps.CHERRYGROVE_POKECENTER_1F,
|
|
tilesets[maps.CHERRYGROVE_POKECENTER_1F.tileset])
|
|
w.player = { cellX = 3, cellY = 7, facing = "down", moving = false }
|
|
check(w:takeWarp(maps.CHERRYGROVE_POKECENTER_1F.warps[1]),
|
|
"out the front door")
|
|
pump(w)
|
|
eq(w.backupWarp, banked,
|
|
"a warp whose arrival declares a real destination leaves the backup alone")
|
|
end
|
|
|
|
-- With no backup banked at all (a fresh boot standing upstairs), the -1 warp
|
|
-- still must not crash; the cart would read whatever the triple last held.
|
|
do
|
|
local w = world("POKECENTER_2F", 0, 7)
|
|
eq(w:takeWarp(maps.POKECENTER_2F.warps[1]), false,
|
|
"an unbanked -1 warp refuses rather than crashing")
|
|
end
|
|
|
|
-- Save and reload upstairs: the triple is saved WRAM on the cart, so it rides
|
|
-- save.backupWarp -- Game2:snapshotSave writes it and World:loadPlayerData
|
|
-- reads it back into the rebuilt world.
|
|
do
|
|
local w = world("CHERRYGROVE_POKECENTER_1F", 0, 7)
|
|
check(w:takeWarp(maps.CHERRYGROVE_POKECENTER_1F.warps[3]), "up the stairs")
|
|
pump(w)
|
|
|
|
-- The snapshot half, through the real Game2 method.
|
|
local Game2 = require("src.core.Game2")
|
|
local host = setmetatable({
|
|
save = { player = {} },
|
|
world = w,
|
|
options = {},
|
|
}, { __index = Game2 })
|
|
w.map = Map.new(maps.POKECENTER_2F, tilesets[maps.POKECENTER_2F.tileset])
|
|
w.player = { cellX = 0, cellY = 7, facing = "down", moving = false }
|
|
local saved = host:snapshotSave()
|
|
check(type(saved.backupWarp) == "table", "the snapshot carries the triple")
|
|
eq(saved.backupWarp.map, "CHERRYGROVE_POKECENTER_1F", "with the map")
|
|
eq(saved.backupWarp.warp, 3, "and the warp")
|
|
|
|
-- The restore half, through the real loadPlayerData on a fresh World.
|
|
local w2 = world("POKECENTER_2F", 0, 7)
|
|
w2:loadPlayerData(saved)
|
|
check(w2.backupWarp ~= nil, "loadPlayerData restores the triple")
|
|
eq(w2.backupWarp.map, "CHERRYGROVE_POKECENTER_1F", "map intact")
|
|
eq(w2.backupWarp.warp, 3, "warp intact")
|
|
w2.loaded = nil
|
|
check(w2:takeWarp(maps.POKECENTER_2F.warps[1]),
|
|
"so the stairs work after a save made upstairs")
|
|
pump(w2)
|
|
eq(w2.loaded and w2.loaded.id, "CHERRYGROVE_POKECENTER_1F",
|
|
"and still lead home")
|
|
end
|
|
|
|
S.finish()
|