Files
gen1recomp/tests/engine/game3_ow_meta_bounds_test.lua
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

70 lines
2.6 KiB
Lua

-- A corrupt OW sprite .meta must not size an allocation.
--
-- L4 regression: load_one read width/height/frameCount straight out of the
-- u16 header (OwExtract.decodeMeta does not validate) and only sanity-checked
-- the byte count against ONE frame. A header with a large frameCount passed
-- that check and then allocated aw x (h * frameCount) pixels -- with
-- frameCount = 65535 and a 32x32 frame that is 2,097,120 rows, and u16 maxima
-- reach ~4.3e9 pixels. The cache that supplies it lives in the user-writable
-- save directory.
-- luajit tests/engine/game3_ow_meta_bounds_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 OwSprites = require("src.core.game3.ow_sprites")
local OwExtract = require("src.import.gba.ow_extract")
local function u16(v) return string.char(v % 256, math.floor(v / 256) % 256) end
-- A well-formed header with an absurd frame count: MAGIC + version + inanimate
-- + graphicsId + width + height + frameCount + paletteTag.
local W, H, FRAMES = 32, 32, 65535
local meta = OwExtract.MAGIC .. string.char(1, 0) .. u16(0)
.. u16(W) .. u16(H) .. u16(FRAMES) .. u16(0)
local rgba = string.rep("\0", W * H * 4) -- exactly one frame of pixels
local decoded = OwExtract.decodeMeta(meta)
check(decoded and decoded.frameCount == FRAMES, "the fixture carries an absurd frame count")
OwSprites.install({
read = function(_, rel)
if rel:sub(-5) == ".meta" then return meta end
if rel:sub(-5) == ".rgba" then return rgba end
return nil
end,
})
-- Spy the allocation the loader asks for.
local asked = {}
local realNew = love.image.newImageData
love.image.newImageData = function(a, b, ...)
asked[#asked + 1] = { a, b }
return realNew(a, b, ...)
end
local spr = OwSprites.get(0)
love.image.newImageData = realNew
check(spr == nil, "a corrupt .meta is rejected instead of allocated")
eq(#asked, 0, "no image allocation is attempted from a corrupt header")
if #asked > 0 then
print(string.format(" (asked for %sx%s)", tostring(asked[1][1]), tostring(asked[1][2])))
end
-- A sane header still loads (regression guard).
local sane = OwExtract.MAGIC .. string.char(1, 0) .. u16(0)
.. u16(W) .. u16(H) .. u16(4) .. u16(0)
OwSprites.install({
read = function(_, rel)
if rel:sub(-5) == ".meta" then return sane end
if rel:sub(-5) == ".rgba" then return string.rep("\0", W * H * 4 * 4) end
return nil
end,
})
check(OwSprites.get(0) ~= nil, "a sane sprite sheet still loads")
T.finish("game3_ow_meta_bounds_test")