mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
171 lines
5.3 KiB
Lua
171 lines
5.3 KiB
Lua
-- home/delay.asm:15
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
local check, eq = T.check, T.eq
|
|
local Data = T.fixtures.fresh()
|
|
require("src.render.Font").load(Data)
|
|
|
|
local Sound = require("src.core.Sound")
|
|
local BattleState = require("src.battle.BattleState")
|
|
|
|
|
|
eq(Sound.rate(), 1, "the rate starts at 1")
|
|
Sound.setRate(2)
|
|
eq(Sound.rate(), 2, "2X sets the one-shot playback rate to 2")
|
|
Sound.setRate(100)
|
|
eq(Sound.rate(), 4, "a huge speed clamps to FF_PITCH_MAX")
|
|
Sound.setRate(nil)
|
|
eq(Sound.rate(), 1, "nil falls back to 1")
|
|
Sound.setRate(-1)
|
|
eq(Sound.rate(), 1, "a negative speed falls back to 1")
|
|
Sound.setRate(0)
|
|
eq(Sound.rate(), 1, "zero falls back to 1")
|
|
Sound.setRate(0 / 0)
|
|
eq(Sound.rate(), 1, "NaN falls back to 1")
|
|
Sound.setRate(1)
|
|
|
|
|
|
local function stub(dur, pitch, playing)
|
|
return {
|
|
playing = playing ~= false,
|
|
getDuration = function() return dur end,
|
|
getPitch = function() return pitch end,
|
|
isPlaying = function(self) return self.playing end,
|
|
stop = function(self) self.playing = false end,
|
|
}
|
|
end
|
|
|
|
eq(Sound.waitFrames(nil), 0, "no source is no wait")
|
|
eq(Sound.waitFrames(stub(2, 1)), 122, "a 2s sfx at pitch 1 is 120 frames plus margin")
|
|
eq(Sound.waitFrames(stub(2, 2)), 62, "pitched to 2X it takes half as many frames")
|
|
eq(Sound.waitFrames(stub(2, 4)), 32, "and a quarter at the 4X clamp")
|
|
|
|
Sound.setRate(4)
|
|
eq(Sound.waitFrames(stub(2, 4)), 122,
|
|
"at rate 4 a 4X-pitched jingle still spans the same 120 logic frames")
|
|
eq(Sound.waitFrames(stub(2, 1)), 482,
|
|
"a source the rate could not pitch costs proportionally more frames")
|
|
Sound.setRate(1)
|
|
|
|
local broken = { getDuration = function() error("no duration") end }
|
|
eq(Sound.waitFrames(broken), 180, "a source with no usable duration gets the fallback")
|
|
eq(Sound.waitFrames(broken, 90), 90, "and the caller may pick the fallback")
|
|
eq(Sound.waitFrames({}), 180, "so does a source with no getDuration at all")
|
|
|
|
|
|
local function gate(src)
|
|
local st = setmetatable({
|
|
waitingSound = src,
|
|
queue = {},
|
|
game = { stack = { top = function() return nil end } },
|
|
}, BattleState)
|
|
return st
|
|
end
|
|
|
|
local stuck = stub(1, 1)
|
|
local st = gate(stuck)
|
|
local budget = Sound.waitFrames(stuck)
|
|
local held = 0
|
|
for _ = 1, budget + 200 do
|
|
if not st:updateQueue() then break end
|
|
held = held + 1
|
|
end
|
|
check(stuck.playing == false, "the budget stops a source that never ends on its own")
|
|
eq(st.waitingSound, nil, "and releases the queue")
|
|
eq(held, budget - 1, "after exactly the frame budget, not forever")
|
|
|
|
local short = stub(1, 1)
|
|
local st2 = gate(short)
|
|
check(st2:updateQueue(), "the gate holds while the sfx sounds")
|
|
short.playing = false
|
|
check(st2:updateQueue() == false, "and releases the frame the sfx goes quiet")
|
|
eq(st2.waitSoundLeft, nil, "the budget is cleared with the source")
|
|
|
|
local function gate4(src)
|
|
local st4 = gate(src)
|
|
st4.game.logicSpeed = function() return 4 end
|
|
return st4
|
|
end
|
|
|
|
local natural = stub(1, 1)
|
|
local stopped = false
|
|
natural.stop = function(self) stopped = true; self.playing = false end
|
|
local steps = 0
|
|
natural.isPlaying = function(self)
|
|
return self.playing and steps < 240
|
|
end
|
|
local st3 = gate4(natural)
|
|
local held3 = 0
|
|
for _ = 1, 1000 do
|
|
if not st3:updateQueue() then break end
|
|
steps = steps + 1
|
|
held3 = held3 + 1
|
|
end
|
|
check(not stopped, "at 4X a source that plays its full length is never stopped early")
|
|
eq(held3, 240, "the gate holds every logic step of the sound's real length")
|
|
eq(st3.waitingSound, nil, "and releases when the sound goes quiet on its own")
|
|
|
|
local stuck4 = stub(1, 1)
|
|
local st4 = gate4(stuck4)
|
|
local held4 = 0
|
|
for _ = 1, budget * 4 + 200 do
|
|
if not st4:updateQueue() then break end
|
|
held4 = held4 + 1
|
|
end
|
|
check(stuck4.playing == false, "at 4X the safety stop still lands on a stuck source")
|
|
eq(held4, budget * 4 - 1, "after the same wall time, four times the logic steps")
|
|
|
|
local quiet4 = stub(1, 1)
|
|
local st5 = gate4(quiet4)
|
|
check(st5:updateQueue(), "at 4X the gate holds while the sfx sounds")
|
|
quiet4.playing = false
|
|
check(st5:updateQueue() == false, "and releases the step the sfx goes quiet")
|
|
|
|
local nanSpeed = gate(stub(1, 1))
|
|
nanSpeed.game.logicSpeed = function() return 0 / 0 end
|
|
check(nanSpeed:updateQueue(), "a NaN speed counts as 1X")
|
|
eq(nanSpeed.waitSoundLeft, budget - 1, "and decrements by a whole frame")
|
|
|
|
|
|
local Game = require("src.core.Game")
|
|
local Game2 = require("src.core.Game2")
|
|
require("src.core.FixedStep"):init(function() end)
|
|
|
|
local function gen1(speed)
|
|
return setmetatable({
|
|
speedOverride = speed,
|
|
audioAccum = 0,
|
|
stack = { top = function() return nil end },
|
|
logicSpeed = function(self)
|
|
return require("src.core.GameSpeed").clamp(self.speedOverride or 1)
|
|
end,
|
|
updateSync = function() end,
|
|
}, { __index = Game })
|
|
end
|
|
|
|
local function gen2(speed)
|
|
return setmetatable({
|
|
phase = "boot",
|
|
speedOverride = speed,
|
|
audioAccum = 0,
|
|
data = {},
|
|
stack = { top = function() return nil end },
|
|
}, { __index = Game2 })
|
|
end
|
|
|
|
Sound.setRate(1)
|
|
gen1(4):update(0)
|
|
eq(Sound.rate(), 1, "a Gen 1 frame does not pitch SFX from GAME SPEED")
|
|
gen2(4):update(0)
|
|
eq(Sound.rate(), 1, "and neither does a Gen 2 frame")
|
|
gen2(100):update(0)
|
|
eq(Sound.rate(), 1, "even at a clamped 200X logic multiplier")
|
|
|
|
Sound.setRate(4)
|
|
require("src.core.SessionLifecycle").endGameSession(nil)
|
|
eq(Sound.rate(), 1, "session teardown resets the rate for the next boot")
|
|
|
|
T.finish("fanfare_speed_bug1952")
|