Files
gen1recomp/tests/engine/battle_ai_move_none_2643.lua
T

110 lines
6.5 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness").suite("Battle AI MOVE_NONE #2643")
local version = os.getenv("AI2643_EDITION") or "emerald"
local root = (version == "emerald" and os.getenv("POKEPORT_EMERALD_CACHE")) or os.getenv("H2643_CACHE") or os.getenv("POKEPORT_GBA_CACHE")
if not root then print("SKIP #2643 set POKEPORT_EMERALD_CACHE or explicit edition cache"); T.finish(); return end
local f = io.open(root .. "/pokemon/battle_moves.lua", "rb")
if not f then print("SKIP #2643 requires exact Emerald battle/AI cache"); T.finish(); return end
f:close()
local candidate = os.getenv("AI2643_CANDIDATE")
if candidate then package.preload["src.core.game3.battle.ai_cmds"] = assert(loadfile(candidate)) end
require("src.core.GameVersion").set(version)
require("src.import.gba.versions").select(version)
local Dataset = require("src.core.game3.dataset"); Dataset.cacheRootOverride = root
require("src.core.game3.profile").reset()
local C = require("src.core.game3.constants").of(version)
local Pokemon = require("src.core.game3.pokemon"); Pokemon.install(nil)
local Space = require("src.core.game3.scripting.space")
Space.store = { flags = {}, vars = {} }
local sess = { version = version, trainerId = 12345, party = {}, flags = Space.store.flags, vars = Space.store.vars }
local Runtime = require("src.core.game3.runtime"); local sessionFn = Runtime.getSession
Runtime.getSession = function() return sess end
local D = require("src.core.game3.rse.frontier.trainers")
local State = require("src.core.game3.battle.state")
local Moves = require("src.core.game3.battle.moves")
local Ai = require("src.core.game3.battle.ai")
local Cmds = require("src.core.game3.battle.ai_cmds")
local Palace = require("src.core.game3.battle.facility_palace")
local Engine = require("src.core.game3.battle.engine")
local none = assert(Pokemon.battleMove(0))
T.eq(none.power, 0, "actual ROM MOVE_NONE power")
T.eq(none.type, 0, "actual ROM MOVE_NONE normal type")
T.eq(none.effect, 0, "actual ROM MOVE_NONE hit effect")
T.raises(function() Moves.get(0) end, "no ROM name", "MOVE_NONE stays invalid as named action")
T.raises(function() Moves.get(9999) end, "no ROM move row", "invalid real action remains strict")
for _, command in ipairs({ "get_move_power_from_result", "get_move_type_from_result", "get_move_effect_from_result" }) do
local vm = { ip = 1, funcResult = 0 }
local ok, err = pcall(Cmds.CMD[command], vm, {})
T.check(ok, "actual source result sentinel command " .. command .. " " .. tostring(err))
T.check(ok and vm.funcResult == none[command:find("power") and "power" or command:find("type") and "type" or "effect"], "ROM row drives " .. command)
end
local full = { "MOVE_TACKLE", "MOVE_GROWL", "MOVE_PROTECT", "MOVE_SURF" }
local counter = { "MOVE_TACKLE", "MOVE_GROWL", "MOVE_PROTECT", "MOVE_COUNTER" }
local function mon(labels, padding)
local m = D.createMon(C:require("species", "SPECIES_SWAMPERT"), 50, 20, 3, 99)
local moves = {}; for i, name in ipairs(labels) do moves[i] = C:require("moves", name) end
D.setMoves(m, moves)
if padding ~= nil then for i = #moves + 1, 4 do m.moves[i] = padding; m.pp[i] = 0; m.maxPp[i] = 0 end end
return m
end
local function state(labels, padding, double)
local player, foe = { mon(labels, padding) }, { mon(full) }
if double then player[2], foe[2] = mon(labels, padding), mon(full) end
local st = State.new({ playerParty = player, foeParty = foe, double = double })
st.session, st.aiFlags, st.trainerItems = sess, 7, { 0, 0, 0, 0 }
st.rng = function(lo) return lo or 0 end
return st
end
local get, zeroLookups = Moves.get, 0
Moves.get = function(id) if id == 0 then zeroLookups = zeroLookups + 1 end; return get(id) end
local Damage = require("src.core.game3.battle.damage")
local damage, noneDamage, realDamage = Damage.calc, 0, 0
Damage.calc = function(user, target, id, opts)
if id == nil or id == 0 or id == "" then noneDamage = noneDamage + 1 else realDamage = realDamage + 1 end
return damage(user, target, id, opts)
end
for _, case in ipairs({
{ "compact partial", { "MOVE_TACKLE" } }, { "valid full", full }, { "first-turn Counter", counter },
{ "numeric padding", { "MOVE_TACKLE" }, 0 }, { "empty string padding", { "MOVE_TACKLE" }, "" },
{ "double Counter", counter, nil, true },
}) do
for _, kind in ipairs(version == "emerald" and { "ordinary", "Palace" } or { "ordinary" }) do
local st = state(case[2], case[3], case[4])
T.check(not st.enemy.lastMove and not st.player.lastMove, case[1] .. " initial last move absent")
local limitations = Engine.moveLimitations
local fac = Palace.new(); fac:start(st)
local ok, result = pcall(function()
if kind == "ordinary" then return Ai.chooseMove(st, { battler = 0 }) end
local a = { kind = "move", slot = 1, move = st.player.mon.moves[1] }
local b = { kind = "move", slot = 1, move = st.enemy.mon.moves[1] }
fac:actions(st, a, b)
return a
end)
T.check(ok, case[1] .. " " .. kind .. " evaluates real imported scripts " .. tostring(result))
T.check(ok and result and result.move ~= 0 and result.slot and st.player.mon.moves[result.slot] ~= 0,
case[1] .. " " .. kind .. " returns only real move action")
T.check(Engine.moveLimitations == limitations, case[1] .. " " .. kind .. " restores mask hook")
end
end
T.eq(zeroLookups, 0, "AI metadata and empty slots never construct a named zero move")
T.eq(noneDamage, 0, "MOVE_NONE and absent slots never reach real damage calculation")
T.check(realDamage > 0, "real move damage remains evaluated")
Damage.calc = damage
Moves.get = get
local cache = Dataset.cache(); local read = cache.read
local rows, loaded = Moves._rom, Moves._romLoaded
Moves._rom, Moves._romLoaded = nil, false
cache.read = function(self, path) if path:find("pokemon/battle_moves.lua", 1, true) then return nil end; return read(self, path) end
T.raises(function() Cmds.move_power(0) end, "cache", "sentinel metadata refuses missing required ROM pack")
T.raises(function() Cmds.move_power(0) end, "cache", "sentinel metadata still refuses after failed pack lookup")
cache.read = read; Moves._rom, Moves._romLoaded = rows, loaded
local battleRows = Pokemon._battleMoves
local withoutNone = {}; for id, row in pairs(battleRows) do if id ~= 0 then withoutNone[id] = row end end
Pokemon._battleMoves = withoutNone
T.raises(function() Cmds.move_power(0) end, nil, "missing ROM sentinel cannot silently use builtin row")
Pokemon._battleMoves = battleRows
T.raises(function() Cmds.move_power(9999) end, "no ROM move row", "invalid nonzero AI metadata stays strict")
Runtime.getSession = sessionFn
T.finish()