Files
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

132 lines
4.8 KiB
Lua

-- The per-step event chain, in the running game.
--
-- `World` kept no step counter at all until this, so `Happiness.step` and
-- `Breeding.step` were written, tested and never called: eggs never hatched.
-- CountStep (engine/overworld/events.asm) now runs between the coord events and
-- the wild roll, and DoEggStep ticks at wStepCount $80.
--
-- This walks a real party with a real egg in it until the counter reaches the
-- egg phase, then asserts the slot came out of it as a Pokemon.
--
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold \
-- POKEPORT_DRIVER=tests/drivers/gold_egg_hatch.lua \
-- perl -e 'alarm 300; exec @ARGV' \
-- python3 -c "import pty; pty.spawn(['love','.'])"
local SHOT_DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-steps"
return function(game)
local w = game.world
local fails = 0
local function wait(n) for _ = 1, n do coroutine.yield() end end
local function ok(cond, msg)
if cond then print("[steps] ok " .. msg)
else fails = fails + 1 print("[steps] FAIL " .. msg) end
return cond
end
local function clearDirs()
game.input.pressQueue = {}
for _, d in ipairs({ "up", "down", "left", "right" }) do
game.input.state[d] = false
game.input.sources[d] = nil
end
end
-- Walk back and forth on a clear row. Holding one direction for a fixed
-- stretch is the reliable shape here: a step is 16 pixels at one a frame, so
-- 20 held frames is always at least one full footfall.
local function pace(frames, dir)
for _ = 1, frames do
if w:busy() then clearDirs() return true end
table.insert(game.input.pressQueue, dir)
game.input.state[dir] = true
coroutine.yield()
end
clearDirs()
coroutine.yield()
return w:busy()
end
os.execute('mkdir -p "' .. SHOT_DIR .. '" 2>/dev/null')
wait(45)
local save = game.save
-- No wild encounters and no coord events in the way: this is about the step
-- counter, not about what else a footfall can trigger.
w.mapScenes.NEW_BARK_TOWN = 1
w:setMap("NEW_BARK_TOWN", 6, 8, "down")
wait(15)
-- An egg on its last cycle, and a mon in front of it so the party is honest.
local Mon = require("src.battle.gen2.Mon")
local lead = Mon.new(game.data, "CYNDAQUIL", 5)
save.party = { lead, {
isEgg = true, species = "TOGEPI", name = "EGG", level = 5,
eggSteps = 1, dvs = lead.dvs, moves = {},
ot = save.player and save.player.name,
otId = save.player and save.player.id,
} }
save.stepCount = nil
save.poisonStepCount = nil
local Breeding = require("src.core.gen2.Breeding")
ok(Breeding.isEgg(save.party[2]), "the party starts with an egg in slot 2")
-- DoEggStep fires at wStepCount $80, so at most 128 footfalls from zero.
local hit = false
for i = 1, 300 do
if pace(24, (i % 2 == 1) and "left" or "right") then hit = true break end
if not Breeding.isEgg(save.party[2]) then hit = true break end
if i == 1 then
ok((save.stepCount or 0) > 0,
"one lap already moved wStepCount to " .. tostring(save.stepCount))
end
end
ok((save.stepCount or 0) > 0,
"the world counts steps at all now (wStepCount = "
.. tostring(save.stepCount) .. ")")
ok(hit, "and something fired inside 128 footfalls")
ok(save.stepCount == 0x80,
"at wStepCount $80, DoEggStep's own phase (got "
.. tostring(save.stepCount) .. ")")
game.capturePath = SHOT_DIR .. "/hatch-huh.png"
wait(4)
-- "Huh?" and the hatch line advance on A; the nickname prompt is a yes/no and
-- B is NO, which is the arm that keeps the species name (HatchEggs' own
-- .nonickname). Answering YES would push the naming screen, which is a stack
-- state rather than a World busy flag and would sit there forever.
for _ = 1, 600 do
if not w:busy() then break end
table.insert(game.input.pressQueue, w.choicebox and "b" or "a")
wait(3)
end
wait(20)
local slot = save.party[2]
ok(slot ~= nil and not Breeding.isEgg(slot), "the egg is no longer an egg")
ok(slot and slot.species == "TOGEPI", "it is a TOGEPI (got "
.. tostring(slot and slot.species) .. ")")
ok(slot and (slot.hp or 0) > 0 and slot.hp == slot.maxHp,
"at full health, the way HatchEggs copies MON_MAXHP into MON_HP")
ok(slot and slot.happiness == 0x78,
"with the hatch happiness of $78 (got "
.. tostring(slot and slot.happiness) .. ")")
ok(save.pokedex and save.pokedex.caught
and save.pokedex.caught.TOGEPI, "and SetSeenAndCaughtMon ticked the #DEX")
-- HatchEggs sets EVENT_TOGEPI_HATCHED (84) by hand, for this species alone.
ok(w.events and w.events:get(84) == true,
"and the world set EVENT_TOGEPI_HATCHED")
game.capturePath = SHOT_DIR .. "/hatched.png"
wait(30)
if fails > 0 then
error(("gold egg hatch: %d assertion(s) failed"):format(fails))
end
print("[driver] PASS gold per-step chain: the egg hatched")
end