mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 04:06:10 +02:00
Merge pull request #1523 from bryanthaboi/dev
some bugs and some switch stuff
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local DIR = os.getenv("SHOT_DIR") or "/tmp/bug1412"
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Evolution = require("src.pokemon.Evolution")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local TradeAnim = require("src.ui.TradeAnim")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
|
||||
local function check(label, ok)
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
return ok
|
||||
end
|
||||
|
||||
U.log("Issue #1412: mon pics must be mirrored on the evolution and trade")
|
||||
U.log("screens, and only there; battle keeps the raw orientation.")
|
||||
|
||||
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||
U.wait(10)
|
||||
check("the overworld fixture is ready", game.overworld ~= nil)
|
||||
|
||||
local mon = Pokemon.new(game.data, "PIKACHU", 20)
|
||||
game.save.party = { mon }
|
||||
-- engine/movie/evolution.asm:103
|
||||
Evolution.evolve(game, mon, "RAICHU", nil, "ITEM")
|
||||
U.wait(12)
|
||||
local top = game.stack:top()
|
||||
check("the evolution screen opened",
|
||||
top and top.screenId == "EvolutionState")
|
||||
U.shot(game, DIR .. "/bug1412_evo_old_pikachu.png")
|
||||
|
||||
for _ = 1, 600 do
|
||||
if top.done then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check("the evolution finished", top.done and not top.canceled)
|
||||
U.wait(2)
|
||||
U.shot(game, DIR .. "/bug1412_evo_new_raichu.png")
|
||||
for _ = 1, 60 do
|
||||
if game.stack:top() == game.overworld then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(4)
|
||||
end
|
||||
|
||||
-- engine/movie/trade.asm:751
|
||||
local sent = Pokemon.new(game.data, "SPEAROW", 10)
|
||||
local recv = Pokemon.new(game.data, "FARFETCHD", 10)
|
||||
recv.nickname = "DUX"
|
||||
recv.traded = true
|
||||
recv.ot = "TRAINER"
|
||||
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||
local anim = Screens.push(game, "TradeAnim", {
|
||||
sent = sent, received = recv, enemyName = "TRAINER",
|
||||
})
|
||||
check("the trade cinematic opened", getmetatable(anim) == TradeAnim)
|
||||
for _ = 1, 200 do
|
||||
if anim.sub == "hold" then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check("the sent SPEAROW pic is on screen",
|
||||
anim.phase == "show_player" and anim.sub == "hold" and anim.monVisible)
|
||||
U.shot(game, DIR .. "/bug1412_trade_sent_spearow.png")
|
||||
|
||||
local function topIsText()
|
||||
return getmetatable(game.stack:top()) == TextBox
|
||||
end
|
||||
for _ = 1, 8000 do
|
||||
if anim.phase == "show_enemy" and anim.monVisible then break end
|
||||
if anim.phase == "done" then break end
|
||||
if anim.waitingText or topIsText() then
|
||||
U.wait(1)
|
||||
else
|
||||
anim:update(1 / 60)
|
||||
end
|
||||
end
|
||||
U.wait(2)
|
||||
check("the received FARFETCH'D pic is on screen",
|
||||
anim.phase == "show_enemy" and anim.monVisible)
|
||||
U.shot(game, DIR .. "/bug1412_trade_recv_farfetchd.png")
|
||||
|
||||
U.log("shots in", DIR)
|
||||
U.log("Right: all four pics are mirrored left-right compared to the same")
|
||||
U.log("mon's battle front sprite (compare the hardware capture on #1412:")
|
||||
U.log("PIKACHU's face points the other way than it does in battle).")
|
||||
U.log("Wrong: any of the four matches the battle orientation.")
|
||||
U.log("Battle, pokedex, title, Hall of Fame, League PC, credits, and the")
|
||||
U.log("museum fossils are untouched and must still match the cart.")
|
||||
|
||||
while true do
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
-- data/moves/sfx.asm:46, data/moves/animations.asm:448, audio/engine_2.asm:1077
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||
|
||||
local function check(label, ok)
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
return ok
|
||||
end
|
||||
|
||||
local opts = game.save.options or {}
|
||||
check(("sfx volume %d (needs > 0 to hear anything)"):format(opts.sfxVol or 7),
|
||||
(opts.sfxVol or 7) > 0)
|
||||
check("battle animations are on", opts.animations ~= false)
|
||||
|
||||
local mdef = game.data.moves.LEER
|
||||
check("LEER is in the move table", mdef ~= nil)
|
||||
local anim = mdef and mdef.anim
|
||||
check(("LEER maps to %s pitch %s tempo %s (wants Battle_31 255 64)"):format(
|
||||
anim and tostring(anim.sound) or "?",
|
||||
anim and tostring(anim.pitch) or "?",
|
||||
anim and tostring(anim.tempo) or "?"),
|
||||
anim ~= nil and anim.sound == "Battle_31"
|
||||
and anim.pitch == 255 and anim.tempo == 64)
|
||||
|
||||
local lead = Pokemon.new(game.data, "CHARMANDER", 20)
|
||||
lead.moves = { { id = "LEER", pp = 30 } }
|
||||
game.save.party = { lead }
|
||||
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||
U.wait(15)
|
||||
local ow = game.overworld
|
||||
check("standing on ROUTE_1", ow.map.id == "ROUTE_1")
|
||||
|
||||
local ChipAudio = require("src.core.ChipAudio")
|
||||
local renders = {}
|
||||
local origNewSfx = ChipAudio.newSfx
|
||||
ChipAudio.newSfx = function(data, name, pitch, tempo, header, plain)
|
||||
renders[#renders + 1] = { name = name, pitch = pitch, tempo = tempo,
|
||||
plain = plain }
|
||||
return origNewSfx(data, name, pitch, tempo, header, plain)
|
||||
end
|
||||
|
||||
local battle = BattleState.newWild(game, "PIDGEY", 8)
|
||||
battle.onFinish = function() end
|
||||
ow:pushBattle(battle)
|
||||
|
||||
local function waitPhase(phase, tries)
|
||||
for _ = 1, tries do
|
||||
if battle.phase == phase then return true end
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
end
|
||||
return battle.phase == phase
|
||||
end
|
||||
|
||||
U.log("")
|
||||
U.log("LISTEN: the cart's LEER opens with a short PIERCING high tone")
|
||||
U.log(" (about a third of a second, while the seed-drop noise is still")
|
||||
U.log(" running) and only then falls to the slow low buzz. Before the")
|
||||
U.log(" fix the recomp skipped the piercing part and played the whole")
|
||||
U.log(" sound as the low buzz.")
|
||||
U.log("")
|
||||
|
||||
for round = 1, 3 do
|
||||
check(("round %d: menu is up"):format(round), waitPhase("menu", 150))
|
||||
U.tap(game, "a")
|
||||
check(("round %d: move list is up"):format(round),
|
||||
waitPhase("moveSelect", 12))
|
||||
U.tap(game, "a")
|
||||
U.wait(150)
|
||||
if round == 1 then U.shot(game, DIR .. "/bug1414_leer.png") end
|
||||
end
|
||||
|
||||
ChipAudio.newSfx = origNewSfx
|
||||
local sawPlain, sawSeed = false, false
|
||||
for _, r in ipairs(renders) do
|
||||
if r.name == "Battle_31" and r.pitch == 255 and r.tempo == 64
|
||||
and (r.plain or 0) > 0 then
|
||||
sawPlain = true
|
||||
end
|
||||
if r.name == "Battle_1B" then sawSeed = true end
|
||||
end
|
||||
check("the seed-drop noise (Battle_1B) rendered", sawSeed)
|
||||
check("LEER's Battle_31 rendered with an unmodified opening"
|
||||
.. " while the noise still ran", sawPlain)
|
||||
for _, r in ipairs(renders) do
|
||||
U.log((" rendered %s pitch=%s tempo=%s plainFrames=%s"):format(
|
||||
r.name, tostring(r.pitch), tostring(r.tempo), tostring(r.plain)))
|
||||
end
|
||||
|
||||
U.log("done; the window stays open, pick LEER again to re-listen")
|
||||
end
|
||||
@@ -97,10 +97,11 @@ local launcher = love.graphics.newImage("assets/launcher/gear.png")
|
||||
eq(launcher.path, "assets/launcher/gear.png",
|
||||
"overlay leaves non-generated paths alone")
|
||||
|
||||
-- The real un-prefixed file wins when it exists
|
||||
-- Leftover un-prefixed Red cache must not shadow the versioned copy
|
||||
-- (pre-#899 assets/generated at the save-dir root).
|
||||
love.filesystem.write(PNG, "root-png-bytes")
|
||||
eq(love.filesystem.read(PNG), "root-png-bytes",
|
||||
"overlay prefers the real un-prefixed file over the versioned copy")
|
||||
eq(love.filesystem.read(PNG), "yellow-png-bytes",
|
||||
"overlay prefers the versioned copy over leftover unprefixed")
|
||||
clearPath(PNG)
|
||||
|
||||
-- Blue gets the same treatment
|
||||
@@ -110,7 +111,29 @@ clearPath("yellow/" .. PNG)
|
||||
eq(love.filesystem.read(PNG), "blue-png-bytes",
|
||||
"overlay maps generated reads to blue/ for Blue")
|
||||
|
||||
-- Red has no prefix: nothing is rewritten
|
||||
-- Leftover unprefixed font (old Red root) must not beat Gold's copy.
|
||||
GameVersion.set("gold")
|
||||
local FONT = "assets/generated/fonts/font.png"
|
||||
love.filesystem.write(FONT, "stale-red-font")
|
||||
love.filesystem.write("gold/" .. FONT, "gold-font")
|
||||
eq(love.filesystem.read(FONT), "gold-font",
|
||||
"Gold versioned font wins over leftover unprefixed Red font")
|
||||
clearPath(FONT)
|
||||
clearPath("gold/" .. FONT)
|
||||
|
||||
-- Gold data/generated: fused NX hides gold/maps.lua at the unprefixed path,
|
||||
-- which is the "Gold cache incomplete / maps.lua Does not exist" crash.
|
||||
local MAPS = "data/generated/maps.lua"
|
||||
love.filesystem.write("gold/" .. MAPS, "return { NEW_BARK_TOWN = true }")
|
||||
local mapsChunk = love.filesystem.load(MAPS)
|
||||
eq(type(mapsChunk) == "function" and mapsChunk().NEW_BARK_TOWN or nil, true,
|
||||
"wrapped filesystem.load resolves gold/data/generated/maps.lua")
|
||||
eq(love.filesystem.read(MAPS), "return { NEW_BARK_TOWN = true }",
|
||||
"wrapped filesystem.read returns gold maps.lua bytes")
|
||||
clearPath("gold/" .. MAPS)
|
||||
|
||||
-- Red has no empty prefix anymore (cachePrefix is red/), but with no
|
||||
-- red/ copy the unprefixed miss stays a miss.
|
||||
GameVersion.set("red")
|
||||
clearPath("blue/" .. PNG)
|
||||
eq(love.filesystem.read(PNG), nil, "Red keeps the stock miss behavior")
|
||||
|
||||
@@ -49,5 +49,16 @@ check(CacheFs.mountVersion("yellow") == true, "mountVersion(yellow) returns true
|
||||
eq(love.filesystem.read("assets/generated/fonts/font.png"), "yellow-font",
|
||||
"Yellow mount exposes fonts/font.png at the unprefixed path")
|
||||
|
||||
-- Gold-only: same overlay contract (the Switch intro/maps.lua hole)
|
||||
love.filesystem._mounts = {}
|
||||
GameVersion.set("gold")
|
||||
love.filesystem.write("gold/data/generated/maps.lua", "return { ok = true }")
|
||||
love.filesystem.write("gold/assets/generated/fonts/font.png", "gold-font")
|
||||
check(CacheFs.mountVersion("gold") == true, "mountVersion(gold) returns true")
|
||||
eq(love.filesystem.read("assets/generated/fonts/font.png"), "gold-font",
|
||||
"Gold mount exposes fonts/font.png at the unprefixed path")
|
||||
eq(love.filesystem.read("data/generated/maps.lua"), "return { ok = true }",
|
||||
"Gold mount exposes maps.lua at the unprefixed path")
|
||||
|
||||
GameVersion.set("red")
|
||||
T.finish()
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Gold on fused NX: gold/data/generated exists, but the overlay mount
|
||||
-- onto data/generated often fails. Game2/World used to love.filesystem.load
|
||||
-- the unprefixed path and crash with "Gold cache incomplete / maps.lua
|
||||
-- Does not exist" after a textless intro. CacheFs.loadActive must read the
|
||||
-- versioned file without any mount.
|
||||
-- Self-contained: luajit tests/engine/cache_fs_gold_nx_load_test.lua
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check = T.check
|
||||
local eq = T.eq
|
||||
|
||||
local CacheFs = require("src.import.CacheFs")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
|
||||
local savedVersion = GameVersion.get()
|
||||
local savedPrefix = CacheFs.prefix
|
||||
|
||||
love.filesystem._mounts = {}
|
||||
GameVersion.set("gold")
|
||||
CacheFs.prefix = GameVersion.cachePrefix()
|
||||
|
||||
love.filesystem.write("gold/data/generated/maps.lua",
|
||||
"return { NEW_BARK_TOWN = { id = 1 } }")
|
||||
love.filesystem.write("gold/data/generated/oak_speech.lua",
|
||||
"return { text = { _OakText1 = 'Hello!' } }")
|
||||
love.filesystem.write("gold/data/generated/font.lua",
|
||||
"return { width = 8 }")
|
||||
|
||||
-- Unprefixed path is a miss: the NX mount hole.
|
||||
eq(love.filesystem.read("data/generated/maps.lua"), nil,
|
||||
"unprefixed maps.lua is missing (mount hole)")
|
||||
eq(love.filesystem.load("data/generated/maps.lua"), nil,
|
||||
"love.filesystem.load misses unprefixed maps.lua")
|
||||
|
||||
local maps, mapsErr = CacheFs.loadActive("data/generated/maps.lua")
|
||||
check(maps ~= nil, "loadActive finds gold/data/generated/maps.lua ("
|
||||
.. tostring(mapsErr) .. ")")
|
||||
eq(maps and maps.NEW_BARK_TOWN and maps.NEW_BARK_TOWN.id, 1,
|
||||
"loadActive returns the Gold maps table")
|
||||
|
||||
local oak = CacheFs.loadActive("data/generated/oak_speech.lua")
|
||||
eq(oak and oak.text and oak.text._OakText1, "Hello!",
|
||||
"loadActive returns oak_speech.lua from gold/")
|
||||
|
||||
local font = CacheFs.loadActive("data/generated/font.lua")
|
||||
eq(font and font.width, 8,
|
||||
"loadActive returns font.lua from gold/")
|
||||
|
||||
love.filesystem.remove("gold/data/generated/maps.lua")
|
||||
love.filesystem.remove("gold/data/generated/oak_speech.lua")
|
||||
love.filesystem.remove("gold/data/generated/font.lua")
|
||||
CacheFs.prefix = savedPrefix
|
||||
GameVersion.set(savedVersion)
|
||||
|
||||
T.finish()
|
||||
@@ -16,5 +16,8 @@ check(CacheFs.read("data/generated/audio.lua") == nil,
|
||||
"read is a nil miss headless, not a crash")
|
||||
check(CacheFs.readActive("data/generated/audio.lua") == nil,
|
||||
"readActive is a nil miss headless, not a crash")
|
||||
local loaded, loadErr = CacheFs.loadActive("data/generated/audio.lua")
|
||||
check(loaded == nil,
|
||||
"loadActive is a nil miss headless, not a crash (" .. tostring(loadErr) .. ")")
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -66,4 +66,21 @@ check(mainSrc:find("isNX", 1, true) ~= nil
|
||||
and mainSrc:find('require("src.core.NxAssetOverlay").install()', 1, true) ~= nil,
|
||||
"the overlay install stays gated on Platform.isNX()")
|
||||
|
||||
-- Gold Game2 / World must compile data/generated via CacheFs.loadActive so
|
||||
-- a fused NX boot does not depend on mounting gold/ onto data/generated.
|
||||
local function readSrc(path)
|
||||
local fh = io.open(path, "r")
|
||||
local body = fh and fh:read("*a") or ""
|
||||
if fh then fh:close() end
|
||||
return body
|
||||
end
|
||||
local game2Src = readSrc("src/core/Game2.lua")
|
||||
check(game2Src:find("loadActive", 1, true) ~= nil,
|
||||
"Game2 compiles generated modules through CacheFs.loadActive")
|
||||
check(game2Src:find("loadActive(path)", 1, true) ~= nil,
|
||||
"Game2.loadGenerated calls loadActive")
|
||||
local worldSrc = readSrc("src/world/gen2/World.lua")
|
||||
check(worldSrc:find("loadActive", 1, true) ~= nil,
|
||||
"World's on-disk fallback also uses CacheFs.loadActive")
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -59,7 +59,7 @@ package.loaded["src.import.RomImporter"] = nil
|
||||
RomImporter = require("src.import.RomImporter")
|
||||
|
||||
local function clearSavesInbox()
|
||||
for _, ver in ipairs({ "red", "blue", "yellow" }) do
|
||||
for _, ver in ipairs({ "red", "blue", "yellow", "gold" }) do
|
||||
local dir = "imports/saves/" .. ver
|
||||
for _, name in ipairs(love.filesystem.getDirectoryItems(dir) or {}) do
|
||||
love.filesystem.remove(dir .. "/" .. name)
|
||||
@@ -135,6 +135,8 @@ check(createdDirs["imports/saves/blue"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/blue/")
|
||||
check(createdDirs["imports/saves/yellow"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/yellow/")
|
||||
check(createdDirs["imports/saves/gold"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/gold/")
|
||||
|
||||
-- NXSAV-02: notice/hint includes save dir + per-game imports/saves/<version>/ MTP path
|
||||
ri = freshImporter()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- sourceTreeHasData must use each version's required-file list. Gold's
|
||||
-- cache has no Gen 1 trade art / pikachu.png; validating it against
|
||||
-- REQUIRED_FILES made a Gold source tree look incomplete forever.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check = T.check
|
||||
|
||||
local f = assert(io.open("src/import/RomImporter.lua", "r"))
|
||||
local src = f:read("*a")
|
||||
f:close()
|
||||
|
||||
local start = src:find("local function sourceTreeHasData", 1, true)
|
||||
check(start ~= nil, "sourceTreeHasData is defined")
|
||||
local finish = src:find("\nfunction RomImporter.isReady", start, true)
|
||||
check(finish ~= nil, "sourceTreeHasData ends before isReady")
|
||||
local body = src:sub(start, finish)
|
||||
|
||||
check(body:find("requiredFilesFor", 1, true) ~= nil,
|
||||
"sourceTreeHasData uses requiredFilesFor (Gold override, not Gen 1 only)")
|
||||
check(body:find("ipairs(REQUIRED_FILES)", 1, true) == nil,
|
||||
"sourceTreeHasData does not iterate the Gen 1 REQUIRED_FILES list raw")
|
||||
|
||||
local helperStart = src:find("local function requiredFilesFor", 1, true)
|
||||
check(helperStart ~= nil, "requiredFilesFor helper exists")
|
||||
local helper = src:sub(helperStart, start)
|
||||
check(helper:find("VERSION_REQUIRED_FILES_OVERRIDE", 1, true) ~= nil,
|
||||
"requiredFilesFor consults VERSION_REQUIRED_FILES_OVERRIDE")
|
||||
|
||||
T.finish()
|
||||
@@ -195,6 +195,55 @@ Studio.addControl()
|
||||
eq(#Studio.skin.pages[2].controls, 1, "controls land on the active page")
|
||||
eq(#Studio.skin.pages[1].controls, 0, "and not on the other one")
|
||||
|
||||
-- page orientation lock follows the canvas when Match canvas is on (#1503)
|
||||
session()
|
||||
Studio.canvasIndex = 1
|
||||
Studio.matchOrient = true
|
||||
eq(Studio.cyclePageOrient(1), "portrait", "cycle starts at portrait from unlocked")
|
||||
eq(Studio.page().orient, "portrait", "and stores the lock on the page")
|
||||
eq(Studio.page().name, "portrait", "a generic page is renamed so play can auto-rotate")
|
||||
eq(Studio.canvas().id, "phone_portrait", "and the canvas stays portrait")
|
||||
|
||||
Studio.addPage()
|
||||
eq(Studio.cyclePageOrient(1), "portrait", "the new page starts unlocked, first lock is portrait")
|
||||
Studio.cyclePageOrient(1)
|
||||
eq(Studio.page().orient, "landscape", "second cycle is landscape")
|
||||
eq(Studio.canvas().id, "phone_landscape", "Match canvas flips the mock device with the page")
|
||||
|
||||
Studio.pageIndex = 1
|
||||
Studio.syncCanvasToPage()
|
||||
eq(Studio.canvas().id, "phone_portrait", "switching back to the portrait page restores portrait canvas")
|
||||
|
||||
Studio.setCanvas(2)
|
||||
eq(Studio.pageIndex, 2, "picking a landscape canvas selects the landscape page")
|
||||
|
||||
Studio.matchOrient = false
|
||||
Studio.pageIndex = 1
|
||||
Studio.setCanvas(2)
|
||||
eq(Studio.pageIndex, 1, "Match canvas off leaves the page when the device changes")
|
||||
eq(Studio.canvas().id, "phone_landscape", "and still honours the canvas click")
|
||||
|
||||
-- a RetroArch overlay that already auto-rotates locks itself on open (#1503)
|
||||
session()
|
||||
Studio.matchOrient = false
|
||||
Studio.canvasIndex = 2
|
||||
Studio.skin = assert(TouchSkin.parse([[
|
||||
overlays = 2
|
||||
overlay0_name = "portrait"
|
||||
overlay0_full_screen = true
|
||||
overlay0_descs = 1
|
||||
overlay0_desc0 = "a,0.5,0.5,radial,0.05,0.05"
|
||||
overlay1_name = "landscape"
|
||||
overlay1_full_screen = true
|
||||
overlay1_descs = 1
|
||||
overlay1_desc0 = "a,0.5,0.5,radial,0.05,0.05"
|
||||
]]))
|
||||
Studio.pageIndex = 1
|
||||
check(Studio.applyImportedOrient(), "import of a portrait/landscape pair is automatic")
|
||||
check(Studio.matchOrient, "and turns Match canvas on")
|
||||
eq(Studio.page().name, "landscape", "keeping the landscape canvas already on screen")
|
||||
eq(Studio.page().orient, "landscape", "with the page already locked")
|
||||
|
||||
-- --------------------------------------------------------------- clone
|
||||
|
||||
local source = TouchSkin.load("assets/skins/gb_anim", "gb_anim")
|
||||
|
||||
@@ -89,6 +89,23 @@ check(probe:find("resolve=yellow/assets/generated/fonts/font.png", 1, true) ~= n
|
||||
"probe records versioned font path visibility")
|
||||
check(not probe:find(string.char(0xEA, 0x9B), 1, true),
|
||||
"probe log contains no ROM-like binary")
|
||||
check(probe:find("data/generated/maps.lua", 1, true) ~= nil,
|
||||
"probe records Gold/Gen2 maps.lua visibility")
|
||||
check(probe:find("loadActive=", 1, true) ~= nil,
|
||||
"probe uses loadActive for generated Lua instead of newImage")
|
||||
|
||||
-- Gold maps.lua / gold/ folders: fused NX intro/naming/overworld crash.
|
||||
GameVersion.set("gold")
|
||||
love.filesystem.write("gold/data/generated/maps.lua", "return { NEW_BARK_TOWN = true }")
|
||||
love.filesystem.write("gold/data/generated/oak_speech.lua", "return {}")
|
||||
SwitchDiagnostics.probeAssets("gold")
|
||||
probe = love.filesystem.read("nx-asset-probe.log") or ""
|
||||
check(probe:find("cachePrefix=gold/", 1, true) ~= nil, "probe records gold prefix")
|
||||
check(probe:find("data/generated/maps.lua", 1, true) ~= nil,
|
||||
"probe lists maps.lua (the Gold cache incomplete path)")
|
||||
check(probe:find("list gold", 1, true) ~= nil, "probe lists gold/ folder")
|
||||
check(probe:find("list gold/data/generated", 1, true) ~= nil,
|
||||
"probe lists gold/data/generated/")
|
||||
|
||||
love.system = { getOS = function() return "OS X" end }
|
||||
Platform._resetForTests()
|
||||
@@ -104,5 +121,7 @@ love.filesystem.remove("nx-asset-probe.log")
|
||||
love.filesystem.remove("yellow/assets/generated/fonts/font.png")
|
||||
love.filesystem.remove("yellow/assets/generated/tilesets/reds_house.png")
|
||||
love.filesystem.remove("yellow/assets/generated/sprites/red.png")
|
||||
love.filesystem.remove("gold/data/generated/maps.lua")
|
||||
love.filesystem.remove("gold/data/generated/oak_speech.lua")
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -51,6 +51,7 @@ local page = skin.pages[1]
|
||||
eq(page.name, "shell", "page name")
|
||||
eq(page.imagePath, "img/back.png", "page background path")
|
||||
check(page.fullScreen, "full_screen parsed")
|
||||
check(not page.aspectFromCfg, "a cfg without aspect_ratio does not lock aspect")
|
||||
eq(page.alphaMod, 0.001, "overlay alpha_mod parsed")
|
||||
eq(#page.controls, 7, "seven descs parsed")
|
||||
|
||||
@@ -109,7 +110,7 @@ local sx, sy, sw, sh, fill = TouchSkin.viewport(W, H)
|
||||
eq(sx, 0, "viewport x") eq(sy, 0, "viewport y")
|
||||
eq(sw, 400, "viewport w") eq(sh, 400, "viewport h")
|
||||
check(fill, "viewport fill flag returned")
|
||||
eq(TouchSkin.viewport(W, H), 0, "viewport is window-relative, not page-relative")
|
||||
eq(TouchSkin.viewport(W, H), 0, "without a locked aspect, viewport tracks the window")
|
||||
|
||||
check(TouchControls:touchpressed("f1", at(0.80, 0.75)), "press on A is captured")
|
||||
check(Input:isDown("a"), "skin A presses GB a")
|
||||
@@ -195,6 +196,8 @@ if bundled then
|
||||
for _, btn in ipairs({ "a", "b", "start", "select", "up", "down", "left", "right" }) do
|
||||
check(named[btn], "gb_anim binds GB " .. btn)
|
||||
end
|
||||
check(not TouchSkin.hasOrientPair(bundled),
|
||||
"gb_anim is not a portrait/landscape auto-rotate overlay")
|
||||
end
|
||||
|
||||
local tv = TouchSkin.load("assets/skins/tv_crt", "tv_crt")
|
||||
@@ -322,4 +325,104 @@ end
|
||||
TouchSkin.setActive(nil)
|
||||
TouchControls:setHotkeyHandler(nil)
|
||||
|
||||
-- ------------------------------------------ auto-rotate (#1503)
|
||||
|
||||
-- RetroArch overlays name a portrait page and a landscape page and wire
|
||||
-- overlay_next between them. Play has to pick the one that matches the
|
||||
-- window, or landscape stretches the portrait ranges into wide ovals.
|
||||
local ORIENT_CFG = [[
|
||||
overlays = 2
|
||||
overlay0_name = "portrait"
|
||||
overlay0_full_screen = true
|
||||
overlay0_normalized = true
|
||||
overlay0_aspect_ratio = 0.45
|
||||
overlay0_descs = 1
|
||||
overlay0_desc0 = "a,0.84382,0.69563,radial,0.09722,0.04375"
|
||||
|
||||
overlay1_name = "landscape"
|
||||
overlay1_full_screen = true
|
||||
overlay1_normalized = true
|
||||
overlay1_aspect_ratio = 2.22222222222222
|
||||
overlay1_descs = 1
|
||||
overlay1_desc0 = "a,0.8307031248,0.8614400000,radial,0.0364168752,0.0813300000"
|
||||
]]
|
||||
local orient = assert(TouchSkin.parse(ORIENT_CFG))
|
||||
check(orient.pages[1].aspectFromCfg, "portrait page locks the cfg aspect_ratio")
|
||||
check(orient.pages[2].aspectFromCfg, "landscape page locks the cfg aspect_ratio")
|
||||
eq(orient.pages[1].orient, "portrait", "a portrait page name locks on import")
|
||||
eq(orient.pages[2].orient, "landscape", "a landscape page name locks on import")
|
||||
check(TouchSkin.hasOrientPair(orient), "the pair is an auto-rotate overlay")
|
||||
TouchSkin.setActive(orient)
|
||||
TouchSkin.autoOrient = true
|
||||
|
||||
local PW, PH = 720, 1600
|
||||
TouchSkin.setSurface(0, 0, PW, PH)
|
||||
eq(TouchSkin.page().name, "portrait", "a tall window picks the portrait page")
|
||||
local _, _, pHalfW, pHalfH =
|
||||
TouchSkin.controlGeometry(TouchSkin.page(), TouchSkin.page().controls[1], PW, PH)
|
||||
eq(math.floor(pHalfW + 0.5), 70, "portrait A half-width follows range_x")
|
||||
eq(math.floor(pHalfH + 0.5), 70, "portrait A half-height follows range_y")
|
||||
|
||||
local LW, LH = 1600, 720
|
||||
TouchSkin.setSurface(0, 0, LW, LH)
|
||||
eq(TouchSkin.page().name, "landscape", "a wide window picks the landscape page")
|
||||
local _, _, lHalfW, lHalfH =
|
||||
TouchSkin.controlGeometry(TouchSkin.page(), TouchSkin.page().controls[1], LW, LH)
|
||||
eq(math.floor(lHalfW + 0.5), 58, "landscape A half-width follows the landscape range")
|
||||
eq(math.floor(lHalfH + 0.5), 59, "landscape A half-height follows the landscape range")
|
||||
check(math.abs(lHalfW - lHalfH) < 2, "landscape A stays round instead of stretching")
|
||||
|
||||
-- 16:9 is not the overlay's 20:9. Letterbox to aspect_ratio so A stays round
|
||||
-- instead of filling the window and turning into a tall oval (#1503).
|
||||
TouchSkin.setSurface(0, 0, 1920, 1080)
|
||||
eq(TouchSkin.page().name, "landscape", "16:9 still picks landscape")
|
||||
local _, _, boxW, boxH = TouchSkin.pageBox(TouchSkin.page(), 1920, 1080)
|
||||
eq(math.floor(boxW + 0.5), 1920, "letterbox keeps the 16:9 width")
|
||||
eq(math.floor(boxH + 0.5), 864, "and the overlay's 20:9 height")
|
||||
local _, _, sHalfW, sHalfH =
|
||||
TouchSkin.controlGeometry(TouchSkin.page(), TouchSkin.page().controls[1], 1920, 1080)
|
||||
check(math.abs(sHalfW - sHalfH) < 2, "A stays round on a 16:9 window")
|
||||
|
||||
local nativeOrient = TouchSkin.parseNative(TouchSkin.serialize(orient))
|
||||
check(nativeOrient and nativeOrient.pages[2].aspectFromCfg,
|
||||
"native export keeps the aspect lock")
|
||||
|
||||
-- an explicit lock wins over the page name, so a studio-authored "main"
|
||||
-- page can still auto-rotate in play
|
||||
local LOCK_CFG = [[
|
||||
overlays = 2
|
||||
overlay0_name = "main"
|
||||
overlay0_full_screen = true
|
||||
overlay0_descs = 1
|
||||
overlay0_desc0 = "a,0.5,0.5,radial,0.05,0.05"
|
||||
overlay1_name = "wide"
|
||||
overlay1_full_screen = true
|
||||
overlay1_descs = 1
|
||||
overlay1_desc0 = "a,0.5,0.5,radial,0.05,0.05"
|
||||
]]
|
||||
local locked = assert(TouchSkin.parse(LOCK_CFG))
|
||||
locked.pages[1].orient = "portrait"
|
||||
locked.pages[2].orient = "landscape"
|
||||
TouchSkin.setActive(locked)
|
||||
TouchSkin.setSurface(0, 0, 1600, 720)
|
||||
eq(TouchSkin.page().name, "wide", "orient lock auto-rotates a page not named landscape")
|
||||
local roundTrip = TouchSkin.parseNative(TouchSkin.serialize(locked))
|
||||
eq(roundTrip.pages[1].orient, "portrait", "native export keeps a portrait lock")
|
||||
eq(roundTrip.pages[2].orient, "landscape", "and a landscape lock")
|
||||
|
||||
TouchSkin.setActive(orient)
|
||||
TouchSkin.autoOrient = false
|
||||
TouchSkin.pageIndex = 1
|
||||
eq(TouchSkin.page().name, "portrait",
|
||||
"the studio can keep a portrait page on a landscape canvas")
|
||||
TouchSkin.autoOrient = true
|
||||
TouchSkin.setSurface(nil)
|
||||
|
||||
-- pages that are not a portrait/landscape pair (gb_anim) stay put
|
||||
TouchSkin.setActive(skin)
|
||||
TouchSkin.setSurface(0, 0, LW, LH)
|
||||
eq(TouchSkin.page().name, "shell", "a non-oriented skin does not auto-rotate")
|
||||
TouchSkin.setSurface(nil)
|
||||
TouchSkin.setActive(nil)
|
||||
|
||||
T.finish("touch_skin")
|
||||
|
||||
@@ -26,7 +26,7 @@ end
|
||||
-- Also gates the NX runtime modules and the NX engine suites so an NX
|
||||
-- runtime regression cannot slip past switch-selftest / switch-build.
|
||||
local SWITCH_PATH_REGEX =
|
||||
[[^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics)_test\.lua$|tests/engine/platform_nx)]]
|
||||
[[^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)]]
|
||||
|
||||
local ci = read(".github/workflows/ci.yml")
|
||||
local release = read(".github/workflows/release.yml")
|
||||
@@ -50,6 +50,7 @@ for _, fragment in ipairs({
|
||||
"nx_generated_guard",
|
||||
"nx_yellow_boot",
|
||||
"switch_diagnostics",
|
||||
"cache_fs_gold_nx_load",
|
||||
"tests/engine/platform_nx",
|
||||
}) do
|
||||
mustContain(ci, fragment, "ci.yml path regex NX fragment")
|
||||
@@ -79,6 +80,7 @@ do
|
||||
mustContain(block, "luajit tests/engine/assets_version_fallback_test.lua", "switch-selftest")
|
||||
mustContain(block, "luajit tests/engine/nx_generated_guard_test.lua", "switch-selftest")
|
||||
mustContain(block, "luajit tests/engine/nx_yellow_boot_test.lua", "switch-selftest")
|
||||
mustContain(block, "luajit tests/engine/cache_fs_gold_nx_load_test.lua", "switch-selftest")
|
||||
mustNotContain(block, "continue-on-error:", "switch-selftest")
|
||||
end
|
||||
|
||||
@@ -185,6 +187,7 @@ mustContain(test_sh, "T0 switch transfer docs gate", "scripts/test.sh")
|
||||
mustContain(test_sh, "tests/engine/assets_version_fallback_test.lua", "scripts/test.sh")
|
||||
mustContain(test_sh, "tests/engine/nx_generated_guard_test.lua", "scripts/test.sh")
|
||||
mustContain(test_sh, "tests/engine/nx_yellow_boot_test.lua", "scripts/test.sh")
|
||||
mustContain(test_sh, "tests/engine/cache_fs_gold_nx_load_test.lua", "scripts/test.sh")
|
||||
mustContain(build_doc, "tests/switch_ci_workflows_test.lua", "switch-build.md path list")
|
||||
mustContain(build_doc, "tests/switch_transfer_docs_test.lua", "switch-build.md path list")
|
||||
|
||||
@@ -197,6 +200,7 @@ for _, path in ipairs({
|
||||
"tests/engine/assets_version_fallback_test.lua",
|
||||
"tests/engine/nx_generated_guard_test.lua",
|
||||
"tests/engine/nx_yellow_boot_test.lua",
|
||||
"tests/engine/cache_fs_gold_nx_load_test.lua",
|
||||
"tests/engine/switch_diagnostics_test.lua",
|
||||
"tests/engine/platform_nx_*",
|
||||
}) do
|
||||
|
||||
@@ -30,6 +30,7 @@ mustContain(transfer, "FTP", "transfer")
|
||||
mustContain(transfer, "sdmc:/switch/gen1recomp/", "transfer")
|
||||
mustContain(transfer, "imports/", "transfer")
|
||||
mustContain(transfer, "imports/mods/", "transfer")
|
||||
mustContain(transfer, "gold", "transfer")
|
||||
mustContain(transfer, "1: SD Card", "transfer")
|
||||
mustContain(transfer, "Scan again", "transfer")
|
||||
mustContain(transfer, "documented example", "transfer")
|
||||
@@ -70,6 +71,9 @@ mustContain(install, "PERFORMANCE", "install")
|
||||
mustContain(install, "Stock engine effect", "install")
|
||||
mustContain(install, "## Limitations", "install")
|
||||
mustContain(install, "Launch with title override", "install")
|
||||
mustContain(install, "Gold", "install")
|
||||
mustContain(install, "imports/saves/gold/", "install")
|
||||
mustContain(install, "exports/gold/", "install")
|
||||
mustNotContain(install, "VoxelMod", "install")
|
||||
mustNotContain(install, "switch-development", "install")
|
||||
mustContain(build, "switch-transfer.md", "build")
|
||||
|
||||
Reference in New Issue
Block a user