Files
gen1recomp/tests/engine/game3_pc_item_capacity_test.lua
T
Shane McGovern 2c356f28ea fix(game3): save, item, map, script and importer bugs found in review
The rest of the Gen 3 review fixes, each with a gated suite in tests/engine/.
ROM semantics were checked against pret/pokefirered.

Scripts
- givemon carried the wrong operand layout, found earlier; four more layout
  desyncs came out of pret asm/macros/event.inc: comparestat is {byte,word},
  and setptr / loadbytefromptr / setptrbyte each carry a leading byte plus a
  word. A wrong size mis-decodes every instruction after the bad one, so
  Versions.CACHE_VERSION moves to 113 and existing caches re-import.
- handlers for previously handler-less verbs: comparestat,
  bufferitemnameplural, setmonmove, setmonmetlocation, the modern
  fateful-encounter pair, the script-locals family (copylocal, setptr,
  loadbytefromptr, setptrbyte, copybyte, compare_local_to_* and
  compare_ptr_to_*), the RAM-script family (setvaddress, vgoto, vcall,
  vgoto_if, vcall_if, vmessage, vbuffermessage, vbufferstring, endram,
  returnram) and the same-map forms of the *at verbs.
- setdooropen / setdoorclosed read their coordinates through VarGet.

Battles
- Knock Off and Thief / Trick persist the item change instead of only
  touching the in-battle copy.
- knocked-off party slots are tracked in a bitmask, so a slot reused later
  does not inherit the flag.

Field and UI
- Player.reset restores facing and clears the surf flags.
- a definition-less Map.load no longer leaves collision unbound.
- an unresolved region-map section no longer reports PALLET TOWN.
- the naming screen splits input from the timer, so update(dt) stops
  indexing a number.
- the hall of fame commits through the engine save path and serializes its
  fields.

Persistence
- gameStats, the link-battle records and the trainer card are serialized.
- the PC deposit refuses at the 999 cap instead of destroying the overflow.

Robustness
- Data.load runs cached modules sandboxed.
- the file browser quotes shell arguments.
- .meta dimensions are bounds-checked and mids.idx validates its header.
2026-09-22 03:28:39 +01:00

68 lines
3.0 KiB
Lua

-- Storage.depositItem must not destroy items when the PC stack is at the cap.
--
-- Regression: the existing-stack branch capped the PC quantity with
-- math.min(MAX_ITEM_QTY, curQty + qty) and then removed the FULL qty from the
-- bag. With a PC stack already at 999, depositing more stored nothing but
-- still deleted the items from the bag -- silent item loss (reachable from
-- PcMenu's deposit action).
--
-- Fix contract: a deposit that does not fit is refused (like the 50-slot cap),
-- so the bag keeps the items. The caller already maps any failure to
-- "The PC is full.".
-- luajit tests/engine/game3_pc_item_capacity_test.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local Storage = require("src.core.game3.storage")
local Bag = require("src.core.game3.bag")
local POTION = 13
local function session_with(pcQty, bagQty)
local s = { bag = Bag.new(), storage = Storage.new() }
s.storage.items = {}
if pcQty and pcQty > 0 then s.storage.items[1] = { id = POTION, qty = pcQty } end
if bagQty and bagQty > 0 then Bag.add(s.bag, POTION, bagQty) end
return s
end
-- 1. The bug: a full PC stack must refuse, not swallow the items.
local s = session_with(Storage.MAX_ITEM_QTY, 5)
local ok, err = Storage.depositItem(s, "ITEMS", 1, 5)
check(ok == false, "depositing into a stack at MAX_ITEM_QTY is refused (err=" .. tostring(err) .. ")")
eq(s.storage.items[1].qty, Storage.MAX_ITEM_QTY, "the PC stack stays at the cap")
eq(Bag.get(s.bag, POTION), 5, "the bag keeps the items -- they are not destroyed")
-- 2. A stack with too little room for the whole deposit is refused too.
s = session_with(995, 5)
ok = Storage.depositItem(s, "ITEMS", 1, 5)
check(ok == false, "a deposit that does not fit in the stack is refused")
eq(s.storage.items[1].qty, 995, "the partial stack is unchanged")
eq(Bag.get(s.bag, POTION), 5, "the bag keeps the items when the deposit is refused")
-- 3. A deposit that exactly fills the stack succeeds and moves the items once.
s = session_with(994, 5)
ok = Storage.depositItem(s, "ITEMS", 1, 5)
check(ok == true, "a deposit that exactly fills the stack succeeds")
eq(s.storage.items[1].qty, Storage.MAX_ITEM_QTY, "the stack reaches the cap")
eq(Bag.get(s.bag, POTION), 0, "the bag is debited exactly once")
-- 4. Regression: the ordinary deposit path is unchanged.
s = session_with(0, 20)
ok = Storage.depositItem(s, "ITEMS", 1, 20)
check(ok == true, "an ordinary deposit succeeds")
eq(s.storage.items[1].qty, 20, "the new stack holds the deposited quantity")
eq(Bag.get(s.bag, POTION), 0, "the bag is emptied by the deposit")
-- 5. Regression: more than the bag holds still fails without touching the PC.
s = session_with(0, 4)
local ok5, err5 = Storage.depositItem(s, "ITEMS", 1, 5)
check(ok5 == false and err5 == "insufficient_bag_qty", "over-depositing the bag fails")
eq(Bag.get(s.bag, POTION), 4, "a refused deposit leaves the bag intact")
T.finish("game3_pc_item_capacity_test")