mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
167 lines
5.5 KiB
Lua
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
|