Files
gen1recomp/tests/game3_stitchfield_onframe_test.lua

185 lines
6.2 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function done()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local Space = require("src.core.game3.scripting.space")
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("scripts/events.lua", { native = true })
if not cacheRoot then
print("[skip] game3_stitchfield_onframe_test: " .. tostring(Cache.reason))
done()
end
print("[info] FireRed cache at " .. cacheRoot)
local ExtractScripts = require("src.import.gba.extract_scripts")
Space.bundle = ExtractScripts.loadBundle(Cache.cache(), cacheRoot, { allowIncomplete = true })
check(Space.bundle ~= nil, "script bundle loads")
local ModRuntime = require("src.mods.Runtime")
local startedLog = {}
local bus = { listeners = { ["script.started"] = true } }
function bus:emit(name, payload)
if name == "script.started" and payload and payload.key then
startedLog[#startedLog + 1] = payload.key
end
end
function bus:removeOwner() end
ModRuntime.install(bus, {
call = function(_, _, vanilla, ...) return vanilla(...) end,
removeOwner = function() end,
})
local Dataset = require("src.core.game3.dataset")
local Runtime = require("src.core.game3.runtime")
local Map = require("src.core.game3.map")
local Field = require("src.core.game3.field")
local Flags = require("src.core.game3.scripting.flags")
local game = { data = {} }
Dataset.hydrate(game)
local session = { map = "FR_PALLET_TOWN", x = 12, y = 20, facing = "down", flags = {}, vars = {} }
game.session = session
Runtime.start(nil, game, session, { reason = "new_game" })
local ICEFALL = "FR_FOUR_ISLAND_ICEFALL_CAVE_1F"
local SEAFOAM_B3F = "FR_SEAFOAM_ISLANDS_B3F"
local NEUTRAL = "FR_PALLET_TOWN"
local VAR_TEMP_1 = 0x4001
local mark = 0
local function since()
local out = {}
for i = mark + 1, #startedLog do out[#out + 1] = startedLog[i] end
mark = #startedLog
return out
end
local function frames(n)
for _ = 1, n do Field.update(1 / 60) end
end
local function warpTo(mapId, x, y)
Map.load(nil, game, mapId, { x = x, y = y, facing = "down" })
frames(4)
end
local function var(id) return Flags.getVar(Space.store, Space.vm and Space.vm.ctx, id) end
local function setVar(id, v) Flags.setVar(Space.store, Space.vm and Space.vm.ctx, id, v) end
print("[test] 1. the two fall maps carry an ON_FRAME row keyed on VAR_TEMP_1")
local iceRows = Space.bundle.events[ICEFALL].mapScripts.onFrame
local seaRows = Space.bundle.events[SEAFOAM_B3F].mapScripts.onFrame
check(type(iceRows) == "table" and iceRows[1] and iceRows[1].var == VAR_TEMP_1
and iceRows[1].value == 1,
"Icefall Cave 1F ON_FRAME is VAR_TEMP_1 == 1, script="
.. tostring(iceRows[1] and iceRows[1].script))
check(type(seaRows) == "table" and seaRows[1] and seaRows[1].var == VAR_TEMP_1
and seaRows[1].value == 1,
"Seafoam B3F ON_FRAME is VAR_TEMP_1 == 1, script="
.. tostring(seaRows[1] and seaRows[1].script))
local ICE_SCRIPT = iceRows[1].script
local SEA_SCRIPT = seaRows[1].script
print("[test] 2. entering Icefall Cave 1F with VAR_TEMP_1 clear starts nothing")
warpTo(NEUTRAL, 12, 20)
warpTo(ICEFALL, 9, 21)
since()
check(var(VAR_TEMP_1) == 0, "VAR_TEMP_1 is 0 on entry, got " .. var(VAR_TEMP_1))
check(Space.vm:isRunning() == false, "no map script is running after the entry drain")
check(Field.locked == false, "the entry lock was released because no ON_FRAME claimed it")
print("[test] 3. standing still for a second does not spuriously fire it")
frames(60)
check(#since() == 0, "60 field frames, no script started")
check(Space._pendingOnFrame == false, "and the claim flag stayed cleared")
print("[test] 4. the ice per-step callback's VarSet is noticed on the very next frame")
-- pokefirered/src/field_tasks.c:243
setVar(VAR_TEMP_1, 1)
check(Space._pendingOnFrame == false, "nothing re-armed the claim flag")
frames(1)
local ran = since()
check(#ran == 1 and ran[1] == ICE_SCRIPT,
"one frame later FallDownHole " .. tostring(ICE_SCRIPT) .. " started, got "
.. tostring(ran[1]) .. " x" .. #ran)
check(Space.vm:isRunning() == true, "it is parked on its delay")
check(Field.locked == true, "its lockall took the field")
print("[test] 5. a locked field is not polled (pret ArePlayerFieldControlsLocked)")
Space.vm:halt(true)
warpTo(NEUTRAL, 12, 20)
warpTo(ICEFALL, 9, 21)
since()
check(var(VAR_TEMP_1) == 0, "temp vars cleared by the map load, got " .. var(VAR_TEMP_1))
Field.lock()
setVar(VAR_TEMP_1, 1)
frames(30)
check(#since() == 0, "30 frames under a field lock start nothing")
Field.unlock()
frames(1)
local ran5 = since()
check(#ran5 == 1 and ran5[1] == ICE_SCRIPT,
"and the first unlocked frame starts it, got " .. tostring(ran5[1]))
print("[test] 6. Seafoam B3F catches the surf-arrival VarSet the same way")
-- pokefirered/src/field_effect.c:1285
Space.vm:halt(true)
Field.unlock()
warpTo(NEUTRAL, 12, 20)
warpTo(SEAFOAM_B3F, 20, 8)
since()
check(var(VAR_TEMP_1) == 0, "B3F entered with VAR_TEMP_1 clear, got " .. var(VAR_TEMP_1))
frames(120)
check(#since() == 0,
"two seconds of frames, the length of the fall animation, start nothing")
setVar(VAR_TEMP_1, 1)
frames(1)
local ran6 = since()
check(ran6[1] == SEA_SCRIPT,
"EnterByFalling " .. tostring(SEA_SCRIPT) .. " started, got " .. tostring(ran6[1]))
print("[test] 7. the row stops matching once the var moves, so the poll stays quiet")
if Space.vm:isRunning() then Space.vm:halt(true) end
Field.unlock()
setVar(VAR_TEMP_1, 0)
since()
frames(60)
check(#since() == 0, "VAR_TEMP_1 back to 0, 60 more frames start nothing")
print("[test] 8. the no-match poll allocates nothing")
warpTo(NEUTRAL, 12, 20)
warpTo(ICEFALL, 9, 21)
since()
check(var(VAR_TEMP_1) == 0, "back in the cave with the row unmatched")
for _ = 1, 20000 do Space.runOnFrame() end
collectgarbage("collect")
local before = collectgarbage("count")
for _ = 1, 20000 do Space.runOnFrame() end
local perPoll = (collectgarbage("count") - before) * 1024 / 20000
check(#since() == 0, "40000 polls started nothing")
check(perPoll < 1,
"the unmatched poll allocates " .. string.format("%.4f", perPoll) .. " bytes per frame")
ModRuntime.reset()
done()