Files
gen1recomp/tests/engine/game3_versions_game_test.lua
T
Shane McGovern e2454bf568 fix(game3): review v3 fixes (battle, field, script, save) + gated T6 tier
Second wave of fixes from the Gen 3 codebase review (210 findings), the
regressions the full-suite sweep surfaced, and a T6 tier so the top-level
tests/game3_*.lua suites run inside ./scripts/test.sh instead of outside it.

Battle: Knock Off and Thief/Trick item persistence follow the ROM, not the
review - the party slot is never written through during battle, and the
knocked-off mask suppresses the item on later send-outs
(pokefirered/src/battle_script_commands.c:2731 MOVE_EFFECT_KNOCK_OFF with the
STICKY_HOLD guard at :2732, opponent-steal guard at :2610-2622, mask use at
:4489). Rapid Spin keeps its one-per-use chain order (:8435-8474); Growl
keeps pret's target (src/data/battle_moves.h, include/battle.h:63).

Scripting: operand layouts match pret/asm/macros/event.inc - givemon 15 bytes
(:989-997), comparestat {B,W} (:1573-1576), setptr/loadbytefromptr/setptrbyte
take a word pointer (:118-137). 18 of the 20 E10 ops are wired per
src/scrcmd.c (per-op citations in docs/game3/e10-opcode-spec.md), money and
random and the warp family read their operands as VarGets
(src/scrcmd.c:1798-1830, :455-461, :719-731), and the day-care party-full
guard is in place (src/daycare.c:525,:1081).

Field and UI: fishing counts rounds as the ROM does
(src/field_player_avatar.c:1740-1765), an unresolved map section no longer
reports Pallet Town (src/region_map.c:3782), and a font provider sits behind
a FireRed capability/profile seam.

Save data: profile-driven Options.block, a save version round-trip, and slot
id validation that blocks path traversal (slotDiskPath("firered", "../evil")
returns nil).

Importer: the parallel import path now writes both completion markers,
object kind/clone bytes decode as pret defines them
(include/global.fieldmap.h:110-130), and the HM table drops Whirlpool
(include/constants/items.h:411-418).

Tests: six new scenario suites (battle_ai, capture, event, menu, move,
overworld) and eight engine suites (profile, capabilities, cache paths, font
provider, options block, save version round-trip, version dispatch,
versions_game). T6 runs every top-level game3 suite at GAME3_JOBS default 8
with per-suite logs, failure cause classification and a KNOWN_GAME3_FAILURES
ceiling of 23 as a shrinking guard.

Docs: the working notes under docs/game3 (triage ledger, pret citation audit,
e10 opcode spec, RSE seams, sweep v3/v113, test baseline, artifact
conversions, merge trial).

Full gate at this snapshot: exit 0, all tiers passed - engine 624/624,
T6 279/279 (0 known, 0 fresh failures), gen2 146/146, modkit 37/37, luacheck
clean, privacy gate 3/3 over a 9,711-file publication set.
2026-09-22 12:22:50 +01:00

81 lines
3.3 KiB
Lua

-- T6.3a: the Versions.game(id) facade skeleton. New file, unwired; this
-- suite pins the fail-closed contract and the FireRed/LeafGreen rows so the
-- T6.2 wiring can alias Versions.game without touching this suite.
-- luajit tests/engine/game3_versions_game_test.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local GameVersion = require("src.core.GameVersion")
local VersionsGame = require("src.import.gba.versions_game")
local Versions = require("src.import.gba.versions")
local prevVersion = GameVersion.get()
-- -------------------------------------------------------- FireRed resolution
VersionsGame.reset()
GameVersion.set("firered")
check(VersionsGame.game("firered") == Versions,
"firered resolves the existing monolith")
check(VersionsGame.game("leafgreen") == Versions,
"leafgreen shares the FireRed tables")
check(VersionsGame.game(nil) == Versions,
"nil resolves the active game's table")
check(VersionsGame.game("") == Versions, "an empty id resolves the active game")
check(VersionsGame.game("ruby") == Versions,
"an unregistered RSE id falls back to FireRed's table")
-- A non-Gen3 process still resolves FireRed, so extractor tooling can require
-- this module without booting a game.
VersionsGame.reset()
GameVersion.set("red")
check(VersionsGame.game(nil) == Versions,
"a non-Gen3 process fails closed to FireRed's table")
-- -------------------------------------------- the table contract the rows owe
VersionsGame.reset()
GameVersion.set("firered")
local tables = VersionsGame.game("firered")
check(type(tables.MAPS) == "table", "MAPS is a table")
check(type(tables.WARPS) == "table", "WARPS is a table")
check(type(tables.TILESETS) == "table", "TILESETS is a table")
check(type(tables.PAIR_TILESET) == "table", "PAIR_TILESET is a table")
check(type(tables.NUM_SPECIES) == "number", "NUM_SPECIES is a number")
check(type(tables.CACHE_VERSION) == "number", "CACHE_VERSION is a number")
eq(tables.NUM_SPECIES, 412,
"NUM_SPECIES is 412 (pret: pokefirered/include/constants/species.h:421-423)")
-- ------------------------------------------------------ registration + cache
local probe = { MAPS = {}, NUM_SPECIES = 1 }
package.loaded["tests.versions_game_probe"] = probe
check(VersionsGame.register("testgame", "tests.versions_game_probe") == true,
"register accepts an id + module path")
check(VersionsGame.game("testgame") == probe, "a registered row resolves its module")
check(VersionsGame.register("", "tests.versions_game_probe") == false,
"register rejects an empty id")
check(VersionsGame.register("testgame", 7) == false, "register rejects a non-string path")
-- A broken row does not raise: it warns once and falls back to FireRed.
check(VersionsGame.register("busted", "no.such.module") == true, "register a broken row")
check(VersionsGame.game("busted") == Versions, "a broken row falls back to FireRed")
-- reset drops resolutions and keeps registrations.
VersionsGame.reset()
check(VersionsGame.game("testgame") == probe, "registrations survive reset")
check(VersionsGame.game("firered") == Versions, "resolutions re-resolve after reset")
-- cleanup
VersionsGame.GAMES["testgame"] = nil
VersionsGame.GAMES["busted"] = nil
package.loaded["tests.versions_game_probe"] = nil
VersionsGame.reset()
GameVersion.set(prevVersion)
T.finish("game3_versions_game_test")