mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
1905261c5b
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
275 lines
10 KiB
Lua
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
|