-- Manual check that a warp arrival hides Pikachu under the player (#863):
-- pokeyellow spawns on the player's own coords and the follow buffer walks
-- it out, but before the fix it popped in already beside him.
-- Warp cells: pokered data/maps/objects/RedsHouse1F.asm / RedsHouse2F.asm.
--   POKEPORT_DRIVER=tests/drivers/pikachu_warp_spawn_bug863_test.lua POKEPORT_IDENTITY=bug863 POKEPORT_TOUCH=0 POKEPORT_VERSION=yellow love .
-- Never add POKEPORT_SPEED; the identity needs an imported Yellow cache.
return function(game)
  local U = dofile("tests/drivers/util.lua")
  local Pokemon = require("src.pokemon.Pokemon")
  local GameVersion = require("src.core.GameVersion")
  local PF = require("src.world.PikachuFollower")
  local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots"

  local function check(label, ok)
    U.log(ok and "PASS" or "FAIL", label)
    return ok
  end

  check("running as Yellow (needs POKEPORT_VERSION=yellow)",
        GameVersion.isYellow())

  -- the follower only spawns behind EVENT_GOT_STARTER with a healthy
  -- PIKACHU in the party (PikachuFollower shouldSpawn)
  game.save.flags = game.save.flags or {}
  game.save.flags.EVENT_GOT_STARTER = true
  game.save.party = { Pokemon.new(game.data, "PIKACHU", 20) }
  game.save.onBike = false
  game.save.player.name = "bryan"

  -- pokered RedsHouse1F.asm: warp_event 7, 1 -> REDS_HOUSE_2F, and
  -- RedsHouse2F.asm: warp_event 7, 1 back down.  Read the live map data
  -- so a hack or mod that moved the stairs still points us at them.
  local function warpTo(fromMap, destMap)
    local def = game.data.maps[fromMap]
    for _, w in ipairs(def and def.warps or {}) do
      if w.destMap == destMap then return w.x, w.y end
    end
    return nil
  end
  local wx, wy = warpTo("REDS_HOUSE_1F", "REDS_HOUSE_2F")
  check("REDS_HOUSE_1F has a warp to REDS_HOUSE_2F", wx ~= nil)
  wx, wy = wx or 7, wy or 1

  local follower = function() return PF.current(game.overworld) end

  local function overlapsPlayer()
    local npc = follower()
    local p = game.overworld and game.overworld.player
    return npc and p and npc.cellX == p.cellX and npc.cellY == p.cellY
  end

  -- press-and-hold dir until the map flips, releasing the instant it
  -- does so no queued step drags the player off the arrival warp cell
  local function walkUntilMap(dir, targetMap, maxFrames)
    for _ = 1, maxFrames do
      local ow = game.overworld
      if ow and ow.map and ow.map.id == targetMap then break end
      table.insert(game.input.pressQueue, dir)
      game.input.state[dir] = true
      coroutine.yield()
    end
    game.input.state[dir] = false
    U.wait(45) -- warp fade + arrival settle
    local ow = game.overworld
    return ow and ow.map and ow.map.id == targetMap
  end

  -- stand two below the stairs facing up; fall back to any walkable cell
  -- below the warp if a mod reshaped the room
  local sx, sy = wx, wy + 2
  U.teleport(game, "REDS_HOUSE_1F", sx, sy, "up")
  U.wait(10)
  local ow = game.overworld
  if not ow.map:isWalkableCell(sx, sy) then
    for dy = 1, 3 do
      if ow.map:isWalkableCell(wx, wy + dy) then
        sx, sy = wx, wy + dy
        U.teleport(game, "REDS_HOUSE_1F", sx, sy, "up")
        U.wait(10)
        break
      end
    end
  end
  -- U.teleport is itself a fresh map load, so the fixed spawn already
  -- parks Pikachu on the player's cell here
  check("follower spawned on map load", follower() ~= nil)
  check("map-load spawn is on the player's own cell", overlapsPlayer())

  -- climb the stairs
  check("walked up into REDS_HOUSE_2F",
        walkUntilMap("up", "REDS_HOUSE_2F", 240))
  check("warp arrival upstairs: Pikachu hidden under the player",
        overlapsPlayer())
  U.shot(game, SHOT_DIR .. "/bug863_2f_arrival.png")

  -- the draw sort tie-break (#863): sharing the player's py must list the
  -- follower first so he draws over it.  The sort runs in draw, and the
  -- shot above rendered a frame, so entities order is post-sort here.
  do
    local npc = follower()
    local p = game.overworld.player
    if npc and p and npc.py == p.py then
      local ni, pi
      for i, e in ipairs(game.overworld.entities) do
        if e == npc then ni = i elseif e == p then pi = i end
      end
      check("draw sort puts the hidden follower under the player",
            ni ~= nil and pi ~= nil and ni < pi)
    end
  end

  -- walk off the stairs: the trail should pull Pikachu out one behind
  U.hold(game, "down", 20)
  U.wait(20)
  U.hold(game, "down", 20)
  U.wait(30)
  do
    local npc = follower()
    local p = game.overworld.player
    check("two steps later Pikachu trails one cell behind",
          npc and p and npc.cellX == p.cellX and npc.cellY == p.cellY - 1)
    check("and it faces down, walking out of the stairwell",
          npc and npc.facing == "down")
  end
  U.shot(game, SHOT_DIR .. "/bug863_2f_trailing.png")

  -- back down the same stairs: descent must hide it the same way
  check("walked back down into REDS_HOUSE_1F",
        walkUntilMap("up", "REDS_HOUSE_1F", 240))
  check("warp arrival downstairs: Pikachu hidden under the player",
        overlapsPlayer())
  U.shot(game, SHOT_DIR .. "/bug863_1f_return.png")

  -- regression: a connection seam is the keepPikachu path (#427), not a
  -- respawn, so the follower must ride across it, not vanish or repark
  U.teleport(game, "PALLET_TOWN", 10, 2, "up")
  U.wait(10)
  check("crossed the Pallet Town north seam into ROUTE_1",
        walkUntilMap("up", "ROUTE_1", 300))
  do
    local npc = follower()
    local p = game.overworld.player
    check("follower survived the connection crossing", npc ~= nil)
    check("and stayed within trailing range of the player",
          npc and p and math.abs(npc.cellX - p.cellX)
                      + math.abs(npc.cellY - p.cellY) <= 2)
  end
  U.shot(game, SHOT_DIR .. "/bug863_route1_seam.png")

  local sfx = game.save.options and game.save.options.sfxVol
  if sfx == 0 then
    U.log("note: sfxVol is 0, Pikachu's steps and voice will be silent")
  end

  U.log("The shots above tell the story: on both stair arrivals only the")
  U.log("player should be visible, Pikachu is tucked under him until he")
  U.log("steps away, then it follows one cell behind facing his way.")
  U.log("If a Pikachu sits beside him the moment a warp lands, that is")
  U.log("the old bug.  The pad is yours; warp around and watch spawns.")

  while true do
    coroutine.yield()
  end
end
