Files
bryanthaboi c65fcf939d battle speed
2026-09-25 14:20:34 -04:00

203 lines
7.3 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local T = require("tests.harness")
local check, eq = T.check, T.eq
local FixedStep = require("src.core.FixedStep")
local GameSpeed = require("src.core.GameSpeed")
local Hooks = require("src.mods.Hooks")
local Runtime = require("src.mods.Runtime")
local Game = require("src.core.Game")
local Game2 = require("src.core.Game2")
local Game3 = require("src.core.Game3")
local function stack(...) return { states = { ... } } end
local unpack = table.unpack or unpack
local battle = { isBattle = true }
local overworld = { isOverworld = true }
local function gen1(states, opts)
return setmetatable({
save = { options = opts or { speedOverworld = 200, speedBattle = 200, speedMenu = 200 } },
stack = stack(unpack(states)),
writeOptions = function() end,
}, { __index = Game })
end
do
local g = gen1({ overworld })
eq(g:logicSpeed(), 200, "gen1 overworld runs the saved 200X")
check(not g:speedLocked(), "gen1 overworld is not locked")
g = gen1({ overworld, battle })
eq(g:logicSpeed(), 200, "gen1 local battle runs BATTLE SPEED")
g.linkSession = true
eq(g:logicSpeed(), 1, "gen1 link battle is 1X at speedBattle 200")
g.speedOverride = 200
eq(g:logicSpeed(), 1, "gen1 link battle ignores speedOverride (--speed, skin FF hold)")
local bus = Hooks.new()
local saved = Runtime.hooks
Runtime.hooks = bus
local ran = false
local unsub = bus:wrap("core.logic_speed", function() ran = true; return 200 end)
g.speedOverride = nil
eq(g:logicSpeed(), 1, "gen1 link battle ignores a core.logic_speed hook returning 200")
check(not ran, "the hook is never called while locked")
local ow = gen1({ overworld })
eq(ow:logicSpeed(), 200, "outside battle the hook still runs")
check(ran, "...and was called")
unsub()
Runtime.hooks = saved
end
do
GameSpeed.setAllowed({ 2, 4 })
local g = gen1({ overworld, battle })
g.linkSession = true
eq(g:logicSpeed(), 1, "a cart ladder without 1 still locks to a literal 1")
eq(gen1({ overworld }, { speedOverworld = 4 }):logicSpeed(), 4,
"and outside battle the ladder applies")
GameSpeed.setAllowed(nil)
end
do
local g = gen1({ overworld, battle },
{ speedOverworld = 1, speedBattle = 1, speedMenu = 1 })
g.linkSession = true
g:_cycleSpeed(1)
g:_cycleSpeed(-1)
g:_cycleSpeed(1)
eq(g.save.options.speedBattle, 1, "gen1 speed presses in a link battle leave speedBattle")
eq(g.save.options.speedOverworld, 1, "and speedOverworld")
g:touchSkinHotkey("fast_forward_toggle", true)
eq(g.save.options.speedBattle, 1, "gen1 skin FF toggle in a link battle is ignored")
g:touchSkinHotkey("fast_forward_hold", true)
eq(g:logicSpeed(), 1, "gen1 skin FF hold in a link battle stays 1X")
g:touchSkinHotkey("fast_forward_hold", false)
eq(g.speedOverride, nil, "and releasing restores the pre-hold override")
g.linkSession = nil
g:_cycleSpeed(1)
eq(g.save.options.speedBattle, 2, "gen1 speed press in a local battle cycles speedBattle")
end
do
local g = gen1({ overworld })
g.linkSession = true
eq(g:logicSpeed(), 1, "gen1 link session is 1X")
g.linkSession = nil
g.linkNet = { closed = false }
eq(g:logicSpeed(), 1, "gen1 open linkNet (arena, tournament, spectate) is 1X")
end
local function gen2(fields)
fields.persistOptions = fields.persistOptions or function() end
return setmetatable(fields, { __index = Game2 })
end
do
eq(gen2({ options = { speed = 200 }, stack = stack({}) }):logicSpeed(), 200,
"gen2 overworld runs GAME SPEED")
eq(gen2({ options = { speed = 200 }, stack = stack({}, battle) }):logicSpeed(), 200,
"gen2 local battle runs GAME SPEED")
eq(gen2({ options = { speed = 200 }, speedOverride = 200, linkNet = { closed = false },
stack = stack({}, battle) }):logicSpeed(), 1,
"gen2 link battle ignores speedOverride")
eq(gen2({ options = { speed = 200 }, linkNet = { closed = false } }):logicSpeed(), 1,
"gen2 online arena / LinkBattle2 linkNet is 1X")
eq(gen2({ options = { speed = 200 }, linkSession = true }):logicSpeed(), 1,
"gen2 tournament / spectate linkSession is 1X")
local g = gen2({ options = { speed = 1 }, stack = stack({}, battle) })
g:_cycleSpeed(1)
eq(g.options.speed, 2, "gen2 pad speed press in a local battle cycles")
g.stack = stack({})
g.linkNet = { closed = false }
g:_cycleSpeed(1)
eq(g.options.speed, 2, "gen2 pad speed press over a link is ignored")
end
local battleActive, battleLink = false, false
package.loaded["src.core.game3.battle"] = {
isActive = function() return battleActive end,
getState = function() return { link = battleLink } end,
}
local function gen3()
return setmetatable({
phase = "field",
options = { speedOverworld = 200, speedBattle = 200, speedMenu = 200 },
writeOptions = function() end,
}, { __index = Game3 })
end
do
local g = gen3()
eq(g:logicSpeed(), 200, "gen3 field runs OVERWORLD SPEED")
battleActive = true
eq(g:logicSpeed(), 200, "gen3 local battle runs BATTLE SPEED")
battleLink = true
eq(g:logicSpeed(), 1, "gen3 link battle is 1X")
g.speedOverride = 200
eq(g:logicSpeed(), 1, "gen3 link battle ignores speedOverride")
g.speedOverride = nil
g:_cycleSpeed(1)
eq(g.options.speedBattle, 200, "gen3 speed press in a link battle is ignored")
battleActive, battleLink = false, false
package.loaded["src.core.game3.link.union_room"] = { isActive = function() return true end }
eq(g:logicSpeed(), 1, "gen3 Union Room is 1X")
package.loaded["src.core.game3.link.union_room"] = nil
package.loaded["src.core.game3.map"] = { current = "FR_UNION_ROOM" }
eq(g:logicSpeed(), 1, "gen3 Union Room map is 1X")
package.loaded["src.core.game3.map"] = nil
eq(g:logicSpeed(), 200, "and the field is back to 200X")
end
local function runFrame(g, lockAt)
local steps = 0
local savedInit = { accum = FixedStep.accum, callback = FixedStep.callback }
FixedStep.clock = function() return 0 end
FixedStep:init(function()
steps = steps + 1
if steps == lockAt then
battleActive, battleLink = true, true
g.linkSession = true
g.stack = stack(overworld, battle)
end
g:_speedLockEdge()
end)
local speed = g:logicSpeed()
g._frameSpeed = speed
FixedStep.maxAccum = FixedStep.catchupLimit(speed, FixedStep.STEP)
FixedStep:update(FixedStep.STEP, speed)
FixedStep.clock = nil
FixedStep.accum, FixedStep.callback = savedInit.accum, savedInit.callback
return steps, speed
end
do
battleActive = false
local g = gen3()
local steps, speed = runFrame(g, 3)
eq(speed, 200, "the frame started unlocked at 200X")
eq(steps, 3, "gen3: a link battle starting mid-frame ends the frame on that step")
battleActive, battleLink = false, false
local g1 = gen1({ overworld })
steps = runFrame(g1, 5)
eq(steps, 5, "gen1: a link battle push mid-frame ends the frame on that step")
eq(g1:logicSpeed(), 1, "and the next frame samples 1X")
local g2 = gen2({ options = { speed = 200 }, stack = stack({}) })
steps = runFrame(g2, 2)
eq(steps, 2, "gen2: a link battle push mid-frame ends the frame on that step")
battleActive, battleLink = false, false
local plain = gen1({ overworld })
steps = runFrame(plain, -1)
check(steps > 100, "without a lock flip the 200X frame runs its steps")
end
package.loaded["src.core.game3.battle"] = nil
T.finish("speed lock")