mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
big bug squash
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user