mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
116 lines
4.7 KiB
Lua
116 lines
4.7 KiB
Lua
-- Driver: issue #11 -- Blue's House wall Town Map phantom pickup + the
|
|
-- sell crash it causes.
|
|
--
|
|
-- In pokered the framed Town Map on the wall of Blue's House
|
|
-- (data/maps/objects/BluesHouse.asm BLUESHOUSE_TOWN_MAP) is a plain
|
|
-- text object -- talking to it prints _BluesHouseTownMapText
|
|
-- ("It's a big map! This is useful!") and nothing enters the bag.
|
|
-- The ROM object carries the 0x80 "has item payload" bit with a payload
|
|
-- id of 0 (ITEM_NONE), which our extractor copies through as item="0".
|
|
-- The engine's item-ball branch treated the truthy string "0" as a real
|
|
-- item, so pressing A picked up a bogus item "0" -- and selling that
|
|
-- unknown id later hard-crashed ShopMenu.sell (nil item def).
|
|
--
|
|
-- This driver reproduces both halves and asserts the correct Gen1
|
|
-- behavior, so it fails while the bug exists and passes once fixed.
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local Bag = require("src.inventory.Bag")
|
|
local Screens = require("src.ui.Screens")
|
|
|
|
local pass, fail = 0, 0
|
|
local function check(ok, label)
|
|
if ok then pass = pass + 1; U.log("PASS", label)
|
|
else fail = fail + 1; U.log("FAIL", label) end
|
|
end
|
|
|
|
-- defensive: keep the pre-fix "<name> found 0!" box from erroring on a
|
|
-- nil player name (the box never appears after the fix)
|
|
game.save.player = game.save.player or {}
|
|
game.save.player.name = game.save.player.name or "RED"
|
|
|
|
-- ---- Part 1: talking to the wall Town Map must not pick anything up ----
|
|
U.teleport(game, "BLUES_HOUSE", 2, 6, "up")
|
|
U.wait(5)
|
|
local wallmap
|
|
for _, n in ipairs(game.overworld.npcs) do
|
|
if n.def and n.def.name == "BLUESHOUSE_TOWN_MAP" then wallmap = n end
|
|
end
|
|
check(wallmap ~= nil, "wall Town Map object present")
|
|
if wallmap then
|
|
game.overworld:talkTo(wallmap)
|
|
U.wait(8)
|
|
U.shot(game, DIR .. "/bh_1_wallmap.png")
|
|
-- correct Gen1 behavior: nothing enters the bag
|
|
check(game.save.inventory["0"] == nil, "no phantom item '0' in bag")
|
|
check((game.save.inventory["0"] == nil) and (game.save.bagOrder == nil
|
|
or not (function()
|
|
for _, id in ipairs(game.save.bagOrder) do
|
|
if id == "0" then return true end
|
|
end
|
|
return false
|
|
end)()), "bag order has no '0' entry")
|
|
-- dismiss whatever box is up
|
|
for _ = 1, 4 do U.tap(game, "b"); U.wait(3) end
|
|
end
|
|
|
|
-- ---- Part 2: selling an unknown id must not crash ----------------------
|
|
-- Seed a corrupted bag (a save that already picked up "0" before the fix,
|
|
-- or any legacy/unknown id) and drive the mart SELL path over it.
|
|
while game.stack:top() do game.stack:pop() end
|
|
U.teleport(game, "PEWTER_MART", 2, 5, "left")
|
|
U.wait(5)
|
|
game.save.money = 3000
|
|
-- known bag state regardless of whether part 1 leaked a phantom "0"
|
|
game.save.inventory = {}
|
|
game.save.bagOrder = nil
|
|
Bag.add(game.save, "0", 1)
|
|
check(game.save.inventory["0"] == 1, "seeded unknown item '0' in bag")
|
|
|
|
-- open the clerk's BUY/SELL/QUIT menu exactly as the mart interaction does
|
|
Screens.push(game, "ShopMenu", {})
|
|
U.wait(4)
|
|
U.tap(game, "down") -- BUY -> SELL
|
|
U.wait(4)
|
|
U.tap(game, "a") -- open the SELL list
|
|
U.wait(6)
|
|
local sellList = game.stack:top()
|
|
check(sellList and sellList.items ~= nil, "SELL list opened")
|
|
U.shot(game, DIR .. "/bh_2_sell_list.png")
|
|
|
|
if sellList and sellList.items then
|
|
-- find the bogus "0" row
|
|
local item, idx
|
|
for i, it in ipairs(sellList.items) do
|
|
if it.value == "0" then item, idx = it, i end
|
|
end
|
|
check(item ~= nil, "bogus '0' row present in sell list")
|
|
if item then
|
|
sellList.index = idx
|
|
local footerBefore = sellList.footer
|
|
-- Invoke the real onChoose the ListMenu would call on A. Before the
|
|
-- fix this throws at math.floor(def.price/2) with def=nil; we catch
|
|
-- it so the run reports the crash instead of hanging on love's error
|
|
-- screen.
|
|
local ok, err = pcall(sellList.onChoose, item, sellList)
|
|
if not ok then
|
|
fail = fail + 1
|
|
U.log("FAIL", "selling '0' CRASHED: " .. tostring(err))
|
|
else
|
|
check(true, "selling '0' did not crash")
|
|
-- nothing may be sold: the guard returns before any QuantityBox
|
|
check(game.save.inventory["0"] == 1, "unknown item still in bag (not sold)")
|
|
check(sellList.footer ~= footerBefore
|
|
and tostring(sellList.footer):find("price") ~= nil,
|
|
"sell footer shows the unsellable message")
|
|
end
|
|
U.wait(4)
|
|
U.shot(game, DIR .. "/bh_3_sell_choose.png")
|
|
end
|
|
end
|
|
|
|
U.log("RESULT", ("pass=%d fail=%d"):format(pass, fail))
|
|
if fail == 0 then U.log("RESULT", "ALL PASS") else U.log("RESULT", "HAS FAILURES") end
|
|
end
|