Files
gen1recomp/tests/engine/union_trade_combos_cache.lua
T
2026-10-07 14:31:38 -04:00

164 lines
7.6 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = love or require("tests.love_stub")
local T = require("tests.harness")
local F = require("tests.engine._xgen_fixture")
local Model = require("src.online.union.TradePrepModel")
local Txn = require("src.online.union.TradeTxn")
local TradeConvert = require("src.online.xgen.TradeConvert")
local Project = require("src.online.xgen.Project")
local Protocol = require("src.link.Protocol")
local GameVersion = require("src.core.GameVersion")
local SaveData = require("src.core.SaveData")
local Save2 = require("src.core.gen2.Save")
local PICK = { [1] = { "red", "blue", "yellow" }, [2] = { "gold", "silver", "crystal" }, [3] = { "firered", "leafgreen" } }
local real = {}
local chosen = {}
for gen, list in pairs(PICK) do
for _, v in ipairs(list) do
if not chosen[gen] then
local d = F.real(v)
if d then real[v], chosen[gen] = d, v end
end
end
end
local gen3root
if chosen[3] then
local base = F.cacheRoot(chosen[3])
gen3root = base and (base .. "/" .. GameVersion.cachePrefix(chosen[3]) .. "data/generated/gba")
local Dataset = require("src.core.game3.dataset")
Dataset.cacheRootOverride = gen3root
Dataset.mountExtractRoots()
require("src.core.game3.pokemon").install(nil)
if not require("src.core.game3.pokemon")._names then chosen[3] = nil end
end
if not (chosen[1] or chosen[2] or chosen[3]) then
print("[skip] union_trade_combos: no imported caches")
os.exit(0)
end
Model.datasetSource = function(v) return real[v] end
local function gameData(d)
return { pokemon = d.raw.pokemon, moves = d.raw.moves, items = d.raw.items or {} }
end
local function sample(data, otId, nick)
local sp = data.species[25]
local moves = {}
for _, row in ipairs(sp.levelMoves) do
if row.level <= 20 and #moves < 2 and row.move <= 165 then
local dup = false
for _, m in ipairs(moves) do if m == row.move then dup = true end end
if not dup then moves[#moves + 1] = row.move end
end
end
if data.generation == 3 then
local list = {}
for _, m in ipairs(moves) do list[#list + 1] = { id = m, pp = data.moves[m].pp, ppUps = 0 } end
local Pokemon = require("src.core.game3.pokemon")
local pid = 0x00C0FFEE
return { species = sp.localKey, level = 20, exp = sp.exp[20], personality = pid, otId = otId, otSecretId = 654,
otName = "ASH", nickname = nick, ivs = { hp = 10, atk = 11, def = 12, spe = 13, spa = 14, spd = 15 },
evs = { hp = 1, atk = 2, def = 3, spe = 4, spa = 5, spd = 6 }, moves = list, friendship = 90, item = 0,
nature = pid % 25, gender = Pokemon.gender(sp.localKey, pid), ability = Pokemon.abilityId(sp.localKey, pid),
abilityNum = 0, metLocation = 1, metLevel = 3, metGame = 4, pokeball = 4, otGender = 0, language = 2, ribbons = 0,
markings = 0, contest = { cool = 0, beauty = 0, cute = 0, smart = 0, tough = 0, sheen = 0 }, isEgg = false,
hp = 40, status = "", pokerus = 0, eggCycles = 0, fatefulEncounter = false, modernFatefulEncounter = false }
end
local list = {}
for _, m in ipairs(moves) do list[#list + 1] = { id = data.moves[m].localKey, pp = data.moves[m].pp, ppUps = 0 } end
local rec = { species = sp.localKey, level = 20, nickname = nick, ot = "ASH", otId = otId,
dvs = { attack = 9, defense = 8, speed = 7, special = 6 }, statExp = { hp = 1, attack = 4, defense = 9, speed = 16, special = 25 },
moves = list }
if data.generation == 2 then rec.experience = sp.exp[20]; rec.happiness = 80 else rec.exp = sp.exp[20] end
return rec
end
local function nativeOf(gen, d, rec)
if gen == 3 then return assert(Protocol.unpackMon3(nil, rec, { strict = true })) end
if gen == 2 then return assert(Protocol.unpackMon2(gameData(d), rec, { strict = true })) end
local m = assert(Protocol.unpackMon(gameData(d), rec, { strict = true }))
m.catchRate = rec.catchRate
return m
end
local function destGame(gen, version, d, existing)
if gen == 3 then
return { session = { version = version, party = { existing }, dex = { seen = {}, owned = {}, caught = {} }, flags = {}, vars = {} } }
end
if gen == 2 then
local was = GameVersion.get()
GameVersion.set(version)
local save = Save2.newGame({ playerName = "GOLD" })
GameVersion.set(was)
save.version = version
save.party = { existing }
return { save = save, data = gameData(d) }
end
return { save = { version = version, party = { existing }, pokedex = { seen = {}, owned = {} } }, data = gameData(d) }
end
local combos = 0
for gs = 1, 3 do
for gd = 1, 3 do
local sv, dv = chosen[gs], chosen[gd]
if sv and dv then
local src, dst = real[sv], real[dv]
local label = sv .. " -> " .. dv
local offered = sample(src, 321, "PIKA")
local existing = nativeOf(gd, dst, sample(dst, 999, "KEEP"))
local game = destGame(gd, dv, dst, existing)
local adapter = Txn.newAdapter(game, dv)
adapter.writer = function() return true end
local sender = Model.new({ version = sv, peerVersion = dv, owned = { { ref = { where = "party", index = 1 }, rec = offered } } })
local rep = sender:choose(1)
T.check(rep.ok, label .. ": offer converts (" .. tostring(rep.blocks[1] and rep.blocks[1].code) .. ")")
if rep.ok then
local payload, digest = sender:payload()
local receiver = Model.new({ version = dv, peerVersion = sv, owned = adapter:owned() })
local ok, why = receiver:receive(payload, function(final) return adapter:validate(final) end)
T.check(ok, label .. ": receiver re-converts and validates in its own game (" .. tostring(why) .. ")")
if ok then
local packedExisting = adapter:pack(existing)
local entry = { key = "room:1:" .. digest, out = { ref = { where = "party", index = 1 },
canonical = TradeConvert.canonical(packedExisting), identity = Txn.identityOf(gd, packedExisting) },
incoming = receiver.peer.final, incomingIdentity = Txn.identityOf(gd, receiver.peer.final) }
local status = Txn.applyEntry(adapter, entry)
T.eq(status, "applied", label .. ": applies into the destination save structure")
local s = adapter:save()
local got = s.party[1]
local repacked = adapter:pack(got)
local view = Project.read(repacked, dst)
T.check(view ~= nil and view.national == 25, label .. ": the result reads back as Pikachu in " .. dv)
local again = gd == 3 and Protocol.unpackMon3(nil, repacked, { strict = true })
or gd == 2 and Protocol.unpackMon2(gameData(dst), repacked, { strict = true })
or Protocol.unpackMon(gameData(dst), repacked, { strict = true })
T.check(again ~= nil, label .. ": the stored mon decodes strictly as a valid " .. dv .. " mon")
if gd == 1 then
local report = SaveData.validate(s, gameData(dst))
T.eq(#report.lostMons, 0, label .. ": Gen 1 save validation keeps it")
T.check(s.pokedex.owned[got.species], label .. ": dex owned")
elseif gd == 2 then
local report = Save2.validate(s)
T.check(Save2.emptyReport(report), label .. ": Gen 2 save validation is clean")
T.check(s.pokedex.caught[got.species], label .. ": dex caught")
else
T.eq(got.friendship, 70, label .. ": Gen 3 traded friendship")
T.check(s.dex.seen[got.species], label .. ": dex seen")
end
T.eq(Txn.applyEntry(adapter, entry), "already", label .. ": a second apply is refused")
combos = combos + 1
end
end
end
end
end
T.check(combos > 0, "at least one combination ran")
print(("[info] union_trade_combos: %d combinations"):format(combos))
T.finish("union_trade_combos")