Files
gen1recomp/tests/engine/box_animation_test.lua
T
2026-10-06 11:00:16 -04:00

125 lines
6.5 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness")
local Serializer = require("src.core.SaveSerializer")
local Catalog = require("src.box.Catalog")
local CacheFs = require("src.import.CacheFs")
local Sprites = require("src.online.OnlineSprites")
local MonAnim = require("src.core.game3.mon_anim")
local GameVersion = require("src.core.GameVersion")
local Data = MonAnim.Data
local bodies, images, draws, quads = {}, {}, {}, 0
local previousRead, previousArt = CacheFs.readAt, Catalog.art
local previousData = Data._cache
Data._cache = { sentinel = true }
CacheFs.readAt = function(path) return bodies[path] end
local front = {
functions = { [0] = "Anim_VerticalSquishBounce" },
animIds = { [1] = 0, [308] = 0 }, animDelays = { [1] = 2 },
species = { [1] = { 0, 1 }, [308] = { 0, 1 } },
lists = {
[0] = { cmds = { { frame = 0, duration = 0 }, { op = "end" } } },
[1] = { cmds = { { frame = 0, duration = 4 }, { frame = 1, duration = 4 },
{ frame = 0, duration = 4 }, { op = "end" } } },
},
}
bodies["emerald/data/generated/gba/pokemon/front_anims.lua"] = Serializer.encode(front)
local frameBytes = 64 * 64 * 4
bodies["emerald/data/generated/gba/pokemon/front_anim/1.rgba"] = string.rep("A", frameBytes) .. string.rep("B", frameBytes)
bodies["emerald/data/generated/gba/pokemon/front_anim_shiny/1.rgba"] = string.rep("S", frameBytes) .. string.rep("T", frameBytes)
local function image(raw, w, h)
local obj = { raw = raw, w = w or 64, h = h or 64 }
function obj:getDimensions() return self.w, self.h end
function obj:setFilter(a, b) self.filter = { a, b } end
function obj:release() self.released = true end
images[#images + 1] = obj
return obj
end
love.image.newImageData = function(w, h, _, raw)
return { w = w, h = h, raw = raw, release = function(self) self.released = true end }
end
love.graphics.newImage = function(pixels) return image(pixels.raw, pixels.w, pixels.h) end
love.graphics.newQuad = function(x, y, w, h) quads = quads + 1; return { x = x, y = y, w = w, h = h } end
love.graphics.draw = function(...) draws[#draws + 1] = { ... } end
local ordinary = { front = image("ordinary") }
local shiny = { front = image("shiny") }
local spotted = { front = image("personality-specific spots") }
local artVersion = "emerald"
Catalog.art = function(entry)
return entry.mon.species == 308 and spotted or entry.display.shiny and shiny or ordinary,
artVersion, { species = entry.mon.species, personality = entry.mon.personality }
end
local function entry(species, isShiny)
return { version = "red", generation = 1, mon = { species = species, personality = 1001 },
display = { national = species, shiny = isShiny } }
end
local Animation = require("src.box.Animation")
local mon = entry(1)
local before = Serializer.encode(mon)
local animation = Animation.new(mon)
T.check(animation ~= nil, "imported Emerald metadata starts the real summary animator")
T.eq(animation.sprite.animationData.version, "emerald", "animator uses an explicit Emerald provider")
T.eq(animation.frames[1].raw:sub(1, 1), "B", "alternate pose comes from the normal Emerald sheet")
T.same(animation.frames[1].filter, { "nearest", "nearest" }, "animated pixels use nearest filtering")
local alternate, transformed = false, false
for _ = 1, 300 do
Animation.update(animation, 1 / 60)
if animation.sprite.frame == 1 then
alternate = true
love.graphics.setColor(0.2, 0.3, 0.4, 0.5)
Animation.draw(animation, 10, 20, 128)
T.eq(draws[#draws][1], animation.frames[1], "detail drawing uses the current animation pose")
T.same({ love.graphics.getColor() }, { 0.2, 0.3, 0.4, 0.5 }, "animation restores the surrounding UI colour")
break
end
end
T.check(alternate, "real sprite command stream advances into the alternate pose")
for _ = 1, 300 do
Animation.update(animation, 1 / 60)
local transform = MonAnim.transform(animation.sprite)
transformed = transformed or transform.y2 ~= 0 or transform.sx ~= 1 or transform.sy ~= 1
if MonAnim.done(animation.sprite) then break end
end
T.check(transformed, "species callback animates its position or affine scale")
T.check(MonAnim.done(animation.sprite), "summary animation finishes instead of looping forever")
local n = animation.sprite.frames
Animation.update(animation, 1)
T.eq(animation.sprite.frames, n, "finished animations remain on their resting pose")
T.eq(Serializer.encode(mon), before, "animation leaves the stored Pokémon unchanged")
T.eq(GameVersion.get(), "red", "Emerald animation does not switch the active game")
T.eq(Data._cache.sentinel, true, "Emerald animation does not replace the active animation cache")
T.eq(CacheFs.prefix, "", "Emerald animation does not remount the imported cache")
local sparkling = Animation.new(entry(1, true))
T.eq(sparkling.frames[1].raw:sub(1, 1), "T", "shiny alternate poses retain the shiny palette")
local spinda = Animation.new(entry(308))
T.check(spinda ~= nil, "one-frame species still get their species motion")
T.eq(spinda.art.front, spotted.front, "Spinda motion retains its personality-specific spots")
T.eq(next(spinda.frames), nil, "Spinda never substitutes a generic second-frame picture")
local egg = entry(1); egg.mon.isEgg = true
T.eq(Animation.new(egg), nil, "eggs keep their egg picture")
artVersion = "sapphire"
T.eq(Animation.new(mon), nil, "non-Emerald fallback artwork stays valid and static")
artVersion = "emerald"
T.eq(Animation.new(entry(999)), nil, "unsupported animation species safely stays static")
Animation.reset()
bodies["emerald/data/generated/gba/pokemon/front_anims.lua"] = nil
T.eq(Animation.new(mon), nil, "older imported caches without animation metadata safely stay static")
T.check(sparkling.released and spinda.released, "refresh releases all current animated pictures")
T.check(animation.released, "finished animation pictures are also released on refresh")
T.eq(Animation.draw(animation, 0, 0, 64), false, "released pictures cannot be drawn")
local icon = { icon = image("icon sheet", 32, 64), iconW = 32, iconH = 32 }
Sprites.drawIcon(icon, 0, 0, 48, 0)
T.eq(draws[#draws][2].y, 0, "icon animation begins at its first pose")
Sprites.drawIcon(icon, 0, 0, 48, 1)
T.eq(draws[#draws][2].y, 32, "icon animation selects its second pose")
Sprites.drawIcon(icon, 0, 0, 48, 2)
T.eq(draws[#draws][2].y, 0, "icon poses loop within the actual sheet")
T.eq(quads, 2, "steady icon animation reuses its quads")
Sprites.drawIcon(icon, 0, 0, 48)
T.eq(draws[#draws][2].y, 0, "existing callers retain the original static icon")
CacheFs.readAt, Catalog.art, Data._cache = previousRead, previousArt, previousData
Animation.reset()
T.finish()