battle speed

This commit is contained in:
bryanthaboi
2026-09-25 14:20:34 -04:00
parent 389184789f
commit c65fcf939d
22 changed files with 126 additions and 150 deletions
+5 -5
View File
@@ -1251,13 +1251,13 @@ level is part of a row label rather than a drawn field. Both are noted in
RFC 0019 as follow-ups.
`core.logic_speed` receives `(next, game)` once per `Game:logicSpeed()` call
(once per frame), but only on frames where the speed is not locked. Every
battle (wild, trainer, link, online, tournament, spectate), link play and a
(once per frame), but only on frames where the speed is not locked. Link
play (link, online, tournament and spectate battles included) and a
fixed-speed minigame lock the logic clock to 1X, and `Game:speedLocked()`
is checked before anything else: the hook is never called on those frames,
so it cannot fast-forward a battle. On every other frame vanilla behavior
resolves the per-category GAME SPEED option (`speedOverworld` or
`speedMenu`) for whichever category `Game.speedCategoryInStack` says is
so it cannot fast-forward a link battle. On every other frame vanilla
behavior resolves the per-category GAME SPEED option (`speedOverworld`,
`speedBattle` or `speedMenu`) for whichever category `Game.speedCategoryInStack` says is
active. A mod may call `next(game)` and return its result to pass that
resolution through, or return a different number outright to override it
for that frame (a bot mod forcing 1X for one route segment, say). The
+1 -1
View File
@@ -27,7 +27,7 @@ Features intentionally added beyond the original Pokémon Red, Blue, and Yellow
* **Filter Find mods by game**, a generation or single-game filter of its own, with every listing showing the games and tags it declares
* **Update all** in one press from the MODS tab, installing every outdated mod and every installed cart the index lists ahead of it, with a summary of what failed
* **Rebindable GAME SPEED shortcuts**, SPEED - / SPEED + rows in CONTROLS that move the shoulder hotkeys to any pad button or switch them off
* **Fast-forward locks to 1X in battles and link rooms**, every battle, link session and the FireRed/LeafGreen Union Room on every game
* **Fast-forward locks to 1X in link play**, every link or online battle, link session and the FireRed/LeafGreen Union Room on every game
* **40-player Union Room** on FireRed and LeafGreen online, a larger square room where every trainer keeps a fixed spot and anyone can be talked to
* **Key bar on the touch pad**, a corner toggle that slides out SAVE, LOAD, SPEED, COLOR, TILT and ZOOM for phones with no keyboard
* **Save editor item verbs**, sorting the bag and PC by item number or name, filling one stack or every stack to x99, and a coin editor on every game
-3
View File
@@ -387,9 +387,6 @@ function Game:speedLocked()
if Game.isFixedSpeedInStack and Game.isFixedSpeedInStack(self.stack) then
return true, "minigame"
end
if Game.speedCategoryInStack(self.stack) == "battle" then
return true, "battle"
end
return false
end
-1
View File
@@ -1291,7 +1291,6 @@ function Game2:speedLocked()
local states = self.stack and self.stack.states
for i = #(states or {}), 1, -1 do
local state = states[i]
if state and state.isBattle then return true, "battle" end
if state and (state.isFixedSpeed or state.isMinigame) then
return true, "minigame"
end
+2 -5
View File
@@ -595,11 +595,8 @@ function Game3:speedLocked()
if self:isFixedSpeed() then return true, "link" end
local Battle = package.loaded["src.core.game3.battle"]
if type(Battle) == "table" and Battle.isActive and Battle.isActive() then
return true, "battle"
end
local Transition = package.loaded["src.core.game3.battle_transition"]
if type(Transition) == "table" and Transition.isActive and Transition.isActive() then
return true, "battle"
local st = Battle.getState and Battle.getState()
if type(st) == "table" and st.link then return true, "link" end
end
local Union = package.loaded["src.core.game3.link.union_room"]
if type(Union) == "table" and Union.isActive and Union.isActive() then
+6
View File
@@ -350,6 +350,12 @@ local function coreRows(opts, hooks)
opts.speedOverworld = GameSpeed.cycle(opts.speedOverworld, dir)
return true
end)
add(Strings("BATTLE SPEED"),
function() return GameSpeed.levelLabel(opts.speedBattle) end,
function(dir)
opts.speedBattle = GameSpeed.cycle(opts.speedBattle, dir)
return true
end)
add(Strings("MENU SPEED"),
function() return GameSpeed.levelLabel(opts.speedMenu) end,
function(dir)
-1
View File
@@ -14,7 +14,6 @@ local Runtime = require("src.mods.Runtime")
local BattleTransition = {}
BattleTransition.__index = BattleTransition
BattleTransition.isOpaque = false -- draws over the frozen overworld
BattleTransition.isFixedSpeed = true
-- BattleTransition_FlashScreenPalettes: fade to black and back, then to
-- white and back; each palette held 2 frames, whole sequence played 3
+10 -1
View File
@@ -539,6 +539,15 @@ local function buildRows(game)
o.speedOverworld = GameSpeed.cycle(o.speedOverworld, dir)
return true
end },
{ id = "speedBattle", label = Strings("BATTLE SPEED"),
value = function(g)
return gameSpeedLabel(g.save.options.speedBattle)
end,
step = function(g, dir)
local o = g.save.options
o.speedBattle = GameSpeed.cycle(o.speedBattle, dir)
return true
end },
{ id = "speedMenu", label = Strings("MENU SPEED"),
value = function(g)
return gameSpeedLabel(g.save.options.speedMenu)
@@ -714,7 +723,7 @@ local GROUPS = {
members = { "uiLayout", "videoMode", "orientation", "faithfulRes",
"screenPos", "fpsCap", "vsync", "logicClock" } },
{ id = "group.speed", label = "SPEED",
members = { "textSpeed", "speedOverworld", "speedMenu" } },
members = { "textSpeed", "speedOverworld", "speedBattle", "speedMenu" } },
{ id = "group.graphics", label = "GRAPHICS",
members = { "colors", "uiLetterbox", "shaderfx", "shaderfx2" } },
-- A mod's Pipelines row splices in after TILT and is in no group, so it
+2 -1
View File
@@ -70,6 +70,7 @@ function Rows.build(ctx)
step = function(c, dir) return cartCycle(c, "textSpeed", 3, dir) end,
})
add(speedRow("speedOverworld", "OVERWORLD SPEED", "speedOverworld"))
add(speedRow("speedBattle", "BATTLE SPEED", "speedBattle"))
add(speedRow("speedMenu", "MENU SPEED", "speedMenu"))
add({
@@ -396,7 +397,7 @@ end
Rows.GROUPS = {
{ id = "group.speed", label = "SPEED",
members = { "textSpeed", "speedOverworld", "speedMenu" } },
members = { "textSpeed", "speedOverworld", "speedBattle", "speedMenu" } },
{ id = "group.video", label = "VIDEO",
members = { "uiLayout", "videoMode", "orientation", "faithfulRes",
"screenPos", "fpsCap", "vsync", "logicClock" } },
-1
View File
@@ -35,7 +35,6 @@ local Tilt = require("src.render.Tilt")
local BattleTransition = {}
BattleTransition.__index = BattleTransition
BattleTransition.isOpaque = false
BattleTransition.isFixedSpeed = true
local COLS, ROWS = 20, 18 -- SCREEN_WIDTH x SCREEN_HEIGHT, in tiles
@@ -25,19 +25,6 @@ return function(game)
ow:pushBattle(battle)
U.log("logic speed", game:logicSpeed(), "sfx rate", Sound.rate())
local function inBattle()
for _, st in ipairs(game.stack.states) do
if st == battle then return true end
end
return false
end
local function assertLocked(where)
if inBattle() and game:logicSpeed() ~= 1 then
error(("speed lock: %s ran at %sX with speedOverride 4")
:format(where, tostring(game:logicSpeed())))
end
end
assertLocked("battle start")
if Sound.rate() ~= 1 then
error(("bug2087: Game:update pitched SFX off battle speed (rate %s at 4X)")
:format(tostring(Sound.rate())))
@@ -71,11 +58,8 @@ return function(game)
end
end
local lockedFrames = 0
local shot = false
for _ = 1, 2400 do
assertLocked("entrance")
if inBattle() then lockedFrames = lockedFrames + 1 end
poll("entrance cry")
if battle.phase == "menu" then break end
if battle.waitingSound and not shot then
@@ -103,7 +87,6 @@ return function(game)
for _ = 1, 2400 do
poll("post-move sfx")
if game.stack:top() ~= battle then break end
assertLocked("post-move")
if #gates > before and cur and not shotLevel then
shotLevel = U.shot(game, DIR .. "/bug2087_levelup.png")
end
@@ -122,12 +105,11 @@ return function(game)
fail = ("bug2087: %s was pitched with battle speed (%s)"):format(
g.label, tostring(g.pitch))
elseif g.dur and g.held < g.dur * 0.9 then
fail = ("bug2087: %s cut short (%.3fs of %.3fs)"):format(
fail = ("bug2087: %s cut short at 4X (%.3fs of %.3fs)"):format(
g.label, g.held, g.dur)
end
end
if fail then error(fail) end
if lockedFrames == 0 then error("speed lock: the battle never reached the stack") end
U.log("PASS every battle sfx gate held for its real length, battle locked to 1X under a 4X override")
U.log("PASS every battle sfx gate held for its real length at 4X")
love.event.quit(0)
end
@@ -25,26 +25,12 @@ return function(game)
ow:pushBattle(battle)
U.log("logic speed", game:logicSpeed(), "sfx rate", Sound.rate())
local function inBattle()
for _, st in ipairs(game.stack.states) do
if st == battle then return true end
end
return false
end
local function assertLocked(where)
if inBattle() and game:logicSpeed() ~= 1 then
error(("speed lock: %s ran at %sX with speedOverride 4")
:format(where, tostring(game:logicSpeed())))
end
end
assertLocked("battle start")
if Sound.rate() ~= 1 then
error(("bug1952: Game:update pitched SFX off GAME SPEED (rate %s at 4X)")
:format(tostring(Sound.rate())))
end
for _ = 1, 240 do
assertLocked("battle intro")
if battle.phase == "menu" then break end
U.tap(game, "a")
U.wait(3)
@@ -63,7 +49,6 @@ return function(game)
local shot = false
for _ = 1, 1200 do
U.wait(1)
assertLocked("level-up")
if battle.waitingSound and not t0 then
local src = battle.waitingSound
t0 = love.timer.getTime()
@@ -94,25 +79,13 @@ return function(game)
if not dur then error("bug1952: no duration for the level-up fanfare") end
if held < dur * 0.9 then
error(("bug2087: the battle cut the fanfare short (%.3fs of %.3fs)")
error(("bug2087: the 4X battle cut the fanfare short (%.3fs of %.3fs)")
:format(held, dur))
end
if held > dur + 1 then
error(("bug1952: the fanfare dragged past its length (%.3fs of %.3fs)")
:format(held, dur))
end
for _ = 1, 2400 do
if not inBattle() then break end
assertLocked("battle end")
U.tap(game, "a")
U.wait(1)
end
if inBattle() then error("speed lock: the battle never ended") end
U.wait(2)
if game:logicSpeed() ~= 4 then
error(("speed lock: after the battle logic speed is %s, want 4")
:format(tostring(game:logicSpeed())))
end
U.log("PASS the fanfare kept natural pitch at 1X in a 4X-override battle, 4X back after")
U.log("PASS the fanfare kept natural pitch and played to completion at 4X")
love.event.quit(0)
end
+12 -12
View File
@@ -39,7 +39,7 @@ return function(game)
local o = game.options
game.speedOverride = nil
o.speedOverworld, o.speedBattle, o.speedMenu = 10, 200, 1
o.speedOverworld, o.speedBattle, o.speedMenu = 10, 4, 1
U.wait(2)
if game:logicSpeed() ~= 10 then
fail(("field at OVERWORLD SPEED 10 reads %s"):format(tostring(game:logicSpeed())))
@@ -55,27 +55,27 @@ return function(game)
local Ui = require("src.core.game3.battle.ui")
for f = 1, 20000 do
if not Battle.isActive() then break end
if game:logicSpeed() ~= 1 then
fail(("battle ran at %sX with speedBattle 200"):format(tostring(game:logicSpeed())))
if game:logicSpeed() ~= 4 then
fail(("local battle ran at %sX with speedBattle 4"):format(tostring(game:logicSpeed())))
end
checked = checked + 1
if checked == 30 then
local st = Battle.getState()
st.link = true
key("1")
game:gamepadaxis(nil, "triggerright", 1)
game:gamepadaxis(nil, "triggerright", 0)
if o.speedOverworld ~= 10 or o.speedBattle ~= 200 or o.speedMenu ~= 1 then
fail(("speed presses in battle changed options (%s/%s/%s)"):format(
if o.speedOverworld ~= 10 or o.speedBattle ~= 4 or o.speedMenu ~= 1 then
fail(("speed presses in a link battle changed options (%s/%s/%s)"):format(
tostring(o.speedOverworld), tostring(o.speedBattle), tostring(o.speedMenu)))
end
game.speedOverride = 200
if game:logicSpeed() ~= 1 then fail("speedOverride 200 defeated the battle lock") end
if game:logicSpeed() ~= 1 then fail("a link battle was not locked to 1X") end
game.speedOverride = nil
if game.touchSkinHotkey then game:touchSkinHotkey("fast_forward_hold", true) end
if game:logicSpeed() ~= 1 then fail("skin FF hold defeated the battle lock") end
if game.touchSkinHotkey then game:touchSkinHotkey("fast_forward_hold", false) end
st.link = false
end
if checked == 200 and not shot then
shot = U.shot(game, DIR .. "/firered_battle_locked.png")
shot = U.shot(game, DIR .. "/firered_battle_speed.png")
end
local st = Battle.getState and Battle.getState()
if st and Battle._phase == "command" and Ui._mode == "menu" and not Ui._pendingCommand then
@@ -96,7 +96,7 @@ return function(game)
if Battle.isActive() then
fail(("battle never ended (phase %s, ui %s)"):format(tostring(Battle._phase), tostring(Ui._mode)))
end
U.log("battle frames checked at 1X", checked)
U.log("battle frames checked at 4X", checked)
U.wait(120)
if game:logicSpeed() ~= 10 then
fail(("after the battle logic speed is %s, want 10"):format(tostring(game:logicSpeed())))
@@ -144,6 +144,6 @@ return function(game)
end
U.shot(game, DIR .. "/firered_union_locked.png")
U.log("union room frames checked at 1X", seen, "union state", tostring(Union.state))
U.log("PASS firered battle and Union Room locked to 1X, OVERWORLD SPEED 10 back after the battle")
U.log("PASS firered local battle at BATTLE SPEED 4, link battle and Union Room locked to 1X")
love.event.quit(0)
end
+12 -12
View File
@@ -50,25 +50,25 @@ return function(game)
local checked, shot = 0, false
for _ = 1, 2000 do
if not inBattle() then break end
if game:logicSpeed() ~= 1 then
return fail(("battle ran at %sX with GAME SPEED 10"):format(tostring(game:logicSpeed())))
if game:logicSpeed() ~= 10 then
return fail(("local battle ran at %sX with GAME SPEED 10"):format(tostring(game:logicSpeed())))
end
checked = checked + 1
if checked == 30 then
game:hotkey("1")
game:_cycleSpeed(1)
game:_cycleSpeed(-1)
if game.options.speed ~= 10 then
return fail(("speed presses in battle moved GAME SPEED to %s"):format(tostring(game.options.speed)))
end
game.linkNet = { closed = false }
game.speedOverride = 200
if game:logicSpeed() ~= 1 then
return fail("speedOverride 200 defeated the battle lock")
return fail("a link battle was not locked to 1X")
end
game:_cycleSpeed(1)
if game.options.speed ~= 10 then
return fail(("speed press in a link battle moved GAME SPEED to %s"):format(tostring(game.options.speed)))
end
game.speedOverride = nil
game.linkNet = nil
end
if battle.phase == "menu" and not shot then
shot = U.shot(game, DIR .. "/gold_battle_locked.png")
shot = U.shot(game, DIR .. "/gold_battle_speed.png")
end
if battle.battle and battle.battle.over then
tap("a", 3)
@@ -85,7 +85,7 @@ return function(game)
tostring(battle.phase), tostring(battle.battle and battle.battle.over),
tostring(top and (top.screenId or top.name) or top)))
end
U.log("battle frames checked at 1X", checked)
U.log("battle frames checked at 10X", checked)
U.wait(30)
if game:logicSpeed() ~= 10 then
return fail(("after the battle logic speed is %s, want 10"):format(tostring(game:logicSpeed())))
@@ -97,6 +97,6 @@ return function(game)
if game:logicSpeed() ~= 1 then return fail("a linkSession did not lock 1X") end
game.linkSession = nil
U.shot(game, DIR .. "/gold_after_battle.png")
U.log("PASS gold battle and link locked to 1X, GAME SPEED 10 back after")
U.log("PASS gold local battle at GAME SPEED 10, link locked to 1X")
love.event.quit(0)
end
+11 -13
View File
@@ -18,7 +18,7 @@ return function(game)
local o = game.save.options
game.speedOverride = nil
o.speedOverworld, o.speedBattle, o.speedMenu = 10, 200, 1
o.speedOverworld, o.speedBattle, o.speedMenu = 10, 4, 1
U.wait(2)
if game:logicSpeed() ~= 10 then
fail(("overworld at OVERWORLD SPEED 10 reads %s"):format(tostring(game:logicSpeed())))
@@ -46,34 +46,32 @@ return function(game)
local checked, shot = 0, false
for _ = 1, 2400 do
if not inBattle() then break end
if game:logicSpeed() ~= 1 then
fail(("battle ran at %sX with speedBattle 200"):format(tostring(game:logicSpeed())))
if game:logicSpeed() ~= 4 then
fail(("local battle ran at %sX with speedBattle 4"):format(tostring(game:logicSpeed())))
end
checked = checked + 1
if checked == 20 then
game.linkSession = true
key("1")
game:gamepadaxis(nil, "triggerright", 1)
game:gamepadaxis(nil, "triggerright", 0)
game:touchSkinHotkey("fast_forward_toggle", true)
if o.speedOverworld ~= 10 or o.speedBattle ~= 200 or o.speedMenu ~= 1 then
fail(("speed presses in battle changed options (%s/%s/%s)"):format(
if o.speedOverworld ~= 10 or o.speedBattle ~= 4 or o.speedMenu ~= 1 then
fail(("speed presses in a link battle changed options (%s/%s/%s)"):format(
tostring(o.speedOverworld), tostring(o.speedBattle), tostring(o.speedMenu)))
end
game:touchSkinHotkey("fast_forward_hold", true)
if game:logicSpeed() ~= 1 then fail("skin FF hold defeated the battle lock") end
game:touchSkinHotkey("fast_forward_hold", false)
game.speedOverride = 200
if game:logicSpeed() ~= 1 then fail("speedOverride 200 defeated the battle lock") end
if game:logicSpeed() ~= 1 then fail("a link battle was not locked to 1X") end
game.speedOverride = nil
game.linkSession = nil
end
if battle.phase == "menu" and not shot then
shot = U.shot(game, DIR .. "/red_battle_locked.png")
shot = U.shot(game, DIR .. "/red_battle_speed.png")
end
U.tap(game, "a")
U.wait(2)
end
if inBattle() then fail("battle never ended") end
U.log("battle frames checked at 1X", checked)
U.log("battle frames checked at 4X", checked)
U.wait(10)
if game:logicSpeed() ~= 10 then
fail(("after the battle logic speed is %s, want 10"):format(tostring(game:logicSpeed())))
@@ -82,6 +80,6 @@ return function(game)
if game:logicSpeed() ~= 1 then fail("a link session did not lock 1X") end
game.linkSession = nil
U.shot(game, DIR .. "/red_after_battle.png")
U.log("PASS red battle and link locked to 1X, OVERWORLD SPEED 10 back after")
U.log("PASS red local battle at BATTLE SPEED 4, link locked to 1X")
love.event.quit(0)
end
+2 -5
View File
@@ -39,12 +39,9 @@ do
battleActive = true
eq(g:speedCategory(), "battle", "battle stays battle")
g.options.speedBattle = 10
eq(g:logicSpeed(), 1, "a gen3 battle runs 1X whatever BATTLE SPEED says")
g.speedOverride = 20
eq(g:logicSpeed(), 1, "and whatever speedOverride says")
g.speedOverride = nil
eq(g:logicSpeed(), 10, "a local gen3 battle runs at BATTLE SPEED")
g:keypressed("1")
eq(g.options.speedBattle, 10, "1 in battle is ignored")
check(g.options.speedBattle ~= 10, "1 in battle cycles BATTLE SPEED")
eq(g.options.speedOverworld, 2, "and leaves OVERWORLD SPEED alone")
battleActive = false
eq(newGame3("boot"):speedCategory(), "menu", "boot phase stays menu")
+6 -7
View File
@@ -1,5 +1,5 @@
-- Per-category GAME SPEED (RFC 0007): Game.speedCategoryInStack's stack
-- walk, Game:logicSpeed()'s precedence (battle/link lock / run-argument override /
-- walk, Game:logicSpeed()'s precedence (link lock / run-argument override /
-- the core.logic_speed hook), Game:_cycleSpeed's per-category cycling, and
-- the core.logic_speed hook itself exercised through the public mod API
-- (Hooks.new() + bus:wrap, the same idiom other hooks' tests use -- not a
@@ -66,7 +66,7 @@ do
local g = gameWith({ battle },
{ speedOverworld = 4, speedBattle = 10, speedMenu = 2 })
eq(g:_resolveLogicSpeed(), 10, "battle reads speedBattle")
eq(g:logicSpeed(), 1, "but a battle always runs 1X whatever speedBattle says")
eq(g:logicSpeed(), 10, "and a local battle runs at BATTLE SPEED")
end
do
local g = gameWith({ overlay },
@@ -105,9 +105,8 @@ end
do
local g = gameWith({ overworld, battle }, { speedOverworld = 50, speedBattle = 50 })
g.speedOverride = 20
eq(g:logicSpeed(), 1, "a battle forces 1X over speedOverride too")
local locked, why = g:speedLocked()
check(locked and why == "battle", "speedLocked reports the battle")
eq(g:logicSpeed(), 20, "speedOverride wins in a local battle")
check(not g:speedLocked(), "a local battle is not speed locked")
end
do
local g = gameWith({ overworld }, { speedOverworld = 4 })
@@ -180,10 +179,10 @@ do
{ speedOverworld = 1, speedBattle = 1, speedMenu = 1 })
function g:writeOptions() writeOptions.calls = writeOptions.calls + 1 end
g:_cycleSpeed(1)
eq(g.save.options.speedBattle, 1, "cycling during battle is ignored")
eq(g.save.options.speedBattle, 2, "cycling during battle bumps speedBattle")
eq(g.save.options.speedOverworld, 1, "...and leaves speedOverworld alone")
eq(g.save.options.speedMenu, 1, "...and leaves speedMenu alone")
eq(writeOptions.calls, 0, "and persists nothing")
eq(writeOptions.calls, 1, "a successful cycle persists the option")
end
do
local calls = 0
+8 -6
View File
@@ -31,11 +31,11 @@ local world = {}
eq(game({ options = { speed = 10 }, stack = stack(world) }):logicSpeed(), 10,
"the overworld under a plain stack honors GAME SPEED")
eq(game({ options = { speed = 10 }, stack = stack(world, battle) }):logicSpeed(), 1,
"a battle on the stack is locked to 1X")
eq(game({ options = { speed = 10 }, speedOverride = 20,
stack = stack(world, battle, {}) }):logicSpeed(), 1,
"even with speedOverride and a menu over the battle")
eq(game({ options = { speed = 10 }, stack = stack(world, battle) }):logicSpeed(), 10,
"a local battle honors GAME SPEED")
eq(game({ options = { speed = 10 }, linkNet = { closed = false },
stack = stack(world, battle) }):logicSpeed(), 1,
"a link battle is locked to 1X")
eq(game({ options = { speed = 10 }, linkNet = { closed = false } }):logicSpeed(), 1,
"an open linkNet (online arena, LinkBattle2) is locked to 1X")
eq(game({ options = { speed = 10 }, linkNet = { closed = true } }):logicSpeed(), 10,
@@ -48,10 +48,12 @@ eq(require("src.ui.gen2.BattleState").isBattle, true,
do
local persisted = 0
local g = game({ options = { speed = 1 }, stack = stack(world, battle),
linkNet = { closed = false },
persistOptions = function() persisted = persisted + 1 end })
g:_cycleSpeed(1)
eq(g.options.speed, 1, "SPEED + in battle is ignored")
eq(g.options.speed, 1, "SPEED + in a link battle is ignored")
eq(persisted, 0, "and persists nothing")
g.linkNet = nil
g.stack = stack(world)
g:_cycleSpeed(1)
eq(g.options.speed, 2, "SPEED + on the overworld still cycles")
@@ -53,7 +53,7 @@ end
for _, spec in ipairs({
{ "group.battle", 6 }, { "group.audio", 3 }, { "group.video", 5 },
{ "group.speed", 3 }, { "group.graphics", 4 }, { "group.extras", 3 },
{ "group.speed", 4 }, { "group.graphics", 4 }, { "group.extras", 3 },
}) do
local row = find(menu.view, spec[1])
T.check(row, spec[1] .. " has an opener row")
@@ -100,7 +100,7 @@ game.stack:pop()
local speed = find(menu.view, "group.speed")
speed.activate(game)
T.eq(ids(game.stack:top().view),
"textSpeed,speedOverworld,speedMenu",
"textSpeed,speedOverworld,speedBattle,speedMenu",
"TEXT SPEED heads the SPEED page")
game.stack:pop()
+37 -23
View File
@@ -31,16 +31,18 @@ do
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(), 1, "gen1 battle is 1X at speedBattle 200")
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 battle ignores speedOverride (--speed, skin FF hold)")
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 battle ignores a core.logic_speed hook returning 200")
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")
@@ -52,6 +54,7 @@ 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")
@@ -61,17 +64,21 @@ 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 battle leave speedBattle")
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 battle is ignored")
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 battle stays 1X")
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
@@ -91,27 +98,28 @@ 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(), 1,
"gen2 battle is 1X")
eq(gen2({ options = { speed = 200 }, speedOverride = 200,
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 battle ignores speedOverride")
"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, 1, "gen2 pad speed press in battle is ignored")
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, 1, "gen2 pad speed press over a link is ignored")
eq(g.options.speed, 2, "gen2 pad speed press over a link is ignored")
end
local battleActive = false
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()
@@ -126,13 +134,15 @@ do
local g = gen3()
eq(g:logicSpeed(), 200, "gen3 field runs OVERWORLD SPEED")
battleActive = true
eq(g:logicSpeed(), 1, "gen3 battle is 1X")
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 battle ignores speedOverride")
eq(g:logicSpeed(), 1, "gen3 link battle ignores speedOverride")
g.speedOverride = nil
g:_cycleSpeed(1)
eq(g.options.speedBattle, 200, "gen3 speed press in battle is ignored")
battleActive = false
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
@@ -148,7 +158,11 @@ local function runFrame(g, lockAt)
FixedStep.clock = function() return 0 end
FixedStep:init(function()
steps = steps + 1
if steps == lockAt then battleActive = true; g.stack = stack(overworld, battle) end
if steps == lockAt then
battleActive, battleLink = true, true
g.linkSession = true
g.stack = stack(overworld, battle)
end
g:_speedLockEdge()
end)
local speed = g:logicSpeed()
@@ -165,18 +179,18 @@ do
local g = gen3()
local steps, speed = runFrame(g, 3)
eq(speed, 200, "the frame started unlocked at 200X")
eq(steps, 3, "gen3: a battle starting mid-frame ends the frame on that step")
battleActive = false
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 battle push mid-frame ends the frame on that step")
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 battle push mid-frame ends the frame on that step")
battleActive = false
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)
+1 -1
View File
@@ -288,7 +288,7 @@ local WANT_IDS = { "textSpeed", "animations", "battleStyle", "battleLayout",
"performance", "colors",
"tilt", "uiLetterbox", "shaderfx", "shaderfx2", "zoom", "voidFill",
"videoMode", "faithfulRes", "screenPos", "fpsCap", "vsync", "logicClock",
"speedOverworld", "speedMenu",
"speedOverworld", "speedBattle", "speedMenu",
"mods", "controls", "dateFormat", "timeFormat" }
local function orow(menu, id)
for _, row in ipairs(menu.rows) do
+5 -1
View File
@@ -2980,7 +2980,11 @@ do
-- bug when the cycling is fine and the row is simply one longer.
for _ = 1, #GameSpeed.LEVELS - 1 do press("a") end
eq(og.save.options.speedOverworld, 1, "OVERWORLD SPEED wraps back to NORMAL")
check(not seek("speedBattle"), "no BATTLE SPEED row: battles always run 1X")
check(seek("speedBattle"), "cursor reaches BATTLE SPEED")
press("a")
eq(og.save.options.speedBattle, 2, "A cycles BATTLE SPEED to 2X")
for _ = 1, #GameSpeed.LEVELS - 1 do press("a") end
eq(og.save.options.speedBattle, 1, "BATTLE SPEED wraps back to NORMAL")
check(seek("speedMenu"), "cursor reaches MENU SPEED")
press("a")
eq(og.save.options.speedMenu, 2, "A cycles MENU SPEED to 2X")