Files
gen1recomp/tests/engine/gen2_shim_engine_require.lua
T
bryanthaboi 51908faabf fix(mods): stop the Gen 2 require facade answering the engine
bootGame's require("src.core.Game") was resolving to Gen2Compat's Game
facade after a Gold session, so the next Gen 1 boot got an empty proxy
whose rawget(load) is nil (iOS: "src.core.Game missing load after
reload"; Android: Game:load called on a nil value).

Two independent causes, both needed:

  * callerIsMod decided "not under src/" meant "a mod", and main.lua is
    not under src/, so the engine's own require was gated as a mod's.
    Root chunks main.lua and conf.lua are now matched exactly, which a
    mod's own mods/<id>/main.lua cannot collide with.

  * devShim.generation was only ever set, never cleared, so the
    generation Gold declared outlived Gold's session. Loader.endSession
    drops it from SessionLifecycle.endMountedSession, and the facade
    gate now requires generation == 2 rather than ~= 1.

Only platforms that return to the launcher in-process (Android, and iOS
since #1808) keep the shim alive across sessions; everywhere else the
process restart cleared it.
2026-08-25 15:14:47 -04:00

91 lines
2.8 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = love or require("tests.love_stub")
local S = require("tests.harness").suite("gen2 shim engine require")
local check, eq = S.check, S.eq
local Loader = require("src.mods.Loader")
local Gen2Compat = require("src.mods.Gen2Compat")
local GameVersion = require("src.core.GameVersion")
local SessionLifecycle = require("src.core.SessionLifecycle")
local NAME = "src.core.Game"
local REAL = { load = function() end }
local function memfs(files)
return {
files = files,
read = function(path) return files[path] end,
write = function(path, content) files[path] = content return true end,
remove = function(path) files[path] = nil return true end,
getInfo = function(path)
if files[path] then return { type = "file" } end
local prefix = path .. "/"
for key in pairs(files) do
if key:sub(1, #prefix) == prefix then return { type = "directory" } end
end
return nil
end,
load = function(path)
if not files[path] then return nil, "no file: " .. path end
return load(files[path], path)
end,
createDirectory = function() return true end,
getDirectoryItems = function(path)
local seen, items = {}, {}
local prefix = path .. "/"
for key in pairs(files) do
if key:sub(1, #prefix) == prefix then
local child = key:sub(#prefix + 1):match("^[^/]+")
if child and not seen[child] then
seen[child] = true
items[#items + 1] = child
end
end
end
table.sort(items)
return items
end,
}
end
local function requireFrom(source)
local chunk = assert(loadstring(
"local name = ... local mod = require(name) return mod", "@" .. source))
return chunk(NAME)
end
local savedVersion = GameVersion.get()
local savedModule = package.loaded[NAME]
package.loaded[NAME] = REAL
GameVersion.set("gold")
local files = {
["mods/facade/manifest.json"] =
[[{"id":"facade","name":"facade","version":"1.0.0","entry":"main.lua",]]
.. [["gen2compat":true}]],
["mods/facade/main.lua"] = "return function(mod) end",
}
local loader = Loader.new({ fs = memfs(files) })
loader:load({ pokemon = {} })
eq(loader.generation, 2, "the fixture loader is a Gen 2 one")
check(Gen2Compat.serves(NAME), "the facade serves " .. NAME)
local facade = Gen2Compat.resolve(NAME)
eq(requireFrom("mods/facade/main.lua"), facade,
"a mod's require still resolves to the Gen 2 facade")
eq(requireFrom("main.lua"), REAL,
"main.lua's require is the engine's own, never the facade")
eq(requireFrom("conf.lua"), REAL, "conf.lua's require is the engine's own")
SessionLifecycle.endMountedSession()
eq(requireFrom("mods/facade/main.lua"), REAL,
"after the session ends the facade stops answering for the next boot")
package.loaded[NAME] = savedModule
GameVersion.set(savedVersion)
S.finish()