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

187 lines
7.3 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")
local G2 = require("tests.fixtures.save.gen2_build")
local Ref = require("tests.save_compat._gen2_reference")
local ITEM_INDEX = {}
for id, def in pairs(K.gen2Data.items) do ITEM_INDEX[id] = def.index end
local function itemIndex(id)
if id == nil then return 0 end
if type(id) == "number" then return id end
return ITEM_INDEX[id] or -1
end
local function sameMon(label, m, r, party)
eq(m.species, r.species, label .. " species")
eq(m.isEgg == true, r.listed == 0xFD, label .. " egg marker")
if r.listed == 0xFD then
eq(m.eggSteps, r.friendship, label .. " hatch counter")
else
eq(m.happiness, r.friendship, label .. " happiness")
end
eq(m.level, r.level, label .. " level")
eq(m.experience, r.exp, label .. " exp")
eq(m.otId, r.otId, label .. " OT id")
eq(m.dvs.attack, r.dvAtk, label .. " atk DV")
eq(m.dvs.defense, r.dvDef, label .. " def DV")
eq(m.dvs.speed, r.dvSpe, label .. " spe DV")
eq(m.dvs.special, r.dvSpc, label .. " spc DV")
eq(itemIndex(m.item), r.item, label .. " item")
eq(m.pokerus, r.pokerus, label .. " pokerus")
local moves = {}
for _, mv in ipairs(r.moves) do if mv ~= 0 then moves[#moves + 1] = mv end end
eq(#m.moves, #moves, label .. " move count")
for i, mv in ipairs(moves) do eq(m.moves[i] and m.moves[i].id, mv, label .. " move " .. i) end
if not r.nick:find("<", 1, true) then eq(m.nickname, r.nick, label .. " nickname") end
if not r.ot:find("<", 1, true) then eq(m.ot, r.ot, label .. " OT") end
if party then
eq(m.hp, r.hp, label .. " hp")
eq(m.maxHp, r.maxHp, label .. " max hp")
end
end
local function bagOf(r)
local order, totals, seen = {}, {}, {}
for _, pocketRows in ipairs({ r.items, r.keys, r.balls }) do
for _, row in ipairs(pocketRows) do
if not seen[row[1]] then seen[row[1]] = true; order[#order + 1] = row[1] end
totals[row[1]] = (totals[row[1]] or 0) + row[2]
end
end
return order, totals
end
local function agree(label, save, r, version)
eq(save.player.id, r.id, label .. ": trainer id")
if not r.name:find("<", 1, true) then eq(save.player.name, r.name, label .. ": player name") end
if not r.rival:find("<", 1, true) then eq(save.rival.name, r.rival, label .. ": rival name") end
eq(save.player.money, r.money, label .. ": money")
local johto, kanto = 0, 0
for i, name in ipairs({ "ZEPHYR", "HIVE", "PLAIN", "FOG", "MINERAL", "STORM", "GLACIER", "RISING" }) do
if save.player.badges[name] then johto = johto + 2 ^ (i - 1) end
end
for i, name in ipairs({ "BOULDER", "CASCADE", "THUNDER", "RAINBOW", "SOUL", "MARSH", "VOLCANO", "EARTH" }) do
if save.player.kantoBadges[name] then kanto = kanto + 2 ^ (i - 1) end
end
eq(johto, r.johto, label .. ": johto badges")
eq(kanto, r.kanto, label .. ": kanto badges")
eq(save.currentBox, r.curBox, label .. ": current box")
eq(save.playTime.hours, r.hours, label .. ": hours")
eq(save.playTime.minutes, r.minutes, label .. ": minutes")
eq(save.playTime.seconds, r.seconds, label .. ": seconds")
eq(save.playTime.frames, r.frames, label .. ": frames")
if version == "crystal" then eq(save.player.gender == "female", r.female, label .. ": gender") end
eq(#save.party, r.party.count, label .. ": party count")
for i, rm in ipairs(r.party.mons) do sameMon(("%s: party %d"):format(label, i), save.party[i], rm, true) end
for b = 1, 14 do
eq(#save.boxes[b], r.boxes[b].count, ("%s: box %d count"):format(label, b))
for i, rm in ipairs(r.boxes[b].mons) do
sameMon(("%s: box %d slot %d"):format(label, b, i), save.boxes[b][i], rm, false)
end
if not r.boxNames[b]:find("<", 1, true) then
eq(save.boxNames[b], r.boxNames[b], ("%s: box %d name"):format(label, b))
end
end
local order, totals = bagOf(r)
local got = {}
for _, id in ipairs(save.bagOrder) do
local n = itemIndex(id)
if totals[n] then got[#got + 1] = n end
end
eq(table.concat(got, ","), table.concat(order, ","), label .. ": bag order")
for n, total in pairs(totals) do
local found
for id, count in pairs(save.inventory) do if itemIndex(id) == n then found = count end end
eq(found, total, ("%s: bag total for item 0x%02X"):format(label, n))
end
local pcTotals = {}
for _, row in ipairs(r.pc) do pcTotals[row[1]] = (pcTotals[row[1]] or 0) + row[2] end
for n, total in pairs(pcTotals) do
local found
for id, count in pairs(save.pcItems) do if itemIndex(id) == n then found = count end end
eq(found, total, ("%s: PC total for item 0x%02X"):format(label, n))
end
local caught, seen = 0, 0
for _ in pairs(save.pokedex.caught) do caught = caught + 1 end
for _ in pairs(save.pokedex.seen) do seen = seen + 1 end
eq(caught, r.caught, label .. ": dex caught")
eq(seen, r.seen, label .. ": dex seen")
for i = 0, 255 do
if save.events[i] ~= r.events[i] then
eq(save.events[i], r.events[i], ("%s: event byte %d"):format(label, i))
break
end
end
end
local function sameRef(label, a, b)
local function flat(v, path, out)
if type(v) ~= "table" then out[path] = tostring(v) return end
for k, x in pairs(v) do flat(x, path .. "." .. tostring(k), out) end
end
local fa, fb = {}, {}
flat(a, "", fa)
flat(b, "", fb)
local bad = 0
for k, v in pairs(fa) do
if k ~= ".checksum1" and k ~= ".checksum2" and fb[k] ~= v then
bad = bad + 1
if bad <= 3 then eq(fb[k], v, label .. " " .. k) end
end
end
eq(bad, 0, label .. ": every field the reference decoder reads is unchanged")
end
local function aggregated(r)
local out = {}
for k, v in pairs(r) do out[k] = v end
for _, key in ipairs({ "items", "keys", "balls", "pc" }) do
local order, totals = {}, {}
for _, row in ipairs(r[key]) do
if not totals[row[1]] then order[#order + 1] = row[1]; totals[row[1]] = 0 end
totals[row[1]] = totals[row[1]] + row[2]
end
local rows = {}
for _, id in ipairs(order) do rows[#rows + 1] = { id, totals[id] } end
out[key] = rows
end
return out
end
local cases = 0
for _, c in ipairs(G2.cases()) do
if not c.refuse then
cases = cases + 1
local src = Ref.decode(c.bytes, c.version)
local save = K.import(2, c.version, c.bytes)
check(save ~= nil, c.id .. ": imports")
if save then
agree(c.id .. " import", save, src, c.version)
local r1 = K.export(2, c.version, save, c.bytes)
local fresh = K.export(2, c.version, assert(K.import(2, c.version, c.bytes)), false)
for label, out in pairs({ r1 = r1, fresh = fresh }) do
check(out ~= nil, c.id .. " " .. label .. ": exports")
if out then
local ref = Ref.decode(out, c.version)
eq(ref.checksum1, true, c.id .. " " .. label .. ": the reference finds checksum 1 valid")
eq(ref.checksum2, true, c.id .. " " .. label .. ": and checksum 2")
if label == "r1" then
sameRef(c.id .. " r1", src, ref)
else
sameRef(c.id .. " fresh", aggregated(src), aggregated(ref))
end
agree(c.id .. " " .. label .. " reimport", assert(K.import(2, c.version, out)), ref, c.version)
end
end
end
end
end
check(cases >= 50, "every Gen 2 fixture went through the reference decoder")
T.finish()