Files
gen1recomp/tests/drivers/gold_spin_clockwise.lua
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

86 lines
3.3 KiB
Lua

-- SPRITEMOVEDATA_SPINCLOCKWISE / _SPINCOUNTERCLOCKWISE, the two spins that are
-- not random.
--
-- POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_spin_clockwise.lua love .
--
-- MovementFunction_SpinClockwise / _SpinCounterclockwise
-- (engine/overworld/map_objects.asm:790-843) hold OBJECT_STEP_DURATION $10 --
-- sixteen frames -- on each quarter and then take the next facing out of a
-- FIXED table: clockwise is down -> left -> up -> right, counterclockwise is
-- down -> right -> up -> left. No Random is called anywhere in the loop, which
-- is the point: the Rocket base's guards are a puzzle, and a puzzle has to be
-- predictable.
--
-- The port used to answer both rows with "stand", so Route 32's Youngster
-- Gordon, Route 35's Firebreather Walt, RadioTower4F's GruntM10 and the Route
-- 40/41 swimmers never turned at all.
local U = require("tests.drivers.util")
local CLOCKWISE = { down = "left", up = "right", left = "up", right = "down" }
local COUNTER = { down = "right", up = "left", left = "down", right = "up" }
return function(game)
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-spin"
U.wait(45)
local world = game.world
assert(world and world.map, "gold world did not boot")
-- Route 32 has Youngster Gordon on SPRITEMOVEDATA_SPINCLOCKWISE ($1f); the
-- driver takes whatever spinners the map actually carries rather than naming
-- an object index, so a cache rebuild cannot silently retarget it.
world:setMap("ROUTE_32", 9, 12, "down")
U.wait(30)
local found = {}
for _, npc in ipairs(world.npcs) do
local m = npc.def and npc.def.movement
if m == 0x1f or m == 0x1e then
found[#found + 1] = { npc = npc, cw = (m == 0x1f) }
end
end
print(("[driver] %d fixed-spin objects on ROUTE_32"):format(#found))
assert(#found > 0, "ROUTE_32 has no SPINCLOCKWISE / SPINCOUNTERCLOCKWISE object")
U.shot(game, out .. "/00-before.png")
-- Sixteen frames a quarter, so 200 frames is a dozen turns even allowing for
-- the initial timer NPC.new seeds.
local seen = {}
for _, e in ipairs(found) do seen[e.npc] = { [e.npc.facing] = true } end
local order = {}
for _, e in ipairs(found) do order[e.npc] = {} end
for _ = 1, 200 do
for _, e in ipairs(found) do
local f = e.npc.facing
local trail = order[e.npc]
if trail[#trail] ~= f then trail[#trail + 1] = f end
seen[e.npc][f] = true
end
U.wait(1)
end
U.shot(game, out .. "/01-after.png")
for i, e in ipairs(found) do
local n = 0
for _ in pairs(seen[e.npc]) do n = n + 1 end
local trail = order[e.npc]
print(("[driver] object %d (%s): %d distinct facings over %d turns: %s")
:format(i, e.cw and "clockwise" or "counterclockwise", n, #trail - 1,
table.concat(trail, ">")))
assert(n == 4,
"a fixed spinner only reached " .. n .. " facings -- it is not turning")
-- Every consecutive pair has to be the table's own successor: a RANDOM
-- spin would pass the count above and fail here.
local want = e.cw and CLOCKWISE or COUNTER
for j = 2, #trail do
assert(trail[j] == want[trail[j - 1]],
("turn %d went %s -> %s, wanted %s"):format(j - 1, trail[j - 1],
trail[j], tostring(want[trail[j - 1]])))
end
end
print("[driver] PASS gold fixed-order spinners in " .. out)
love.event.quit()
end