Files
gen1recomp/tests/game3_battle_anims_phase1_test.lua
bryanthaboi 96d23ab93b battle stuff
2026-09-18 13:11:33 -04:00

167 lines
5.5 KiB
Lua

-- Phase 1 Battle Animation Primitives Parity Test
-- Validates exact GBA math, 60fps frame stepping, Z-indexing, and side-awareness.
local Anim = require("src.core.game3.battle.anim")
local AnimVm = require("src.core.game3.battle.anim_vm")
local AnimTasks = require("src.core.game3.battle.anim_tasks")
local AnimSprites = require("src.core.game3.battle.anim_sprites")
local passed = 0
local failed = 0
local function check(cond, name)
if cond then
passed = passed + 1
else
failed = failed + 1
print("[FAIL] " .. tostring(name))
end
end
print("=== Phase 1 Battle Animation Primitives Test ===")
local function spawn_now(name, args, v)
local t = AnimTasks.spawn(name, 2, args, v)
if t and t.func then t.func(t, v) end
return t
end
-- 1. Coordinate Space & Sub-pixel Fixed Point Math (TranslateMonElliptical & RespectSide)
print("[test] 1. TranslateMonElliptical & Coordinate Mapping")
Anim.reset({ headless = true })
local vm = Anim.vm()
local pAtk = Anim.present("player")
-- Spawn elliptical translation for player mon: radiusX=20, radiusY=10, 1 loop, speed=3 (wavePeriod=8)
AnimTasks.reset()
pAtk.ox = 0
pAtk.oy = 0
pAtk.z = AnimVm.Z.PLAYER
local task = spawn_now("TranslateMonElliptical", { 0, 20, 10, 1, 3 }, vm)
check(task ~= nil, "TranslateMonElliptical spawned")
for _ = 1, 8 do
AnimTasks.update(vm)
end
check(pAtk.ox == 20, "X offset at 90 deg is exactly radiusX (20), got: " .. tostring(pAtk.ox))
check(pAtk.oy == 10, "Y offset at 90 deg is exactly radiusY (10), got: " .. tostring(pAtk.oy))
-- pokefirered/src/battle_anim_mon_movement.c:356
for _ = 1, 24 do
AnimTasks.update(vm)
end
check(AnimTasks.activeCount() == 0, "TranslateMonElliptical terminated after 1 loop")
check(pAtk.ox == 0 and pAtk.oy == 0, "Clean return to (0,0) with no coordinate drift")
-- 2. Side-Awareness Logic (Player vs Opponent Lunges & Sways)
print("[test] 2. Side-awareness vector flipping")
-- Player horizontal lunge (forward = +X)
AnimTasks.reset()
pAtk.ox = 0
pAtk.oy = 0
pAtk.z = AnimVm.Z.PLAYER
vm._attackerSide = "player"
spawn_now("DoHorizontalLunge", { 4, 6 }, vm)
for _ = 1, 4 do AnimTasks.update(vm) end
check(pAtk.ox == 24, "Player lunge forward gives +24 X, got: " .. tostring(pAtk.ox))
for _ = 5, 12 do AnimTasks.update(vm) end
check(AnimTasks.activeCount() == 0, "Player lunge completed cleanly")
check(pAtk.ox == 0, "Player lunge returned to 0 X")
-- Enemy horizontal lunge (forward = -X)
AnimTasks.reset()
local pEnemy = Anim.present("enemy")
pEnemy.ox = 0
pEnemy.oy = 0
pEnemy.z = AnimVm.Z.ENEMY
vm._attackerSide = "enemy"
spawn_now("DoHorizontalLunge", { 4, 6 }, vm)
for _ = 1, 4 do AnimTasks.update(vm) end
check(pEnemy.ox == -24, "Enemy lunge forward gives -24 X, got: " .. tostring(pEnemy.ox))
for _ = 5, 12 do AnimTasks.update(vm) end
check(AnimTasks.activeCount() == 0, "Enemy lunge completed cleanly")
check(pEnemy.ox == 0, "Enemy lunge returned to 0 X")
-- 3. Dynamic Z-Index Layering with SlideMonToOriginalPos
print("[test] 3. Dynamic Z-Index restoration on SlideMonToOriginalPos")
AnimTasks.reset()
vm._attackerSide = "player"
pAtk.ox = 30
pAtk.oy = 15
pAtk.z = AnimVm.Z.FRONT
-- SlideMonToOriginalPos: 0=attacker, 0=both, 6 frames
spawn_now("SlideMonToOriginalPos", { 0, 0, 6 }, vm)
for _ = 1, 3 do AnimTasks.update(vm) end
check(pAtk.ox < 30 and pAtk.ox > 0, "Mid-slide interpolation progressing")
for _ = 1, 4 do AnimTasks.update(vm) end
check(AnimTasks.activeCount() == 0, "SlideMonToOriginalPos finished")
check(pAtk.ox == 0 and pAtk.oy == 0, "Offset restored to exact (0,0)")
-- 4. Frame-Clock Syncing & Exact Delay Shakes (ShakeMon, ShakeMon2, ShakeMonInPlace)
print("[test] 4. Shake tasks frame clock stepping")
-- ShakeMon2: 0=player, 4=xOff, 0=yOff, 3=numShakes, 2=delay
AnimTasks.reset()
pAtk.ox = 0
spawn_now("ShakeMon2", { 0, 4, 0, 3, 2 }, vm)
check(pAtk.ox == 4, "Spawn frame initial shake +4")
AnimTasks.update(vm)
check(pAtk.ox == 4, "Frame 1 hold +4")
AnimTasks.update(vm)
check(pAtk.ox == -4, "Frame 2 toggles to -4")
AnimTasks.update(vm)
AnimTasks.update(vm)
AnimTasks.update(vm)
check(pAtk.ox == 4, "Frame 5 toggles to +4")
AnimTasks.update(vm)
AnimTasks.update(vm)
AnimTasks.update(vm)
check(AnimTasks.activeCount() == 0, "ShakeMon2 destroyed after exact 3 shakes")
check(pAtk.ox == 0, "Final offset restored to 0")
-- 5. WindUpLunge 2-Stage Mechanics (Take Down, Double-Edge)
print("[test] 5. WindUpLunge 2-stage mechanics")
AnimTasks.reset()
pAtk.ox = 0
pAtk.oy = 0
vm._attackerSide = "player"
-- speed1 = -20 (subpixel), amp = 6, dur1 = 10, delay = 2, targetX = 30, dur2 = 5
spawn_now("WindUpLunge", { 0, -20, 6, 10, 2, 30, 5 }, vm)
-- Step through stage 1 (10 frames)
for _ = 1, 10 do AnimTasks.update(vm) end
check(pAtk.ox == -20, "Stage 1 wind-up reached -20 X, got: " .. tostring(pAtk.ox))
-- Step through delay (2 frames)
AnimTasks.update(vm)
AnimTasks.update(vm)
-- Step through stage 2 lunge (5 frames)
for _ = 1, 5 do AnimTasks.update(vm) end
check(AnimTasks.activeCount() == 0, "WindUpLunge completed")
check(pAtk.ox == 10, "Final forward lunge reached (-20 + 30) = +10 X, got: " .. tostring(pAtk.ox))
-- 6. Utility Tasks (GetAttackerSide, GetTargetSide)
print("[test] 6. Utility Side Query Tasks")
vm.args = {}
AnimTasks.reset()
vm._attackerSide = "player"
vm._targetSide = "enemy"
AnimTasks.spawn("GetAttackerSide", 2, {}, vm)
AnimTasks.update(vm)
check(vm.args[7] == 0, "GetAttackerSide for player returns 0")
AnimTasks.spawn("GetTargetSide", 2, {}, vm)
AnimTasks.update(vm)
check(vm.args[7] == 1, "GetTargetSide for enemy returns 1")
print(string.format("\nPhase 1 Results: %d Passed, %d Failed", passed, failed))
if failed > 0 then
os.exit(1)
end