mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 13:09:54 +02:00
Merge branch 'my-uncles-neighbor' into dev
# Conflicts: # src/import/RomImporter.lua
This commit is contained in:
@@ -41,12 +41,21 @@ end
|
||||
-- Mock isReady
|
||||
RomImporter.isReady = function(v)
|
||||
return v == "red" or v == "gold" or v == "blue" or v == "yellow"
|
||||
or v == "silver"
|
||||
end
|
||||
|
||||
local ok = RomImporter.syncAndroidShortcuts("gold")
|
||||
check(ok == true, "syncAndroidShortcuts returns true on Android")
|
||||
check(#capturedShortcuts == 4, "syncAndroidShortcuts caps at 4 items")
|
||||
check(capturedShortcuts[1] == "gold", "activeVersion 'gold' is placed first")
|
||||
check(capturedShortcuts[2] == "red" and capturedShortcuts[3] == "blue"
|
||||
and capturedShortcuts[4] == "yellow",
|
||||
"the rest follow GameVersion.ORDER until the cap")
|
||||
|
||||
capturedShortcuts = nil
|
||||
RomImporter.syncAndroidShortcuts("silver")
|
||||
check(#capturedShortcuts == 4, "a fifth ready game does not widen the payload")
|
||||
check(capturedShortcuts[1] == "silver", "activeVersion 'silver' is placed first")
|
||||
|
||||
-- Test with subset of ready games (e.g. only Red and Gold)
|
||||
RomImporter.isReady = function(v)
|
||||
@@ -78,4 +87,4 @@ end
|
||||
love.system.getLaunchGame = savedGetLaunchGame
|
||||
love.system.updateShortcuts = nil
|
||||
|
||||
print("8/8 checks passed (android_shortcuts_payload_test)")
|
||||
T.finish("android_shortcuts_payload_test")
|
||||
|
||||
@@ -23,6 +23,7 @@ T.eq(GameVersion.generation("red"), 1, "Red is Gen 1")
|
||||
T.eq(GameVersion.generation("blue"), 1, "Blue is Gen 1")
|
||||
T.eq(GameVersion.generation("yellow"), 1, "Yellow is Gen 1")
|
||||
T.eq(GameVersion.generation("gold"), 2, "Gold is Gen 2")
|
||||
T.eq(GameVersion.generation("silver"), 2, "Silver is Gen 2")
|
||||
|
||||
-- ------- 2. manifest: gen2compat is opt-in and defaults off
|
||||
|
||||
|
||||
@@ -39,45 +39,54 @@ end
|
||||
local edited = 0
|
||||
local hooks = { editTouchControls = function() edited = edited + 1 end }
|
||||
|
||||
local gold = LauncherSettings.open(hooks, "gold")
|
||||
check(has(gold, "TOUCH PAD"), "Gold's gear offers TOUCH PAD")
|
||||
check(has(gold, "VIBRATION"), "and VIBRATION")
|
||||
check(has(gold, "TOUCH CONTROLS"), "and the layout editor")
|
||||
check(has(gold, "VOID FILL"), "and VOID FILL")
|
||||
for _, version in ipairs({ "gold", "silver" }) do
|
||||
local model = LauncherSettings.open(hooks, version)
|
||||
check(has(model, "TOUCH PAD"), version .. " gear offers TOUCH PAD")
|
||||
check(has(model, "VIBRATION"), version .. " and VIBRATION")
|
||||
check(has(model, "TOUCH CONTROLS"), version .. " and the layout editor")
|
||||
check(has(model, "VOID FILL"), version .. " and VOID FILL")
|
||||
|
||||
local voidFill = findRow(gold, "VOID FILL")
|
||||
eq(voidFill.value(), "FADE ", "VOID FILL defaults to FADE")
|
||||
voidFill.step(1)
|
||||
eq(gold.opts.gold.voidFill, "water", "right stores water in the gold block")
|
||||
eq(voidFill.value(), "WATER", "and the row reads WATER")
|
||||
voidFill.step(-1)
|
||||
eq(gold.opts.gold.voidFill, "fade", "left restores fade")
|
||||
local voidFill = findRow(model, "VOID FILL")
|
||||
eq(voidFill.value(), "FADE ", version .. " VOID FILL defaults to FADE")
|
||||
voidFill.step(1)
|
||||
eq(model.opts.gold.voidFill, "water",
|
||||
version .. " right stores water in the gold block")
|
||||
eq(voidFill.value(), "WATER", version .. " and the row reads WATER")
|
||||
voidFill.step(-1)
|
||||
eq(model.opts.gold.voidFill, "fade", version .. " left restores fade")
|
||||
|
||||
-- Every write has to land in the gold block: the flat keys beside it are
|
||||
-- Red's, and Gold's boot never reads them (src/core/gen2/Save.lua:299).
|
||||
-- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gold
|
||||
-- rows leave them exactly as they found them.
|
||||
local flatPad = gold.opts.touchControls
|
||||
local flatBuzz = gold.opts.haptics
|
||||
-- Every write has to land in the gen2 block: the flat keys beside it are
|
||||
-- Red's, and no Gen 2 boot reads them (src/core/gen2/Save.lua:299).
|
||||
-- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gen 2
|
||||
-- rows leave them exactly as they found them.
|
||||
local flatPad = model.opts.touchControls
|
||||
local flatBuzz = model.opts.haptics
|
||||
|
||||
local pad = findRow(gold, "TOUCH PAD")
|
||||
local before = pad.value()
|
||||
pad.step(1)
|
||||
check(pad.value() ~= before, "stepping TOUCH PAD flips it")
|
||||
check(type(gold.opts.gold) == "table", "into the gold block")
|
||||
eq(gold.opts.gold.touchControls.enabled, false, "which now carries enabled")
|
||||
eq(gold.opts.touchControls, flatPad, "leaving the flat Gen 1 key alone")
|
||||
local pad = findRow(model, "TOUCH PAD")
|
||||
local before = pad.value()
|
||||
pad.step(1)
|
||||
check(pad.value() ~= before, version .. " stepping TOUCH PAD flips it")
|
||||
check(type(model.opts.gold) == "table", version .. " into the gold block")
|
||||
eq(model.opts.gold.touchControls.enabled, false,
|
||||
version .. " which now carries enabled")
|
||||
eq(model.opts.touchControls, flatPad,
|
||||
version .. " leaving the flat Gen 1 key alone")
|
||||
eq(model.opts.silver, nil,
|
||||
version .. " and inventing no second Gen 2 block beside it")
|
||||
|
||||
local buzz = findRow(gold, "VIBRATION")
|
||||
local buzzBefore = buzz.value()
|
||||
buzz.step(1)
|
||||
check(buzz.value() ~= buzzBefore, "stepping VIBRATION moves the level")
|
||||
eq(gold.opts.gold.haptics, TouchControls.normalizeHaptics(gold.opts.gold.haptics),
|
||||
"VIBRATION stores a level the shared module knows")
|
||||
eq(gold.opts.haptics, flatBuzz, "also without touching Red's")
|
||||
local buzz = findRow(model, "VIBRATION")
|
||||
local buzzBefore = buzz.value()
|
||||
buzz.step(1)
|
||||
check(buzz.value() ~= buzzBefore, version .. " stepping VIBRATION moves the level")
|
||||
eq(model.opts.gold.haptics,
|
||||
TouchControls.normalizeHaptics(model.opts.gold.haptics),
|
||||
version .. " VIBRATION stores a level the shared module knows")
|
||||
eq(model.opts.haptics, flatBuzz, version .. " also without touching Red's")
|
||||
|
||||
findRow(gold, "TOUCH CONTROLS").action()
|
||||
eq(edited, 1, "the editor row reaches the host hook")
|
||||
edited = 0
|
||||
findRow(model, "TOUCH CONTROLS").action()
|
||||
eq(edited, 1, version .. " the editor row reaches the host hook")
|
||||
end
|
||||
|
||||
-- The Gen 1 gear is untouched by the extraction: same three rows, still on
|
||||
-- the flat table.
|
||||
@@ -94,6 +103,8 @@ eq(findRow(red, "TOUCH PAD").value() ~= nil, true, "and reading it back")
|
||||
-- rather than dead, on both sides.
|
||||
eq(has(LauncherSettings.open(nil, "gold"), "TOUCH CONTROLS"), false,
|
||||
"no hook, no editor row on Gold")
|
||||
eq(has(LauncherSettings.open(nil, "silver"), "TOUCH CONTROLS"), false,
|
||||
"nor on Silver")
|
||||
eq(has(LauncherSettings.open(nil, "red"), "TOUCH CONTROLS"), false,
|
||||
"nor on Red")
|
||||
-- The Edit row hands the screen to the host, and the host has to know WHICH
|
||||
|
||||
@@ -155,28 +155,34 @@ modImp.mods = mods
|
||||
modImp._ensureMods = function() return mods end
|
||||
LauncherView.draw(modImp)
|
||||
LauncherView.draw(modImp)
|
||||
check((modImp._modScrollMax or 0) > 0,
|
||||
"60 mods overflow the list viewport inside the panel")
|
||||
local list = modImp._modListRect
|
||||
check(list.x + list.w
|
||||
<= modImp._tabRegionRect.x + modImp._tabRegionRect.w - Kit.scrollBarW(),
|
||||
"the rows stop short of the region's scrollbar gutter")
|
||||
pointer(list.x + 10, list.y + 10)
|
||||
local reg = modImp._tabRegionRect
|
||||
check((modImp._tabScrollMax.mods or 0) > reg.h,
|
||||
"60 installed mods are ONE continuous list: the region's travel spans "
|
||||
.. "the whole list, not one page of it")
|
||||
eq(modImp._pages.mods, nil, "and no page state is ever minted for it")
|
||||
pointer(reg.x + 10, reg.y + 40)
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
check((modImp.modScroll or 0) > 0, "a notch over the mod list scrolls the list")
|
||||
eq(modImp._tabScroll.mods or 0, 0, "not the panel region around it")
|
||||
eq(modImp._pageScroll, 0, "and not the page behind that")
|
||||
check((modImp._tabScroll.mods or 0) > 0,
|
||||
"a notch over the region scrolls the list like any other tab")
|
||||
eq(modImp._pageScroll, 0, "without reaching the page behind it")
|
||||
|
||||
modImp._tabScroll.mods = modImp._tabScrollMax.mods
|
||||
LauncherView.draw(modImp)
|
||||
pointer(reg.x + 10, reg.y + reg.h * 0.5)
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
eq(modImp._pages.mods, nil,
|
||||
"bottoming the list out never auto-advances any pager")
|
||||
eq(modImp._tabScroll.mods, modImp._tabScrollMax.mods,
|
||||
"the list just rests at its end")
|
||||
|
||||
modImp._modActions = "mod1"
|
||||
local shielded = modImp.modScroll
|
||||
local shieldedPage = modImp._pageScroll
|
||||
pointer(list.x + 10, list.y + 10)
|
||||
local shieldedAt = modImp._tabScroll.mods
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
eq(modImp.modScroll, shielded, "a shielded mod list ignores the notch")
|
||||
eq(modImp._tabScroll.mods or 0, 0, "and so does the region under the scrim")
|
||||
eq(modImp._pageScroll, shieldedPage, "and the page behind that")
|
||||
eq(modImp._tabScroll.mods, shieldedAt,
|
||||
"a shielded mod list ignores the notch under the scrim")
|
||||
modImp._modActions = nil
|
||||
modImp._wheelY = 0
|
||||
LauncherView.draw(modImp)
|
||||
@@ -225,32 +231,27 @@ dragMods.mods = mods
|
||||
dragMods._ensureMods = function() return mods end
|
||||
LauncherView.draw(dragMods)
|
||||
LauncherView.draw(dragMods)
|
||||
local dlist = dragMods._modListRect
|
||||
local dListMax = dragMods._modScrollMax
|
||||
local dreg = dragMods._tabRegionRect
|
||||
local dRegionMax = dragMods._tabScrollMax.mods
|
||||
check(dListMax > 0 and dRegionMax > 0,
|
||||
"the mods tab has both an inner list and a region to scroll")
|
||||
LauncherView.touchpressed(dragMods, 7, dlist.x + 20, dlist.y + 30)
|
||||
LauncherView.touchmoved(dragMods, 7, dlist.x + 20, dlist.y + 30 - 60)
|
||||
eq(dragMods.modScroll, math.min(60, dListMax),
|
||||
"the first pixels of the drag move the list")
|
||||
eq(dragMods._tabScroll.mods or 0, 0, "and nothing else")
|
||||
LauncherView.touchmoved(dragMods, 7, dlist.x + 20,
|
||||
dlist.y + 30 - 60 - dListMax - dRegionMax * 2)
|
||||
eq(dragMods.modScroll, dListMax, "carrying on saturates the list")
|
||||
eq(dragMods._tabScroll.mods, dRegionMax,
|
||||
"then the same gesture walks the region to its bottom")
|
||||
check(dRegionMax > 0, "the mods region scrolls its overscan like any tab")
|
||||
LauncherView.touchpressed(dragMods, 7, dreg.x + 20, dreg.y + 30)
|
||||
LauncherView.touchmoved(dragMods, 7, dreg.x + 20, dreg.y + 30 - 60)
|
||||
eq(dragMods._tabScroll.mods, math.min(60, dRegionMax),
|
||||
"a drag moves the region by the finger's travel")
|
||||
LauncherView.touchmoved(dragMods, 7, dreg.x + 20,
|
||||
dreg.y + 30 - 60 - dRegionMax * 2)
|
||||
eq(dragMods._tabScroll.mods, dRegionMax, "carrying on saturates the region")
|
||||
check((dragMods._pageScroll or 0) > 0, "and only then reaches the page")
|
||||
LauncherView.touchreleased(dragMods, 7, dlist.x + 20, dlist.y - 900)
|
||||
LauncherView.touchreleased(dragMods, 7, dreg.x + 20, dreg.y - 900)
|
||||
|
||||
dragMods._skins = { { id = "s1", source = "user", controls = 8, pages = 1 } }
|
||||
for i = 2, 12 do
|
||||
dragMods._skins[i] = { id = "s" .. i, source = "user", controls = 8, pages = 1 }
|
||||
end
|
||||
dragMods._ensureSkins = function() return dragMods._skins end
|
||||
dragMods.modScroll = 0
|
||||
local heldModScroll = dragMods.modScroll
|
||||
local overList = dlist.y + 30
|
||||
local heldModsPage = dragMods._pages.mods or 1
|
||||
local heldModsAt = dragMods._tabScroll.mods
|
||||
local overList = dreg.y + 60
|
||||
dragMods:_switchTab("skins")
|
||||
LauncherView.draw(dragMods)
|
||||
LauncherView.draw(dragMods)
|
||||
@@ -260,8 +261,10 @@ LauncherView.touchpressed(dragMods, 9, sreg.x + 20, overList)
|
||||
LauncherView.touchmoved(dragMods, 9, sreg.x + 20, overList - 200)
|
||||
check((dragMods._tabScroll.skins or 0) > 0,
|
||||
"a drag on the skins tab scrolls the skins tab")
|
||||
eq(dragMods.modScroll, heldModScroll,
|
||||
"and leaves the mod list where the player parked it")
|
||||
eq(dragMods._pages.mods or 1, heldModsPage,
|
||||
"and leaves the mod list on the page the player parked it")
|
||||
eq(dragMods._tabScroll.mods, heldModsAt,
|
||||
"with its region offset held for the return trip")
|
||||
LauncherView.touchreleased(dragMods, 9, sreg.x + 20, sreg.y - 400)
|
||||
|
||||
love.graphics.polygon = love.graphics.polygon or function() end
|
||||
@@ -308,8 +311,8 @@ local view = read("src/import/LauncherView.lua")
|
||||
check(view:find("Kit.scrollBegin(", 1, true) ~= nil,
|
||||
"the panel dispatch opens a scroll region")
|
||||
check(view:find("Kit.scrollEnd(", 1, true) ~= nil, "and closes it")
|
||||
check(view:find("modListWantsWheel", 1, true) ~= nil,
|
||||
"the nested mod list is asked before the region takes a notch")
|
||||
check(view:find("modListWantsWheel", 1, true) == nil,
|
||||
"no nested list steals the wheel from the region any more")
|
||||
check(view:find("start.region", 1, true) ~= nil,
|
||||
"a touch drag that began in the region scrolls the region")
|
||||
check(view:find("Kit.scrollGutter(", 1, true) ~= nil,
|
||||
|
||||
@@ -32,11 +32,13 @@ do
|
||||
"a version id names exactly that game")
|
||||
eq(table.concat(ModTargets.expand("GEN1"), ","), "red,blue,yellow",
|
||||
"gen1 is every Gen 1 game, case-insensitive")
|
||||
eq(table.concat(ModTargets.expand("gen2"), ","), "gold",
|
||||
eq(table.concat(ModTargets.expand("gen2"), ","), "gold,silver",
|
||||
"gen2 is every Gen 2 game")
|
||||
eq(table.concat(ModTargets.expand("silver"), ","), "silver",
|
||||
"and each of them names itself")
|
||||
eq(table.concat(ModTargets.expand("all"), ","),
|
||||
table.concat(GameVersion.ORDER, ","), "all is the launcher order itself")
|
||||
eq(ModTargets.expand("silver"), nil, "a game this engine has no cache for")
|
||||
eq(ModTargets.expand("crystal"), nil, "a game this engine has no cache for")
|
||||
eq(ModTargets.expand("gen9"), nil, "a generation with no games is unknown")
|
||||
eq(ModTargets.expand(7), nil, "a non-string token is not a game")
|
||||
end
|
||||
@@ -56,7 +58,7 @@ end
|
||||
do
|
||||
eq(list(mf({})), "red,blue,yellow",
|
||||
"a manifest with no games key is Gen 1, which is what it was tested as")
|
||||
eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold",
|
||||
eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold,silver",
|
||||
"gen2compat keeps Gen 1 and adds Gen 2")
|
||||
eq(mf({}).gen2compat, false, "and the derived flag agrees")
|
||||
eq(mf({ gen2compat = true }).gen2compat, true, "both ways")
|
||||
@@ -67,14 +69,14 @@ end
|
||||
|
||||
do
|
||||
local gen2 = mf({ games = { "gen2" } })
|
||||
eq(list(gen2), "gold", "games can name Gen 2 alone")
|
||||
eq(list(gen2), "gold,silver", "games can name Gen 2 alone")
|
||||
eq(gen2.gen2compat, true, "which IS the gen2compat claim the gate reads")
|
||||
local both = mf({ games = { "gen1", "gen2" } })
|
||||
eq(list(both), "red,blue,yellow,gold", "or both generations")
|
||||
eq(list(both), "red,blue,yellow,gold,silver", "or both generations")
|
||||
local one = mf({ games = { "blue" } })
|
||||
eq(list(one), "blue", "or one single game")
|
||||
eq(one.gen2compat, false, "a Gen 1 game is not a Gen 2 claim")
|
||||
eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold",
|
||||
eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold,silver",
|
||||
"an old gen2compat beside a new games list still adds its game")
|
||||
end
|
||||
|
||||
@@ -224,10 +226,12 @@ do
|
||||
local bad = ModProfile.decode(require("src.core.SaveSerializer").encode({
|
||||
format = "g1rmodlist", formatVersion = 1,
|
||||
profile = { name = "P", enabledByVersion = {
|
||||
gold = { a = true }, silver = { a = true }, red = "nope" } },
|
||||
gold = { a = true }, silver = { a = true }, crystal = { a = true },
|
||||
red = "nope" } },
|
||||
}))
|
||||
eq(bad.enabledByVersion.gold.a, true, "a shared file's known game is kept")
|
||||
eq(bad.enabledByVersion.silver, nil, "an unknown game is dropped on read")
|
||||
eq(bad.enabledByVersion.silver.a, true, "every one of them, not just the first")
|
||||
eq(bad.enabledByVersion.crystal, nil, "an unknown game is dropped on read")
|
||||
eq(bad.enabledByVersion.red, nil, "and so is a bucket that is not a table")
|
||||
end
|
||||
|
||||
|
||||
@@ -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", "gold" }) do
|
||||
for _, ver in ipairs({ "red", "blue", "yellow", "gold", "silver" }) do
|
||||
local dir = "imports/saves/" .. ver
|
||||
for _, name in ipairs(love.filesystem.getDirectoryItems(dir) or {}) do
|
||||
love.filesystem.remove(dir .. "/" .. name)
|
||||
@@ -137,6 +137,8 @@ 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/")
|
||||
check(createdDirs["imports/saves/silver"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/silver/")
|
||||
|
||||
-- NXSAV-02: notice/hint includes save dir + per-game imports/saves/<version>/ MTP path
|
||||
ri = freshImporter()
|
||||
|
||||
@@ -81,7 +81,8 @@ local function importer(ready)
|
||||
}, RomImporter)
|
||||
end
|
||||
|
||||
local allReady = importer({ red = true, blue = true, yellow = true, gold = true })
|
||||
local allReady = importer({ red = true, blue = true, yellow = true, gold = true,
|
||||
silver = true })
|
||||
allReady:_queueBaseRomScan()
|
||||
eq(allReady.baseRomScan.state, "done", "ready launcher skips discovery")
|
||||
eq(listings, 0, "ready launcher does not enumerate baseroms")
|
||||
@@ -123,7 +124,8 @@ eq(picks, 0, "missing detected ROM does not open the picker unexpectedly")
|
||||
missing:choose("red")
|
||||
eq(picks, 1, "the next import attempt falls back to the native picker")
|
||||
|
||||
local rescanned = importer({ red = true, blue = true, yellow = true, gold = true })
|
||||
local rescanned = importer({ red = true, blue = true, yellow = true, gold = true,
|
||||
silver = true })
|
||||
rescanned.baseRoms.red = { path = "baseroms/z-red.gb", name = "z-red.gb" }
|
||||
rescanned:reimport("red")
|
||||
check(rescanned.baseRoms.red == nil, "re-import clears the detected ROM")
|
||||
|
||||
Reference in New Issue
Block a user