Files
gen1recomp/tests/drivers/gold_cycling_road_bug1718_1749_test.lua
T
bryanthaboi 1905261c5b Spider Car Unleashed
CLOSES #1483, CLOSES #1610, CLOSES #1615, CLOSES #1646, CLOSES #1649, CLOSES #1651, CLOSES #1653, CLOSES #1656, CLOSES #1683, CLOSES #1685, CLOSES #1686, CLOSES #1687, CLOSES #1688, CLOSES #1689, CLOSES #1690, CLOSES #1693, CLOSES #1694, CLOSES #1695, CLOSES #1696, CLOSES #1702, CLOSES #1704, CLOSES #1705, CLOSES #1706, CLOSES #1707, CLOSES #1708, CLOSES #1710, CLOSES #1711, CLOSES #1712, CLOSES #1713, CLOSES #1716, CLOSES #1717, CLOSES #1718, CLOSES #1719, CLOSES #1720, CLOSES #1721, CLOSES #1725, CLOSES #1732, CLOSES #1745, CLOSES #1748, CLOSES #1749, CLOSES #1751, CLOSES #1754
2026-08-24 07:52:05 -04:00

275 lines
10 KiB
Lua

-- #1718 / #1749: START while the Cycling Road rolls the bike downhill, and SURF
-- refused there. Assertion driver -- every line it prints is PASS or FAIL.
-- Covers the halves tests/gen2_cycling_road_test.lua cannot: Game2's joypad
-- latch, which needs a real Input, and the A press at the water.
-- ../pokegold/maps/Route17.asm:11-16, engine/overworld/events.asm:193-231.
-- Never add POKEPORT_SPEED: which fixed step a press lands on IS the subject.
--
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_TOUCH=0 \
-- POKEPORT_DRIVER=tests/drivers/gold_cycling_road_bug1718_1749_test.lua \
-- POKEPORT_SHOT_DIR=/tmp/gold-cycling-road \
-- perl -e 'alarm 240; exec @ARGV' \
-- python3 -c "import pty; pty.spawn(['love','.'])"
local U = require("tests.drivers.util")
local FieldMoves = require("src.world.gen2.FieldMoves")
local Mon = require("src.battle.gen2.Mon")
local Permissions = require("src.world.gen2.Permissions")
local Screens = require("src.ui.Screens")
-- The lane: x=9 is road, x=10 the water beside it, and Route 17's four bikers
-- stand at (4,17) (16,32) (3,53) (6,80) (../pokegold/maps/Route17.asm:147-150).
local ROAD = { map = "ROUTE_17", x = 9, y = 36 }
-- The control, a route with no ALWAYS_ON_BIKE on it: the shore south of the
-- Union Cave mouth (../pokegold/maps/Route32.asm:861 is the nearest object).
local SEA = { map = "ROUTE_32", x = 11, y = 41 }
return function(game)
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-cycling-road"
local fails = 0
local function report(ok, line)
if not ok then fails = fails + 1 end
U.log((ok and "PASS " or "FAIL ") .. line)
return ok
end
U.wait(45)
local world = game.world
if not (world and world.map) then
U.log("FAIL gold world did not boot; nothing below ran")
while true do coroutine.yield() end
end
local save = game.save
-- --------------------------------------------------------------- helpers
local function tap(button, frames)
game.input.pressQueue[#game.input.pressQueue + 1] = button
game.input.state[button] = true
U.wait(2)
game.input.state[button] = false
U.wait(frames or 6)
end
-- One edge then a sustained hold: re-queueing every frame would hand the
-- gate a fresh edge on the landing tick and prove nothing.
local function holdUntilMenu(button, frames)
game.input.pressQueue[#game.input.pressQueue + 1] = button
game.input.state[button] = true
local opened = false
for _ = 1, frames do
coroutine.yield()
if game.stack:top() then opened = true break end
end
game.input.state[button] = false
U.wait(2)
return opened
end
local function tapAndRelease(button)
game.input.pressQueue[#game.input.pressQueue + 1] = button
game.input.state[button] = true
coroutine.yield()
game.input.state[button] = false
end
local function closeMenus()
for _ = 1, 40 do
if not game.stack:top() then return true end
tap("b", 4)
end
return game.stack:top() == nil
end
local function dismiss()
for _ = 1, 80 do
if not (world.textbox or world.choicebox or world:busy()) then return true end
if world.choicebox then tap("b", 4) else tap("a", 4) end
end
return not (world.textbox or world.choicebox)
end
-- Put the roll back at the top of the lane, standing, so every trial starts
-- from the same step phase and none of them drifts into a biker.
local function park(x, y, facing)
local p = world.player
p.moving = false
p.progress = 0
p.targetX, p.targetY = nil, nil
p.turnTimer = 0
p.cellX, p.cellY = x, y
p.px, p.py = x * 16, y * 16
p.facing = facing or "down"
world.stepFinished = false
world.heldDir = nil
end
-- ---- preflight: everything here fails the same silent way the bugs do ----
local okScreen = pcall(Screens.get, game, "Gen2StartMenu")
report(okScreen, "the Gen2StartMenu screen id resolves")
local badges = save.player.badges or {}
save.player.badges = badges
badges[FieldMoves.BADGE.SURF] = true
report(FieldMoves.hasBadge(save, FieldMoves.BADGE.SURF), "the FOGBADGE is on")
local swimmer = Mon.new(game.data, "LAPRAS", 30, { moves = { { id = "SURF" } } })
report(swimmer ~= nil, "the cache builds a LAPRAS")
save.party = { swimmer }
report(FieldMoves.partyMoveUser(save.party, "SURF") ~= nil,
"and it knows SURF, so CheckPartyMove has a mon to find")
report(world:setMap(ROAD.map, ROAD.x, ROAD.y, "down") ~= false,
"ROUTE_17 loads")
U.wait(10)
report(world.map.id == ROAD.map, "and the world is on it")
report(world:alwaysOnBike() == true,
"MAPCALLBACK_NEWMAP set ALWAYS_ON_BIKE")
report(world:downhill() == true, "and DOWNHILL")
report(FieldMoves.isBiking(world.playerState),
"CheckUpdatePlayerSprite put the player on the bike")
for cy = ROAD.y, ROAD.y + 12 do
local road = Permissions.isLand(world.map:cellCollision(ROAD.x, cy))
local sea = Permissions.isWater(world.map:cellCollision(ROAD.x + 1, cy))
if not (road and sea) then
report(false, ("ROUTE_17 (%d,%d) is no longer road-beside-water")
:format(ROAD.x, cy))
break
end
end
report(true, "the lane is road all the way down with water on its right")
local clear = true
for _, npc in ipairs(world.npcs or {}) do
if npc.cellX == ROAD.x and npc.cellY >= ROAD.y - 2
and npc.cellY <= ROAD.y + 14 then
clear = false
end
end
report(clear, "and no biker is parked in it")
if fails > 0 then
U.log("preflight failed -- the trials below would be measuring the setup,")
U.log("not the fix. fix the lines above first.")
while true do coroutine.yield() end
end
-- ---- #1718: the nine offsets in an 8-frame STEP_BIKE cell ---------------
-- Offset 0 is the one tick the gate opens; 1..8 only the latch can carry.
local opened = 0
for offset = 0, 8 do
park(ROAD.x, ROAD.y, "down")
U.wait(offset)
local moving = world.player.moving and "mid-step" or "standing"
local ok = holdUntilMenu("start", 24)
if ok then opened = opened + 1 end
report(ok, ("START at offset %d (%s) opened the menu"):format(offset, moving))
if offset == 4 then U.shot(game, out .. "/01-start-mid-roll.png") end
closeMenus()
end
report(opened == 9, ("all nine offsets opened the menu (%d/9)"):format(opened))
-- A press RELEASED inside the step is one the frozen mirror never sees again.
park(ROAD.x, ROAD.y, "down")
U.wait(3)
report(world.player.moving == true, "three ticks in, the roll is mid-step")
tapAndRelease("start")
U.wait(24)
report(game.stack:top() == nil,
"a START tapped and released inside the step opens nothing")
closeMenus()
-- And the dropped press must not poison the latch for the next one.
park(ROAD.x, ROAD.y, "down")
U.wait(3)
report(holdUntilMenu("start", 24),
"the next held START still opens the menu")
closeMenus()
-- ---- #1749: SURF refused on the road ------------------------------------
park(ROAD.x, ROAD.y, "down")
-- Held RIGHT into the water the step is refused every tick, so the player
-- stands facing the sea instead of rolling and an A press can land at all.
game.input.pressQueue[#game.input.pressQueue + 1] = "right"
game.input.state.right = true
U.wait(20)
report(world.player.facing == "right", "facing the water off the Cycling Road")
report(world.player.moving == false, "and held there by the water it bumps")
local ctx = world:fieldContext()
report(Permissions.isWater(ctx.facingColl), "the faced cell really is water")
report(ctx.alwaysOnBike == true,
"and the field-move context carries ALWAYS_ON_BIKE (#1749)")
U.shot(game, out .. "/02-road-facing-water.png")
-- TrySurfOW's arm is `.quit`: xor a, no script, no text
-- (engine/events/overworld.asm:513-515). Pressed through the real Input.
game.input.pressQueue[#game.input.pressQueue + 1] = "a"
game.input.state.a = true
U.wait(2)
game.input.state.a = false
U.wait(12)
game.input.state.right = false
report(world.textbox == nil, "A at the water put up no text box")
report(world.queuedFieldMove == nil, "and queued no field move")
report(FieldMoves.isBiking(world.playerState), "the player is still on the bike")
-- SurfFunction.TrySurf's arm is .FailSurf, which TALKS
-- (engine/events/overworld.asm:350-352, :387-390). The world has to be idle
-- first or useFieldMove answers CANT_USE_HERE off its own busy gate, which
-- is a refusal for the wrong reason and would read as a pass.
dismiss()
report(not world:busy(), "the world is idle before the PACK's SURF is asked")
local menu = world:useFieldMove("SURF", swimmer)
report(menu and menu.ok == false, "the PACK's SURF refuses on the road")
report(menu and menu.text == FieldMoves.TEXT.CANT_SURF,
"with CantSurfText, not the badge line and not silence")
U.wait(6)
report(world.textbox ~= nil, "and the refusal is on screen")
U.wait(70) -- let the line finish typing so the shot is readable
U.shot(game, out .. "/03-road-cant-surf.png")
dismiss()
-- ---- the control: plain water, ALWAYS_ON_BIKE clear ---------------------
report(world:setMap(SEA.map, SEA.x, SEA.y, "right") ~= false, "ROUTE_32 loads")
U.wait(10)
world:applyPlayerState(FieldMoves.PLAYER_NORMAL)
report(world:alwaysOnBike() == false, "ROUTE_32 has no ALWAYS_ON_BIKE")
local sea = world:fieldContext()
report(sea.alwaysOnBike == false, "so the context reports it clear")
report(Permissions.isWater(sea.facingColl), "and the player faces its water")
local allowed = world:useFieldMove("SURF", swimmer)
report(allowed and allowed.ok == true, "the PACK's SURF still works there")
world.queuedFieldMove = nil
tap("a", 10)
report(world.textbox ~= nil or world.choicebox ~= nil,
"and A at the water still offers to SURF")
U.wait(70)
U.shot(game, out .. "/04-route32-offer.png")
dismiss()
if fails == 0 then
U.log("all trials passed. shots are in " .. out)
else
U.log(fails .. " trial(s) failed -- see the FAIL lines above")
end
U.log("what a wrong fix looks like: START opening the menu on roughly one")
U.log("press in eight is the World half applied without the Game2 latch, and")
U.log("a menu from the tapped-and-released press is the latch re-pressing a")
U.log("button the player already let go of.")
world:setMap(ROAD.map, ROAD.x, ROAD.y, "down")
U.wait(10)
U.log("parked back on the Cycling Road; the controls are yours from here")
while true do
coroutine.yield()
end
end