mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 21:46:51 +02:00
c0f4315cd8
CLOSES #1484, CLOSES #1486, CLOSES #1513, CLOSES #1517, CLOSES #1556, CLOSES #1564, CLOSES #1567
145 lines
6.1 KiB
Lua
145 lines
6.1 KiB
Lua
-- T4: a mod may not reach a Gen 2 engine module from a Gen 1 game.
|
|
--
|
|
-- src/battle/gen2/Mon.lua returns { hp, attack, defense, speed, specialAttack,
|
|
-- specialDefense }; a Gen 1 party mon carries `special` instead. A Yellow mod
|
|
-- that required that module and ran refreshStats over save.party wrote the Gen
|
|
-- 2 block onto Gen 1 mons, and every reader of stats.special raised from then
|
|
-- on (#1517). The require shim refuses it now, whatever the manifest declares:
|
|
-- engine_internals is a disclosure, not a generation gate.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
|
|
local PROBE = [[
|
|
local mod = ...
|
|
local out = mod.exports
|
|
local function attempt(name)
|
|
local ok, result = pcall(require, name)
|
|
if ok then return nil, type(result) end
|
|
return tostring(result), nil
|
|
end
|
|
out.attempt = attempt
|
|
out.monErr, out.monType = attempt("src.battle.gen2.Mon")
|
|
out.saveErr = attempt("src.core.gen2.Save")
|
|
out.worldErr = attempt("src.ui.gen2.Chrome")
|
|
out.game2Err = attempt("src.core.Game2")
|
|
out.semverErr, out.semverType = attempt("src.mods.Semver")
|
|
out.statsErr, out.statsType = attempt("src.pokemon.Stats")
|
|
out.compatErr, out.compatType = attempt("src.mods.Gen2Compat")
|
|
out.loggerErr, out.loggerType = attempt("src.core.Logger")
|
|
]]
|
|
|
|
local function manifest(id, permissions)
|
|
return ('{"id":"%s","name":"%s","version":"1.0.0","entry":"main.lua",'
|
|
.. '"api":2,"games":["all"]%s}'):format(id, id, permissions or "")
|
|
end
|
|
|
|
-- ------- Gen 1 boot: refused, and the permission does not unlock it
|
|
|
|
local FILES = {
|
|
["mods/gen2_declared/manifest.json"] =
|
|
manifest("gen2_declared", ',"permissions":["engine_internals"]'),
|
|
["mods/gen2_declared/main.lua"] = PROBE,
|
|
["mods/gen2_undeclared/manifest.json"] = manifest("gen2_undeclared"),
|
|
["mods/gen2_undeclared/main.lua"] = PROBE,
|
|
}
|
|
|
|
T.eq(package.loaded["src.battle.gen2.Mon"], nil,
|
|
"the Gen 2 mon module is not loaded before the Gen 1 boot")
|
|
|
|
local run = T.sdk.loadMods({ "mods/gen2_declared", "mods/gen2_undeclared" },
|
|
{ fs = T.sdk.memfs(FILES), data = {}, generation = 1 })
|
|
T.eq(#run.errors, 0,
|
|
"a mod that only probes still loads clean (" .. tostring(run.errors[1]) .. ")")
|
|
|
|
local declared = run.loader.exports.gen2_declared or {}
|
|
local undeclared = run.loader.exports.gen2_undeclared or {}
|
|
|
|
T.check(declared.monErr, "src.battle.gen2.Mon is refused on a Gen 1 game")
|
|
T.check(declared.monErr and declared.monErr:find("src.battle.gen2.Mon", 1, true),
|
|
"the refusal names the module: " .. tostring(declared.monErr))
|
|
T.check(declared.monErr and declared.monErr:find("gen2_declared", 1, true),
|
|
"and the mod that asked for it")
|
|
T.check(declared.monErr and declared.monErr:find("mod.game", 1, true),
|
|
"and the surface to use instead")
|
|
T.eq(declared.monType, nil, "nothing came back for the mod to call")
|
|
T.eq(package.loaded["src.battle.gen2.Mon"], nil,
|
|
"and the module was never loaded at all")
|
|
|
|
T.check(declared.saveErr, "src.core.gen2.Save is refused the same way")
|
|
T.check(declared.worldErr, "so is src.ui.gen2.Chrome")
|
|
T.check(declared.game2Err and declared.game2Err:find("src.core.Game2", 1, true),
|
|
"and src.core.Game2, the Gen 2 service owner: " .. tostring(declared.game2Err))
|
|
|
|
T.check(undeclared.monErr,
|
|
"a mod with no permissions is refused for the same reason")
|
|
T.eq(declared.monErr and declared.monErr:gsub("gen2_declared", "X"),
|
|
undeclared.monErr and undeclared.monErr:gsub("gen2_undeclared", "X"),
|
|
"engine_internals is a disclosure, not a generation gate: same refusal")
|
|
|
|
-- ------- what the gate does NOT touch
|
|
|
|
T.eq(declared.semverErr, nil, "the supported requires still resolve")
|
|
T.eq(declared.semverType, "table", "and hand back the module")
|
|
T.eq(declared.statsErr, nil, "src.pokemon.Stats is still published to mods")
|
|
T.eq(declared.compatErr, nil,
|
|
"src.mods.Gen2Compat is not a Gen 2 engine module: it is the adapter table")
|
|
T.eq(declared.compatType, "table", "so it still answers")
|
|
T.eq(declared.loggerErr, nil,
|
|
"an undeclared engine require is still a warning, not a block")
|
|
T.eq(declared.loggerType, "table", "and still resolves")
|
|
T.eq(undeclared.loggerErr, nil,
|
|
"including for a mod that declared no permissions at all")
|
|
|
|
-- a lazy require made long after the entry chunk ran is gated too: the mod's
|
|
-- own require is what the shim identifies, not the load phase
|
|
do
|
|
local err = declared.attempt and declared.attempt("src.world.gen2.World")
|
|
T.check(err and err:find("src.world.gen2.World", 1, true),
|
|
"a require made after load is refused too: " .. tostring(err))
|
|
end
|
|
|
|
-- the shim only judges a mod's own require; engine and harness callers keep
|
|
-- the module they asked for
|
|
do
|
|
local ok, module = pcall(require, "src.world.gen2.WorldAPI")
|
|
T.check(ok, "a non-mod caller still reaches a Gen 2 module: " .. tostring(module))
|
|
T.eq(type(module), "table", "and gets the real one")
|
|
end
|
|
|
|
run.release()
|
|
|
|
-- ------- an unguarded reach fails the whole mod, on the boot error feed
|
|
|
|
local HARD = {
|
|
["mods/gen2_hard/manifest.json"] = manifest("gen2_hard",
|
|
',"permissions":["engine_internals"]'),
|
|
["mods/gen2_hard/main.lua"] = [[
|
|
local mod = ...
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
mod.exports.reached = Mon ~= nil
|
|
]],
|
|
}
|
|
local hard = T.sdk.loadMods({ "mods/gen2_hard" },
|
|
{ fs = T.sdk.memfs(HARD), data = {}, generation = 1 })
|
|
T.eq(hard.loader.mods.gen2_hard.state, "failed",
|
|
"a mod that reaches across the generation does not load")
|
|
T.eq(hard.loader.exports.gen2_hard, nil, "and publishes nothing")
|
|
T.check(hard.errors[1] and hard.errors[1]:find("src.battle.gen2.Mon", 1, true),
|
|
"the boot error feed names the module: " .. tostring(hard.errors[1]))
|
|
hard.release()
|
|
|
|
-- ------- Gen 2 boot: the same require is the mod's own generation
|
|
|
|
local gen2 = T.sdk.loadMods({ "mods/gen2_declared" },
|
|
{ fs = T.sdk.memfs(FILES), data = {}, generation = 2 })
|
|
local onGold = gen2.loader.exports.gen2_declared or {}
|
|
T.eq(onGold.monErr, nil,
|
|
"on a Gen 2 game the same require is answered: " .. tostring(onGold.monErr))
|
|
T.eq(onGold.monType, "table", "with the real Gen 2 mon module")
|
|
T.eq(onGold.game2Err, nil, "and src.core.Game2 is this game's service owner")
|
|
gen2.release()
|
|
|
|
T.finish("cross_generation_require")
|