mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
93e071bcaa
The SBC and RG34XXSP ports hand LÖVE a source *directory*
(`love <dir>`), so `love.filesystem.isFused()` is false there and
`Boot.run` bailed on its first line. The launcher still offered
"Update vX.Y.Z" and "Restart to update", so an update downloaded,
verified, and then was silently ignored forever.
- Boot.canUpdateInPlace() replaces the fused-only gate: a packaged
build updates whether it is fused (AppImage, Flatpak game.love) or
unpacked, and only a dev / source checkout (engine "0.0.0-dev") is
excluded. Fails closed when the host cannot be established.
Prelaunch.updateAllowed now delegates to it, so the boot gate and the
`--update` gate cannot disagree.
- Check.hostPort() reads the release-target marker, with
POKEPORT_HANDHELD as a legacy fallback (the SBC launcher has always
exported it, so packs predating this change still identify
themselves). The marker was read from the environment but never
exported, so a full-package fallback on a handheld resolved to a
desktop AppImage that cannot run there.
- A handheld now fetches its own package ("Download port update",
reusing the worker's cross-platform download_full) instead of
offering a URL it has no browser to open, and reports "Update package
ready" once it is in the save directory for a manual re-extract.
- Launchers export POKEPORT_PORTMASTER / POKEPORT_RG34XXSP.
An in-place update only ever mounts a payload over the running source,
so relaxing the gate is less invasive than the fused path it joins.
A runtime bump still needs a full package, which the minShell gate
already reports as needs_full.
Tests: tests/engine/update_boot_host_gate.lua (new, 10 checks) pins the
gate; update_check_tests.lua pins both port asset names and hostPort().
scripts/test.sh --quick: all tiers passed; scripts/lint.sh: 0 errors.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
107 lines
3.9 KiB
Lua
107 lines
3.9 KiB
Lua
-- The self-updater's host gate: which builds may hand off to a downloaded
|
|
-- payload. A packaged build updates in place -- fused (AppImage, Flatpak's
|
|
-- game.love) or unpacked (every PortMaster-style port, which runs
|
|
-- `love <dir>` and so reports isFused() false). A dev / source checkout must
|
|
-- not, and an unknown host must fail closed.
|
|
--
|
|
-- This gate is the difference between the PortMaster SBC port applying an
|
|
-- update and silently ignoring it after the launcher already said "Restart to
|
|
-- update", so the unpacked case is pinned here.
|
|
-- luajit tests/engine/update_boot_host_gate.lua
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
|
|
local Boot = require("src.update.Boot")
|
|
local love_stub = require("tests.love_stub")
|
|
|
|
local VERSION_KEY = "src.core.Version"
|
|
|
|
-- A packaged build stamps a real engine (X.Y.Z); a working tree keeps the
|
|
-- "0.0.0-dev" placeholder, which is what Version.isDev() reports.
|
|
local function withVersion(isDev, fn)
|
|
local saved = package.loaded[VERSION_KEY]
|
|
package.loaded[VERSION_KEY] = { isDev = function() return isDev end }
|
|
local ok, err = pcall(fn)
|
|
package.loaded[VERSION_KEY] = saved
|
|
if not ok then error(err, 0) end
|
|
end
|
|
|
|
-- Stand in for the running LÖVE process: nil (no love at all) or a filesystem
|
|
-- that reports the given fused state.
|
|
local function withLove(fs, fn)
|
|
local saved = _G.love
|
|
_G.love = fs and { filesystem = fs } or nil
|
|
local ok, err = pcall(fn)
|
|
_G.love = saved
|
|
if not ok then error(err, 0) end
|
|
end
|
|
|
|
local function fsWith(isFused)
|
|
if isFused == nil then return {} end
|
|
return { isFused = function() return isFused end }
|
|
end
|
|
|
|
-- no love at all (headless host): never update
|
|
withLove(nil, function()
|
|
eq(Boot.canUpdateInPlace(), false, "no love.filesystem -> no in-place update")
|
|
end)
|
|
|
|
-- fused wins outright: a binary carrying its own game archive is a packaged
|
|
-- build whatever the engine string says
|
|
withLove(fsWith(true), function()
|
|
withVersion(false, function()
|
|
eq(Boot.canUpdateInPlace(), true, "fused packaged build updates in place")
|
|
end)
|
|
withVersion(true, function()
|
|
eq(Boot.canUpdateInPlace(), true, "fused build updates even with a dev engine string")
|
|
end)
|
|
end)
|
|
|
|
-- the fix: an unpacked packaged build (PortMaster SBC / RG34XXSP) is not fused,
|
|
-- but it carries a released engine and its source on disk is never rewritten,
|
|
-- so a downloaded payload can be mounted over it
|
|
withLove(fsWith(false), function()
|
|
withVersion(false, function()
|
|
eq(Boot.canUpdateInPlace(), true,
|
|
"unpacked released build (love <dir>) updates in place")
|
|
end)
|
|
withVersion(true, function()
|
|
eq(Boot.canUpdateInPlace(), false,
|
|
"unpacked dev checkout (engine 0.0.0-dev) never updates")
|
|
end)
|
|
end)
|
|
|
|
-- a filesystem that cannot answer the fused question at all still updates when
|
|
-- the engine proves this is a packaged build
|
|
withLove(fsWith(nil), function()
|
|
withVersion(false, function()
|
|
eq(Boot.canUpdateInPlace(), true,
|
|
"released build without isFused() still updates")
|
|
end)
|
|
end)
|
|
|
|
-- fail closed: an unreadable / malformed Version must not open the gate
|
|
withLove(fsWith(false), function()
|
|
local saved = package.loaded[VERSION_KEY]
|
|
package.loaded[VERSION_KEY] = "not a module"
|
|
eq(Boot.canUpdateInPlace(), false, "non-table Version -> no in-place update")
|
|
package.loaded[VERSION_KEY] = { engine = "1.4.2" }
|
|
eq(Boot.canUpdateInPlace(), false, "Version without isDev -> no in-place update")
|
|
package.loaded[VERSION_KEY] = nil
|
|
eq(Boot.canUpdateInPlace(), false, "unloadable Version -> no in-place update")
|
|
package.loaded[VERSION_KEY] = saved
|
|
end)
|
|
|
|
-- Boot.run is the first line of love.load: in a dev checkout it must return
|
|
-- false immediately, leaving the bundled game to boot as normal
|
|
withLove(love_stub.filesystem, function()
|
|
withVersion(true, function()
|
|
eq(Boot.run(nil), false, "Boot.run is a no-op in a dev checkout")
|
|
end)
|
|
end)
|
|
|
|
T.finish("update_boot_host_gate")
|