mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
180 lines
6.0 KiB
Lua
180 lines
6.0 KiB
Lua
-- Driver: boots Gold straight into an online arena battle
|
|
-- (POKEPORT_ARENA_SPEC), plays it against an in-process headless guest over
|
|
-- Net.loopbackPair, and screenshots the battle and the launcher the arena
|
|
-- returns to. The Gen 2 peer of tests/drivers/arena_boot_loopback.lua.
|
|
--
|
|
-- SHOT_DIR=/tmp/shots POKEPORT_TOUCH=0 POKEPORT_VERSION=gold \
|
|
-- POKEPORT_IDENTITY=<a sandbox holding a gold cache> POKEPORT_SPEED=10 \
|
|
-- POKEPORT_ARENA_SPEC=tests/drivers/arena_boot_gen2_spec.lua \
|
|
-- POKEPORT_DRIVER=tests/drivers/arena_boot_gen2_loopback.lua love .
|
|
--
|
|
-- POKEPORT_SPEED matters here: the headless guest takes that many fixed steps
|
|
-- per rendered frame, and it is the pacing side of the lockstep, so at 1 the
|
|
-- match runs at a fraction of real speed.
|
|
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
os.execute('mkdir -p "' .. DIR .. '" 2>/dev/null')
|
|
|
|
local ctx = _G.POKEPORT_ARENA_TEST
|
|
if not ctx then
|
|
U.log("FAIL POKEPORT_ARENA_SPEC did not run (no loopback context)")
|
|
love.event.quit(1)
|
|
return
|
|
end
|
|
|
|
local arena = game.stack.states[1]
|
|
U.log("arena state:", tostring(arena and arena.screenId),
|
|
"stage:", tostring(arena and arena.stage))
|
|
if not (arena and arena.screenId == "Gen2ArenaState") then
|
|
U.log("FAIL the game did not boot into the Gen 2 ArenaState")
|
|
love.event.quit(1)
|
|
return
|
|
end
|
|
if arena.stage ~= "running" then
|
|
U.log("FAIL the arena battle did not construct:", tostring(arena.message))
|
|
love.event.quit(1)
|
|
return
|
|
end
|
|
|
|
local shots = {
|
|
[90] = DIR .. "/arena2_0_battle.png",
|
|
[400] = DIR .. "/arena2_1_turns.png",
|
|
}
|
|
|
|
local function verify()
|
|
local ok = true
|
|
for _, path in pairs(shots) do
|
|
local f = io.open(path, "rb")
|
|
if f then
|
|
f:close()
|
|
U.log("captured", path)
|
|
else
|
|
ok = false
|
|
U.log("FAIL screenshot did not reach disk:", path)
|
|
end
|
|
end
|
|
U.log("arena finished: host result", tostring(ctx.result),
|
|
"guest result", tostring(ctx.guestResult))
|
|
if ctx.result ~= "win" and ctx.result ~= "lose" and ctx.result ~= "draw" then
|
|
ok = false
|
|
U.log("FAIL the arena reported no battle result")
|
|
end
|
|
if ctx.result == "win" and ctx.guestResult ~= "lose" then ok = false end
|
|
if ctx.result == "lose" and ctx.guestResult ~= "win" then ok = false end
|
|
if not ok then U.log("FAIL the two sides disagree on the outcome") end
|
|
return ok
|
|
end
|
|
|
|
local baseDraw = love.draw
|
|
local launcherShot, launcherFrames = false, 0
|
|
love.draw = function()
|
|
baseDraw()
|
|
-- ctx.readyForShot: the guest finished too (or ran out of grace), so the
|
|
-- launcher behind the arena is settled and both results can be compared.
|
|
if ctx.readyForShot and not launcherShot then
|
|
launcherFrames = launcherFrames + 1
|
|
if launcherFrames > 45 then
|
|
launcherShot = true
|
|
love.graphics.captureScreenshot(function(imagedata)
|
|
local encoded = imagedata:encode("png")
|
|
local path = DIR .. "/arena2_2_launcher.png"
|
|
local f = io.open(path, "wb")
|
|
if f then
|
|
f:write(encoded:getString())
|
|
f:close()
|
|
U.log("captured", path)
|
|
else
|
|
U.log("FAIL screenshot did not reach disk:", path)
|
|
end
|
|
love.event.quit(verify() and 0 or 1)
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
local LinkBattle2 = require("src.link.LinkBattle2")
|
|
|
|
-- The guest has no window: a bare pad, a bare stack and no save of its own,
|
|
-- which is everything src/ui/gen2/BattleState.lua asks a game for.
|
|
local guestInput = {
|
|
pressed = { a = true },
|
|
state = {},
|
|
pressQueue = {},
|
|
wasPressed = function(_, button) return button == "a" end,
|
|
isDown = function() return false end,
|
|
step = function() end,
|
|
}
|
|
local guestStack = { list = {} }
|
|
function guestStack:push(state, ...)
|
|
table.insert(self.list, state)
|
|
if state.enter then state:enter(...) end
|
|
end
|
|
function guestStack:pop() return table.remove(self.list) end
|
|
function guestStack:top() return self.list[#self.list] end
|
|
function guestStack:clear() self.list = {} end
|
|
function guestStack:update(dt)
|
|
local top = self:top()
|
|
if top and top.update then top:update(dt) end
|
|
end
|
|
|
|
local guestGame = {
|
|
data = game.data,
|
|
input = guestInput,
|
|
stack = guestStack,
|
|
options = {},
|
|
save = { party = {}, player = { name = "SILVER" }, inventory = {},
|
|
pokedex = { seen = {}, caught = {} } },
|
|
}
|
|
local guest, why = LinkBattle2.newGuest(guestGame, ctx.guestNet, {
|
|
myParty = ctx.guestParty,
|
|
theirParty = ctx.hostParty or (arena.spec and arena.spec.myParty),
|
|
theirName = "GOLD",
|
|
seed = ctx.seed,
|
|
verdict = "full",
|
|
strict = true,
|
|
keepNetOpen = true,
|
|
})
|
|
if not guest then
|
|
U.log("FAIL headless guest could not start:", tostring(why))
|
|
love.event.quit(1)
|
|
return
|
|
end
|
|
guest.onFinish = function(result) ctx.guestResult = result end
|
|
guestStack:push(guest)
|
|
U.log("headless guest running")
|
|
|
|
-- The guest runs several fixed steps per rendered frame: it is the pacing
|
|
-- side of the lockstep (the host waits for its action), so at one step a
|
|
-- frame the whole match would crawl at half real speed.
|
|
local GUEST_STEPS = tonumber(os.getenv("POKEPORT_SPEED")) or 6
|
|
for step = 1, 6000 do
|
|
table.insert(game.input.pressQueue, "a")
|
|
for _ = 1, GUEST_STEPS do guestStack:update(1 / 60) end
|
|
if shots[step] then game.capturePath = shots[step] end
|
|
coroutine.yield()
|
|
if ctx.result then break end
|
|
end
|
|
|
|
if not ctx.result then
|
|
U.log("FAIL the arena battle never finished")
|
|
love.event.quit(1)
|
|
return
|
|
end
|
|
|
|
-- The host reports the moment its own screen pops; the guest is still
|
|
-- draining the last of its message queue, so give it its own frames rather
|
|
-- than reading a result it has not written yet.
|
|
for _ = 1, 600 do
|
|
if ctx.guestResult then break end
|
|
for _ = 1, GUEST_STEPS do guestStack:update(1 / 60) end
|
|
coroutine.yield()
|
|
end
|
|
ctx.readyForShot = true
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|