Files
gen1recomp/tests/engine/game3_rse_compositor_test.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

147 lines
5.7 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local Palette = require("src.core.game3.gba_palette")
local Scanline = require("src.core.game3.scanline_fx")
local Affine = require("src.core.game3.bg_affine")
local Tasks = require("src.core.game3.gba_tasks")
local Sprites = require("src.core.game3.gba_sprites")
local BootModules = require("src.ui.game3.boot_modules")
do
local p = Palette.new()
p:load({ 0x001F }, 0, 1)
eq(p.faded[0], 0x001F, "LoadPalette writes the faded buffer")
check(p:beginFade(Palette.ALL, 0, 0, 16, Palette.BLACK), "fade starts")
eq(p.pltt[0], 0x001F, "BeginNormalPaletteFade copies y=0 to hardware")
eq(p:update(), Palette.STATUS_ACTIVE, "second update blends the OBJ half")
eq(p:update(), Palette.STATUS_LOADING, "no update before the buffer transfer")
p:transfer()
eq(p:update(), Palette.STATUS_ACTIVE, "BG half at y=2")
eq(p.faded[0], 27, "31 + ((0 - 31) * 2 >> 4) = 27")
check(not p:beginFade(Palette.ALL, 0, 16, 0, Palette.WHITE), "a second fade is refused while active")
local calls = 0
while p:fadeActive() and calls < 100 do
p:transfer()
p:update()
calls = calls + 1
end
eq(p.faded[0], 0, "fade reaches black")
eq(calls, 20, "fade 0->16 finishes after 16 blend updates and 5 finishing updates")
p:resetFade()
p:load({ Palette.rgb(10, 20, 30) }, 256, 1)
p:blendMask(Palette.OBJ, 8, Palette.WHITE)
eq(p.faded[256], Palette.rgb(20, 25, 30), "BlendPalettes halfway to white")
p:copyUnfaded(256, 256 + 15 * 16 + 3, 13)
eq(p.unfaded[256 + 15 * 16 + 3], Palette.rgb(10, 20, 30), "shifted palette copy")
end
do
local s = Scanline.new()
s:initWave(0, 160, 4, 4, 1, "BG1HOFS", nil)
eq(s.buffers[0][0], 0, "wave line 0")
eq(s.buffers[0][16], 4, "wave peak at theta 64")
eq(s.buffers[0][48], 65532, "wave trough stored as u16")
eq(s.buffers[1][16], 4, "second buffer seeded")
s:vblank({})
local lines = s:lineValues()
eq(lines[16], 4, "vblank latches the written buffer")
eq(s.srcBuffer, 1, "buffers swap each vblank")
check(s:runWaveTask(), "wave task runs")
eq(s.buffers[1][16], 4, "first pass keeps the phase")
eq(s.buffers[1][15], 3, "line 15 is sin(60) * 4 / 256")
check(s:runWaveTask(), "wave task runs again")
eq(s.buffers[1][15], 3, "delay interval 1 holds the phase for two passes")
check(s:runWaveTask(), "wave task runs a third time")
eq(s.buffers[1][15], 4, "then the wave moves up one line")
s.state = 3
local regs = {}
s:vblank(regs)
check(s:lineValues() == nil, "state 3 stops the DMA")
check(not s:runWaveTask(), "and the wave task ends")
end
do
local r = Affine.panFadeAndZoom(120, 80, 256, 0)
eq(r.pa, 256, "unit zoom pa")
eq(r.pb, 0, "unit zoom pb")
eq(r.pd, 256, "unit zoom pd")
eq(r.dx, 8 * 256, "tex centre 128 minus screen 120")
eq(r.dy, 48 * 256, "tex centre 128 minus screen 80")
local z = Affine.panFadeAndZoom(120, 80, 512, 0)
eq(z.pa, 512, "2x scale")
eq(z.dx, (128 - 240) * 256, "2x reference point")
local x, y = Affine.sample(r, 0, 0, 256, 256, false)
eq(x, 8, "sample x")
eq(y, 48, "sample y")
eq(Affine.convertScaleParam(128), 512, "ConvertScaleParam")
local m = Affine.objAffineSet(256, 256, 0)
eq(m.a, 256, "identity OBJ matrix a")
eq(m.b, 0, "identity OBJ matrix b")
local q = Affine.objAffineSet(256, 256, 64 * 256)
eq(q.a, 0, "quarter turn a")
eq(q.b, -256, "quarter turn b")
eq(q.c, 256, "quarter turn c")
end
do
local t = Tasks.new()
local order = {}
local a, b
a = t:create(function(id)
order[#order + 1] = "a"
if #order == 1 then t:create(function() order[#order + 1] = "c" end, 0) end
t:destroy(id)
end, 0)
b = t:create(function() order[#order + 1] = "b" end, 0)
t:create(function() order[#order + 1] = "early" end, 0)
t:run()
eq(table.concat(order, ","), "a,b,early,c", "created tasks append and run in the same pass")
order = {}
t:run()
eq(table.concat(order, ","), "b,early,c", "destroyed task is unlinked")
check(a ~= b, "distinct task ids")
end
do
local pal = Palette.new()
local sp = Sprites.new(pal)
local anims = { { { op = "frame", frame = 0, duration = 2 }, { op = "frame", frame = 1, duration = 2 }, { op = "jump", target = 0 } } }
local id = sp:create({ w = 16, h = 16, anims = anims }, 50, 60, 0)
local s = sp:get(id)
local seen = {}
for i = 1, 6 do
sp:animateAll()
seen[i] = s.frame
end
eq(table.concat(seen, ","), "0,0,1,1,0,0", "AnimCmd frame durations")
sp:buildOam()
local e = sp.oamBuffer[1]
eq(e.x, 42, "centre to corner x")
eq(e.y, 52, "centre to corner y")
eq(sp:loadPalette(7, { 1, 2, 3 }), 0, "first sprite palette slot")
sp:setReservedPalettes(8)
eq(sp:loadPalette(9, { 4 }), 8, "reserved palettes skip low slots")
eq(pal.unfaded[256 + 8 * 16], 4, "sprite palette lands in OBJ palette RAM")
local aid = sp:create({ w = 16, h = 16, anims = anims, affineMode = Sprites.AFFINE_DOUBLE,
affineAnims = { { { op = "frame", xScale = 128, yScale = 128, rotation = 0, duration = 0 }, { op = "end" } } } }, 0, 0, 0)
local as = sp:get(aid)
eq(as.centerToCornerVecX, -16, "double-size affine sprites double the corner vector")
sp:animateAll()
eq(sp.matrices[as.oam.matrixNum].a, 512, "affine anim frame sets half scale matrix")
end
do
local fr = BootModules.resolve({ id = "firered" })
check(fr.custom == false, "profiles without a boot block keep the FireRed boot")
local em = BootModules["for"](require("src.core.game3.profiles.emerald"))
check(em.custom == true, "Emerald boot block routes through modules")
eq(em.intro, "src.ui.game3.rse.intro_emerald", "Emerald intro module")
eq(em.title, "src.ui.game3.rse.title_rse", "Emerald title module")
eq(em.params.titleParams.song, "MUS_TITLE", "title song by name")
end
T.finish()