mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
151 lines
6.6 KiB
Lua
151 lines
6.6 KiB
Lua
-- Driver (#222): a completed LAN link trade must autosave immediately, so a
|
|
-- player who quits before touching the START menu keeps the received mon and
|
|
-- cannot clone the sent one by resetting.
|
|
--
|
|
-- pokered engine/link/cable_club.asm: the Cable Club calls SaveSAVtoSRAM
|
|
-- (engine/menus/save.asm) right after every trade commits, so the swap is on
|
|
-- the cartridge the instant it happens. Our LinkState:updateTrade "done"
|
|
-- branch swaps game.save.party in memory (TradeSession:apply) but, before the
|
|
-- fix, never persisted it -- SaveData.load then returned the pre-trade party
|
|
-- from disk, losing the received mon and re-materializing the sent one (the
|
|
-- classic reset-to-clone vector).
|
|
--
|
|
-- This drives the real receiver side: lay a baseline disk save with a PIDGEY
|
|
-- party (the "player saved earlier" state), negotiate a TradeSession to
|
|
-- "done" against a peer's RATTATA (RATTATA has no trade evolution, so the run
|
|
-- is deterministic), hand it to a live LinkState so the completion branch
|
|
-- runs, then RELOAD FROM DISK and assert the on-disk party holds the received
|
|
-- RATTATA and no stale PIDGEY -- fails while the autosave is missing, passes
|
|
-- once updateTrade calls game:writeSave().
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
local Protocol = require("src.link.Protocol")
|
|
local LinkState = require("src.link.LinkState")
|
|
local Net = require("src.link.Net")
|
|
local SaveData = require("src.core.SaveData")
|
|
local TextBox = require("src.render.TextBox")
|
|
local TradeAnim = require("src.ui.TradeAnim")
|
|
|
|
local function topIs(cls)
|
|
return getmetatable(game.stack:top()) == cls
|
|
end
|
|
|
|
-- (1) a real overworld so writeSave's captureSave has a world to stamp
|
|
U.teleport(game, "PALLET_TOWN", 5, 6, "down")
|
|
U.wait(5)
|
|
|
|
-- (2) baseline party + baseline disk save: this is the state the player
|
|
-- would fall back to if the trade never persisted (must NOT contain RATTATA)
|
|
game.save.party = { Pokemon.new(game.data, "PIDGEY", 10) }
|
|
local version = game.save.version
|
|
game:writeSave()
|
|
U.shot(game, DIR .. "/bug222_00_before.png")
|
|
|
|
local baseline = SaveData.load(version)
|
|
U.log("baseline on disk:", baseline and baseline.party and baseline.party[1]
|
|
and baseline.party[1].species, "count:",
|
|
baseline and baseline.party and #baseline.party)
|
|
assert(baseline and baseline.party and baseline.party[1]
|
|
and baseline.party[1].species == "PIDGEY",
|
|
"#222 setup: baseline disk save must hold the PIDGEY party")
|
|
|
|
-- (3) drive two TradeSessions to "done" exactly like run_link_tests.lua:
|
|
-- ours is built on the LIVE game.save.party so apply() mutates it in place.
|
|
local peerParty = { Pokemon.new(game.data, "RATTATA", 8) }
|
|
peerParty[1].ot = "CULLEN"; peerParty[1].otId = 16012 -- distinct sender
|
|
local ourTrade = Protocol.TradeSession.new(game.data, game.save.party)
|
|
local peerTrade = Protocol.TradeSession.new(game.data, peerParty)
|
|
ourTrade:handle({ type = "party", mons = Protocol.packParty(peerParty) })
|
|
peerTrade:handle({ type = "party", mons = Protocol.packParty(game.save.party) })
|
|
assert(ourTrade.stage == "picking", "trade should reach picking")
|
|
local pickOurs = ourTrade:pick(1) -- gives PIDGEY
|
|
local pickPeer = peerTrade:pick(1) -- gives RATTATA
|
|
ourTrade:handle(pickPeer)
|
|
peerTrade:handle(pickOurs)
|
|
assert(ourTrade.stage == "confirming", "both picks -> confirming")
|
|
local cOurs = ourTrade:confirm(true)
|
|
local cPeer = peerTrade:confirm(true)
|
|
ourTrade:handle(cPeer)
|
|
peerTrade:handle(cOurs)
|
|
assert(ourTrade.stage == "done", "both confirms -> done")
|
|
|
|
-- (4) attach the completed session to a live LinkState in the trade stage,
|
|
-- fresh Net (no peer) so poll() is empty and the "done" branch runs at once
|
|
local ls = LinkState.new(game)
|
|
ls.net = Net.new()
|
|
ls.peerName = "CULLEN"
|
|
ls.verdict = "full"
|
|
ls.confirmed = true
|
|
ls.stage = "trade"
|
|
ls.trade = ourTrade
|
|
game.stack:push(ls)
|
|
|
|
-- (5) let the stack update ls once: the "done" branch applies the swap into
|
|
-- game.save.party, autosaves (after the fix), pops ls, pushes TradeAnim
|
|
for _ = 1, 120 do
|
|
if game.stack:top() ~= ls then break end
|
|
U.wait(1)
|
|
end
|
|
assert(game.stack:top() ~= ls, "#222: LinkState must leave the trade 'done' branch")
|
|
U.log("in-memory party after trade:", game.save.party[1]
|
|
and game.save.party[1].species)
|
|
|
|
-- advance the TradeAnim until the "Trade completed!" TextBox appears for the
|
|
-- after shot; the disk write already happened in the done branch, this is
|
|
-- cosmetic. A fresh Font require keeps this independent of TradeAnim state.
|
|
local Font = require("src.render.Font")
|
|
local sawText = false
|
|
for _ = 1, 4000 do
|
|
local top = game.stack:top()
|
|
if top == game.overworld then break end
|
|
if getmetatable(top) == TradeAnim and not top.waitingText then
|
|
top:update(1 / 60)
|
|
end
|
|
if topIs(TextBox) then sawText = true; break end
|
|
U.tap(game, "a")
|
|
U.wait(1)
|
|
end
|
|
-- fully reveal the current page ("Trade completed!") so the shot shows text
|
|
-- regardless of the typewriter speed (mirrors trade_anim_test.lua)
|
|
local tb = game.stack:top()
|
|
if getmetatable(tb) == TextBox and tb.pages and tb.pageIndex then
|
|
local page = tb.pages[tb.pageIndex]
|
|
if page then
|
|
tb.shown = {}
|
|
for _, line in ipairs(page) do
|
|
tb.shown[#tb.shown + 1] = Font.encode(line)
|
|
end
|
|
tb.lineIndex = #page
|
|
tb.charIndex = #(tb.shown[#tb.shown] or {})
|
|
tb.done = true
|
|
end
|
|
end
|
|
U.wait(2)
|
|
U.shot(game, DIR .. "/bug222_01_trade_complete.png")
|
|
U.log("reached trade-complete text:", sawText)
|
|
|
|
-- (6) RELOAD FROM DISK and assert the trade was persisted without a manual
|
|
-- save. These fail before the fix (disk still holds the baseline PIDGEY).
|
|
local disk = SaveData.load(version)
|
|
U.log("on disk after trade:", disk and disk.party and disk.party[1]
|
|
and disk.party[1].species, "count:",
|
|
disk and disk.party and #disk.party)
|
|
|
|
assert(disk and disk.party, "#222: a save file must exist on disk")
|
|
assert(disk.party[1] and disk.party[1].species == "RATTATA",
|
|
"#222: trade must autosave -- on-disk lead must be the received RATTATA, was "
|
|
.. tostring(disk.party[1] and disk.party[1].species))
|
|
assert(#disk.party == 1,
|
|
"#222: on-disk party must be exactly the swapped party (no phantom slot), had "
|
|
.. tostring(#disk.party))
|
|
for i, mon in ipairs(disk.party) do
|
|
assert(mon.species ~= "PIDGEY",
|
|
"#222: the sent PIDGEY must not survive on disk (clone vector), found at slot "
|
|
.. tostring(i))
|
|
end
|
|
|
|
U.log("#222 PASS: link trade autosaved; disk holds RATTATA, PIDGEY gone")
|
|
end
|