Files
gen1recomp/tests/save_compat/gen1_blackout_test.lua
T
2026-10-01 17:30:52 -04:00

148 lines
6.1 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local K = require("tests.save_compat._codec")
if not K.gen1Available() then
print("gen1_blackout skipped (needs data/generated/ for the Gen 1 save codec)")
os.exit(0)
end
local GenSave = require("src.save_convert.GenSave")
local G1 = require("tests.fixtures.save.gen1_build")
local P = require("tests.save_compat._pret_asm")
local Game = require("src.core.Game")
local Overworld = require("src.world.OverworldController")
do
local i = 1
while true do
local name = debug.getupvalue(Overworld.escapeWarpTarget, i)
if not name then break end
if name == "Game" then debug.setupvalue(Overworld.escapeWarpTarget, i, Game) end
i = i + 1
end
end
local O = GenSave.OFFSETS
local table13 = require("src.save_convert.data.blackout_maps")
local function cartWith(version, byte)
return G1.build({ version = version, patch = function(b) b[G1.OFF.lastBlackoutMap] = byte end })
end
local function isPokeCenter(id)
return id:match("POKECENTER$") or id == "INDIGO_PLATEAU_LOBBY"
end
local function run(version)
local data = K.gen1Data(version)
local cw = GenSave.crosswalks(data)
local root = P.root(version)
local tag = version .. ": "
eq(#table13, 13, tag .. "the blackout table has the 13 FlyWarpDataPtr rows")
local seen = {}
for i, row in ipairs(table13) do
seen[row[1]] = true
check(cw.mapsIndex[row[1]] ~= nil, tag .. row[1] .. " is a map in the cache")
local fw = data.field.flyWarps[row[1]]
check(fw and fw.x == row[2] and fw.y == row[3], tag .. row[1] .. " matches the extracted fly warp")
end
local centerCount = 0
if root then
local order, coords = P.flyWarpData(root)
local idx = P.mapIndexes(root)
eq(#order, #table13, tag .. "pret FlyWarpDataPtr has 13 rows")
for i, map in ipairs(order) do
eq(table13[i][1], map, tag .. "row " .. i .. " is " .. map)
eq(table13[i][2], coords[map][1], tag .. map .. " x from special_warps.asm")
eq(table13[i][3], coords[map][2], tag .. map .. " y from special_warps.asm")
eq(cw.mapsIndex[map], idx[map], tag .. map .. " map id matches map_constants.asm")
end
else
print("gen1_blackout: pret checkout missing for " .. version .. ", asm cross-checks skipped")
end
for _, row in ipairs(table13) do
local town, x, y = row[1], row[2], row[3]
local byte = cw.mapsIndex[town]
local bytes = cartWith(version, byte)
local save = assert(K.import(1, version, bytes))
eq(save.lastHeal.map, town, tag .. town .. ": imports as the heal town")
eq(save.lastHeal.x, x, tag .. town .. ": heal x is the fly warp x")
eq(save.lastHeal.y, y, tag .. town .. ": heal y is the fly warp y")
Game.data, Game.save = data, save
local map, lx, ly = Overworld.escapeWarpTarget(Overworld)
eq(map, town, tag .. town .. ": a blackout lands in the town")
eq(lx, x, tag .. town .. ": a blackout lands at the fly warp x")
eq(ly, y, tag .. town .. ": a blackout lands at the fly warp y")
local out = assert(K.export(1, version, save))
eq(out:byte(O.lastBlackoutMap + 1), byte, tag .. town .. ": the byte survives cart -> recomp -> cart")
save.lastHeal = { map = town, x = x, y = y }
out = assert(K.export(1, version, save, false))
eq(out:byte(O.lastBlackoutMap + 1), byte, tag .. town .. ": a town heal exports its own id")
local centers = {}
if root then
for _, dest in ipairs(P.warpDestinations(root, town)) do
if isPokeCenter(dest) then centers[#centers + 1] = dest end
end
else
for _, warp in ipairs(data.maps[town].warps or {}) do
if isPokeCenter(warp.destMap) then centers[#centers + 1] = warp.destMap end
end
end
local uniq, seenCenter = {}, {}
for _, c in ipairs(centers) do
if not seenCenter[c] then seenCenter[c] = true; uniq[#uniq + 1] = c end
end
centers = uniq
centerCount = centerCount + #centers
for _, center in ipairs(centers) do
for _, withOutdoor in ipairs({ false, true }) do
local heal = { map = center, x = 4, y = 4 }
if withOutdoor then heal.outdoor = { id = town, x = x, y = y } end
save.lastHeal = heal
local exported = assert(K.export(1, version, save, false))
eq(exported:byte(O.lastBlackoutMap + 1), byte,
("%s%s heal exports %s (%s outdoor)"):format(tag, center, town, withOutdoor and "with" or "without"))
end
Game.data, Game.save = data, { lastHeal = { map = center, x = 4, y = 4 } }
local m, mx, my = Overworld.escapeWarpTarget(Overworld)
check(m ~= town or (mx == x and my == y), tag .. center .. ": the engine itself resolves a center-only heal sanely")
end
end
eq(centerCount, 12, tag .. "12 Pokemon Center style maps hang off the 13 blackout towns")
local pallet = assert(K.import(1, version, cartWith(version, 0)))
eq(pallet.lastHeal.map, "PALLET_TOWN", tag .. "a never-healed cart blacks out to Pallet Town")
eq(assert(K.export(1, version, pallet)):byte(O.lastBlackoutMap + 1), 0, tag .. "and exports 0")
local route1 = cw.mapsIndex.ROUTE_1
for _, bad in ipairs({ route1, 0xFF, 0x55 }) do
local save = assert(K.import(1, version, cartWith(version, bad)))
eq(save.lastHeal.map, "PALLET_TOWN", tag .. ("byte %02X is not a FlyWarpDataPtr row, the recomp uses Pallet"):format(bad))
eq(assert(K.export(1, version, save)):byte(O.lastBlackoutMap + 1), bad, tag .. ("byte %02X is preserved on the way out"):format(bad))
save.lastHeal = { map = "CELADON_POKECENTER", x = 3, y = 3 }
eq(assert(K.export(1, version, save)):byte(O.lastBlackoutMap + 1), cw.mapsIndex.CELADON_CITY,
tag .. ("byte %02X is replaced once the player heals"):format(bad))
end
local dungeon = assert(K.import(1, version, cartWith(version, 0)))
dungeon.lastHeal = { map = "SEAFOAM_ISLANDS_B1F", x = 23, y = 7 }
dungeon.cartBlackoutMap = nil
local before = dungeon.rawImport:byte(O.lastBlackoutMap + 1)
eq(assert(K.export(1, version, dungeon)):byte(O.lastBlackoutMap + 1), before,
tag .. "a dungeon escape spot is never written as wLastBlackoutMap")
end
run("red")
run("yellow")
T.finish()