mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 20:20:19 +02:00
skin studio updates, save sync CLOSES #1533
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
|
||||
local dir = os.getenv("SHOT_DIR") or "/tmp/syncmodal"
|
||||
os.execute('mkdir -p "' .. dir .. '" 2>/dev/null')
|
||||
love.window.setMode(1024, 768, { resizable = true, highdpi = true })
|
||||
U.wait(2)
|
||||
|
||||
local eng = {
|
||||
phase = "idle", status = "Ready", conflicts = {}, state = { enabled = true },
|
||||
isLinked = false, isBusy = false,
|
||||
linked = function(self) return self.isLinked end,
|
||||
busy = function(self) return self.isBusy end,
|
||||
createAccount = function(self)
|
||||
self.isLinked = true
|
||||
self.codes = { code1 = "1234-5678", code2 = "8765-4321" }
|
||||
self.status = "Sync account created"
|
||||
return true
|
||||
end,
|
||||
linkDevice = function(self) self.isLinked = true return true end,
|
||||
syncNow = function(self) self.status = "Checking for changes..." return true end,
|
||||
unlink = function(self) self.isLinked, self.codes = false, nil return true end,
|
||||
shareMods = function(self) self.shareCode = "K7QW3M" return true end,
|
||||
fetchShare = function(self) return true end,
|
||||
applyModPlan = function(self) self.modPlan = nil return true end,
|
||||
resolveConflict = function(self) self.conflicts = {} self.phase = "idle" return true end,
|
||||
}
|
||||
|
||||
local imp = RomImporter.new(function() end, { launcher = true })
|
||||
imp._sync = eng
|
||||
imp._syncTransportOk = true
|
||||
|
||||
local pending = nil
|
||||
love.draw = function()
|
||||
imp:draw()
|
||||
if pending then
|
||||
local path = pending
|
||||
pending = nil
|
||||
love.graphics.captureScreenshot(function(imagedata)
|
||||
local f = io.open(path, "wb")
|
||||
if f then f:write(imagedata:encode("png"):getString()) f:close() end
|
||||
end)
|
||||
end
|
||||
end
|
||||
local function shot(name)
|
||||
pending = dir .. "/" .. name
|
||||
for _ = 1, 90 do
|
||||
if not pending then break end
|
||||
imp:update(1 / 60)
|
||||
coroutine.yield()
|
||||
end
|
||||
U.wait(3)
|
||||
local f = io.open(dir .. "/" .. name, "rb")
|
||||
U.log(f and "shot" or "FAIL shot", name)
|
||||
if f then f:close() end
|
||||
end
|
||||
|
||||
imp:_openSync()
|
||||
U.wait(3)
|
||||
U.log("modal view:", imp._syncModal.view, "linked:", tostring(eng:linked()))
|
||||
shot("sync_new.png")
|
||||
|
||||
imp:_syncView("link")
|
||||
imp:_syncFocusField("code1")
|
||||
imp:textinput("1234-5678")
|
||||
imp:_syncFocusField("code2")
|
||||
imp:textinput("8765ab4321")
|
||||
U.wait(2)
|
||||
U.log("codes typed:", imp._syncModal.code1, imp._syncModal.code2)
|
||||
shot("sync_link.png")
|
||||
|
||||
imp:_syncView("home")
|
||||
imp:_syncCreate()
|
||||
U.wait(2)
|
||||
U.log("codes shown:", eng.codes.code1, eng.codes.code2)
|
||||
shot("sync_codes.png")
|
||||
|
||||
eng.isBusy = true
|
||||
eng.status = "Uploading saves..."
|
||||
U.wait(2)
|
||||
shot("sync_busy.png")
|
||||
eng.isBusy = false
|
||||
eng.status = "Ready"
|
||||
|
||||
imp:_syncView("mods")
|
||||
imp:_syncShareMods()
|
||||
eng.modPlan = {
|
||||
indexes = { "https://example.invalid/index.json" },
|
||||
toInstall = { { id = "jp_green" }, { id = "randomizer" } },
|
||||
toEnable = { { id = "jp_green", version = "red" } },
|
||||
missing = { { id = "gone" } },
|
||||
}
|
||||
U.wait(2)
|
||||
U.log("share code:", tostring(eng.shareCode))
|
||||
shot("sync_mods.png")
|
||||
|
||||
eng.phase = "conflict"
|
||||
eng.status = "These saves were played at the same time."
|
||||
eng.conflicts = { {
|
||||
key = "red/abcd1234", version = "red", overlap = true,
|
||||
localMeta = { savedAt = os.time(), sessionStart = os.time() - 3600,
|
||||
summary = { name = "ASH", badges = 3, timeText = "5:42", dexCount = 40 } },
|
||||
remoteMeta = { savedAt = os.time() - 600, sessionStart = os.time() - 4200,
|
||||
summary = { name = "ASH", badges = 4, timeText = "6:10", dexCount = 44 } },
|
||||
} }
|
||||
U.wait(2)
|
||||
shot("sync_conflict.png")
|
||||
|
||||
love.window.setMode(520, 760, { resizable = true, highdpi = true })
|
||||
U.wait(3)
|
||||
shot("sync_conflict_narrow.png")
|
||||
|
||||
eng.phase = "idle"
|
||||
eng.conflicts = {}
|
||||
imp:_syncView("home")
|
||||
U.wait(2)
|
||||
shot("sync_home_narrow.png")
|
||||
|
||||
imp:_closeSync()
|
||||
U.wait(2)
|
||||
U.log("closed:", tostring(imp._syncModal == nil))
|
||||
shot("sync_closed.png")
|
||||
|
||||
U.log("done")
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -0,0 +1,115 @@
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
local Fetch = require("src.net.Fetch")
|
||||
|
||||
local dir = os.getenv("SHOT_DIR") or "/tmp/skinurl"
|
||||
os.execute('mkdir -p "' .. dir .. '" 2>/dev/null')
|
||||
love.window.setMode(1024, 768, { resizable = true, highdpi = true })
|
||||
U.wait(2)
|
||||
|
||||
local imp = RomImporter.new(function() end, {
|
||||
launcher = true,
|
||||
onOpenSkinStudio = function() end,
|
||||
})
|
||||
|
||||
local pending = nil
|
||||
love.draw = function()
|
||||
imp:draw()
|
||||
if pending then
|
||||
local path = pending
|
||||
pending = nil
|
||||
love.graphics.captureScreenshot(function(imagedata)
|
||||
local f = io.open(path, "wb")
|
||||
if f then f:write(imagedata:encode("png"):getString()) f:close() end
|
||||
end)
|
||||
end
|
||||
end
|
||||
local function shot(name)
|
||||
pending = dir .. "/" .. name
|
||||
for _ = 1, 90 do
|
||||
if not pending then break end
|
||||
imp:update(1 / 60)
|
||||
coroutine.yield()
|
||||
end
|
||||
U.wait(3)
|
||||
local f = io.open(dir .. "/" .. name, "rb")
|
||||
U.log(f and "shot" or "FAIL shot", name)
|
||||
if f then f:close() end
|
||||
end
|
||||
|
||||
imp:_switchTab("skins")
|
||||
U.wait(3)
|
||||
|
||||
U.log("name from url:", RomImporter.skinUrlName(
|
||||
"https://example.com/pads/Neon.deltaskin"))
|
||||
U.log("name from cfg:", RomImporter.skinUrlName(
|
||||
"https://example.com/overlay.cfg"))
|
||||
|
||||
imp.skinUrl = "https://example.com/pads/neon.deltaskin"
|
||||
imp._skinUrlFocus = true
|
||||
U.wait(2)
|
||||
shot("skins_url_typed.png")
|
||||
|
||||
local realDownload, realPoll, realRelease =
|
||||
Fetch.download, Fetch.poll, Fetch.release
|
||||
local state = { status = "pending", progress = 0.4 }
|
||||
Fetch.download = function(url, dest)
|
||||
U.log("download:", url, "->", dest)
|
||||
return 1
|
||||
end
|
||||
Fetch.poll = function() return state end
|
||||
Fetch.release = function() end
|
||||
|
||||
imp._skinUrlFocus = false
|
||||
imp:_addSkinFromUrl()
|
||||
U.log("in flight:", tostring(imp._skinFetch ~= nil))
|
||||
U.wait(2)
|
||||
shot("skins_url_downloading.png")
|
||||
|
||||
state = { status = "error", err = "could not resolve host" }
|
||||
imp:_pumpSkinFetch()
|
||||
U.log("failure notice:", imp._skinNotice.text)
|
||||
U.wait(2)
|
||||
shot("skins_url_failed.png")
|
||||
|
||||
Fetch.download, Fetch.poll, Fetch.release = realDownload, realPoll, realRelease
|
||||
|
||||
local staged = TouchSkin.export(
|
||||
assert(TouchSkin.load("assets/skins/gb_anim", "gb_anim")),
|
||||
"skins/url_probe.zip")
|
||||
local raw = love.filesystem.read("skins/url_probe.zip")
|
||||
love.filesystem.remove("skins/url_probe.zip")
|
||||
U.log("staged:", tostring(staged), "bytes:", raw and #raw or 0)
|
||||
imp:_installSkinData("downloaded_pad.zip", raw)
|
||||
U.log("install notice:", imp._skinNotice.text)
|
||||
U.wait(2)
|
||||
shot("skins_url_installed.png")
|
||||
|
||||
local skins = imp:_ensureSkins(true)
|
||||
for _, e in ipairs(skins) do
|
||||
U.log((" %s format=%s buttons=%d"):format(e.id, tostring(e.format),
|
||||
e.controls))
|
||||
end
|
||||
|
||||
imp._skinActions = { id = skins[1] and skins[1].id }
|
||||
U.wait(2)
|
||||
shot("skins_actions_sheet.png")
|
||||
|
||||
for _, kind in ipairs({ "native", "retroarch", "delta" }) do
|
||||
local path = imp:_exportSkin(skins[1].id, kind)
|
||||
U.log("export " .. kind .. ":", tostring(path))
|
||||
end
|
||||
imp._skinActions = nil
|
||||
U.wait(2)
|
||||
shot("skins_exported.png")
|
||||
|
||||
love.window.setMode(520, 820, { resizable = true, highdpi = true })
|
||||
U.wait(3)
|
||||
shot("skins_url_narrow.png")
|
||||
|
||||
U.log("done")
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -0,0 +1,227 @@
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local TouchControls = require("src.core.TouchControls")
|
||||
local TouchSkin = require("src.core.TouchSkin")
|
||||
local Playfield = require("src.render.Playfield")
|
||||
|
||||
local dir = os.getenv("SHOT_DIR") or "/tmp/skin-cutout"
|
||||
local gen2 = game.overworld == nil
|
||||
local failures, checks = 0, 0
|
||||
|
||||
local SKIN = [[
|
||||
return {
|
||||
name = "containment_probe",
|
||||
pages = {
|
||||
{
|
||||
name = "probe",
|
||||
fullScreen = true,
|
||||
viewport = { x = 0.25, y = 0.1, w = 0.5, h = 0.6 },
|
||||
controls = { { bind = "nul", x = 0.5, y = 0.92, w = 0.04, h = 0.04 } },
|
||||
},
|
||||
},
|
||||
}
|
||||
]]
|
||||
love.filesystem.createDirectory("skins/containment_probe")
|
||||
assert(love.filesystem.write("skins/containment_probe/skin.lua", SKIN))
|
||||
|
||||
love.window.setMode(1280, 720, { resizable = true, highdpi = true })
|
||||
love.graphics.setBackgroundColor(0, 0, 0, 1)
|
||||
U.wait(2)
|
||||
|
||||
local options = gen2 and game.options or game.save.options
|
||||
options.touchControls = { enabled = true, skin = "containment_probe" }
|
||||
options.tilt = 0
|
||||
options.zoom = 0
|
||||
options.pipelines = {}
|
||||
options.videoMode = "windowed"
|
||||
options.faithfulRes = 0
|
||||
game:applyOptions()
|
||||
love.window.setMode(1280, 720, { resizable = true, highdpi = true })
|
||||
U.wait(4)
|
||||
|
||||
U.log("gen:", gen2 and 2 or 1, "skin:", tostring(TouchControls.skinId),
|
||||
"err:", tostring(TouchControls.skinError))
|
||||
U.log("drawable:", TouchSkin.drawable(), "hasViewport:", TouchSkin.hasViewport())
|
||||
|
||||
local function cutoutPx()
|
||||
local pw, ph = love.graphics.getPixelDimensions()
|
||||
local x, y, w, h = Playfield.cutout(pw, ph)
|
||||
return x, y, w, h, pw, ph
|
||||
end
|
||||
|
||||
local cx, cy, cw, ch, pw, ph = cutoutPx()
|
||||
if not cx then
|
||||
U.log("FAIL no cutout is active; nothing to prove")
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
U.log(("cutout px: %d,%d %dx%d in %dx%d"):format(cx, cy, cw, ch, pw, ph))
|
||||
|
||||
local INSET = 4
|
||||
local function scan(label, data)
|
||||
local w, h = data:getWidth(), data:getHeight()
|
||||
local sx, sy = w / pw, h / ph
|
||||
local x1, y1 = math.floor(cx * sx) - INSET, math.floor(cy * sy) - INSET
|
||||
local x2 = math.ceil((cx + cw) * sx) + INSET
|
||||
local y2 = math.ceil((cy + ch) * sy) + INSET
|
||||
local bad, firstX, firstY, worst = 0, nil, nil, 0
|
||||
local inked = 0
|
||||
local step = math.max(2, math.floor(math.min(w, h) / 360))
|
||||
for y = 0, h - 1, step do
|
||||
for x = 0, w - 1, step do
|
||||
local r, g, b = data:getPixel(x, y)
|
||||
local lit = math.max(r, g, b)
|
||||
local outside = x < x1 or x >= x2 or y < y1 or y >= y2
|
||||
if outside then
|
||||
if lit > 0.02 then
|
||||
bad = bad + 1
|
||||
if not firstX then firstX, firstY = x, y end
|
||||
if lit > worst then worst = lit end
|
||||
end
|
||||
elseif lit > 0.02 then
|
||||
inked = inked + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
checks = checks + 1
|
||||
if bad > 0 then
|
||||
failures = failures + 1
|
||||
U.log(("FAIL %s: %d lit samples outside the cutout (first %d,%d, max %.2f)")
|
||||
:format(label, bad, firstX, firstY, worst))
|
||||
elseif inked == 0 then
|
||||
failures = failures + 1
|
||||
U.log("FAIL " .. label .. ": nothing drew inside the cutout either")
|
||||
else
|
||||
U.log(("ok %s: contained (%d lit samples inside)"):format(label, inked))
|
||||
end
|
||||
end
|
||||
|
||||
local pending = nil
|
||||
local function probe(label)
|
||||
U.wait(2)
|
||||
pending = label
|
||||
love.graphics.captureScreenshot(function(data)
|
||||
scan(pending, data)
|
||||
pending = nil
|
||||
end)
|
||||
for _ = 1, 180 do
|
||||
if not pending then break end
|
||||
coroutine.yield()
|
||||
end
|
||||
if pending then
|
||||
failures = failures + 1
|
||||
U.log("FAIL " .. tostring(pending) .. ": screenshot never arrived")
|
||||
pending = nil
|
||||
end
|
||||
if os.getenv("SHOT_PNG") == "1" then
|
||||
U.shot(game, ("%s/%s.png"):format(dir, label:gsub("[^%w]+", "_")))
|
||||
end
|
||||
end
|
||||
|
||||
if gen2 then
|
||||
for i = 1, 2 do
|
||||
probe("gold_boot_" .. i)
|
||||
U.wait(60)
|
||||
end
|
||||
for _ = 1, 240 do
|
||||
if game.world and game.world.map then break end
|
||||
game.input.pressQueue[#game.input.pressQueue + 1] = "start"
|
||||
U.wait(4)
|
||||
end
|
||||
if game.world and game.world.map then
|
||||
probe("gold_overworld")
|
||||
local Zoom = require("src.render.Zoom")
|
||||
Zoom.allowSurvey = true
|
||||
for _, off in ipairs({ -2, -1, 1, 2 }) do
|
||||
Zoom.offset = off
|
||||
probe("gold_zoom_" .. (off < 0 and "out" or "in") .. math.abs(off))
|
||||
end
|
||||
Zoom.offset = 0
|
||||
local function tap(button, frames)
|
||||
game.input.pressQueue[#game.input.pressQueue + 1] = button
|
||||
game.input.state[button] = true
|
||||
U.wait(2)
|
||||
game.input.state[button] = false
|
||||
U.wait(frames or 12)
|
||||
end
|
||||
tap("start", 24)
|
||||
probe("gold_start_menu")
|
||||
tap("b", 12)
|
||||
probe("gold_after_menu")
|
||||
else
|
||||
U.log("FAIL gold world never booted")
|
||||
failures = failures + 1
|
||||
end
|
||||
else
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) }
|
||||
game.save.player.name = "bryan"
|
||||
U.teleport(game, "PALLET_TOWN", 10, 8, "down")
|
||||
U.wait(12)
|
||||
probe("red_overworld")
|
||||
|
||||
local Zoom = require("src.render.Zoom")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
Zoom.allowSurvey = true
|
||||
local lo, hi = Zoom.offsetRange(Renderer:fitScale())
|
||||
for off = lo, hi do
|
||||
Zoom.offset = off
|
||||
probe("red_zoom_" .. Zoom.offsetLabel(off))
|
||||
end
|
||||
Zoom.offset = 0
|
||||
|
||||
U.tap(game, "start")
|
||||
U.wait(20)
|
||||
probe("red_start_menu")
|
||||
|
||||
local function stress(label, mutate)
|
||||
local state = game.stack:top()
|
||||
local original = state.draw
|
||||
state.draw = function(...)
|
||||
original(...)
|
||||
mutate()
|
||||
end
|
||||
probe(label)
|
||||
state.draw = original
|
||||
end
|
||||
stress("red_screen_veil", function()
|
||||
Renderer.screenVeil = { 1, 0.85 }
|
||||
end)
|
||||
stress("red_battle_wipe", function()
|
||||
Renderer.battleWipe = { style = "spiralin", prog = 0.45 }
|
||||
end)
|
||||
stress("red_letterbox_paper", function()
|
||||
Renderer.extendedWorldBand = true
|
||||
end)
|
||||
stress("red_ui_anchor", function()
|
||||
Renderer.uiCentered = false
|
||||
Renderer:setUIAnchor(0, 96, 160, 48, "bottom")
|
||||
end)
|
||||
U.tap(game, "b")
|
||||
U.wait(10)
|
||||
|
||||
game.save.options.uiLayout = "dynamic"
|
||||
game:applyOptions()
|
||||
U.tap(game, "start")
|
||||
U.wait(20)
|
||||
probe("red_dynamic_start_menu")
|
||||
U.tap(game, "b")
|
||||
U.wait(10)
|
||||
game.save.options.uiLayout = "centered"
|
||||
game:applyOptions()
|
||||
|
||||
local Tilt = require("src.render.Tilt")
|
||||
game.save.options.tilt = 1
|
||||
Tilt.applyOptions(game.save.options)
|
||||
U.wait(30)
|
||||
probe("red_tilt")
|
||||
game.save.options.tilt = 0
|
||||
Tilt.applyOptions(game.save.options)
|
||||
U.wait(20)
|
||||
end
|
||||
|
||||
U.log(("done: %d/%d frames contained, %d failures")
|
||||
:format(checks - failures, checks, failures))
|
||||
love.event.quit()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
Reference in New Issue
Block a user