Files
gen1recomp/tests/engine/game3_profile_test.lua
T
Shane McGovern 41935aa2a2 fix(game3): wave 2 - 118 review fixes, seam work, and the gate to 282 suites
Second batch of the Gen 3 review work: the review lanes closed their queues
and the test gate grew with them.

Review census: 139 routed findings -- 118 fixed, 14 invalid (the report had
Gen 4+ semantics in more than one place), 6 struck as stale after
re-verification, 1 re-routed. Every fix carries its own pret citation in
docs/game3/review-v3-triage.md; the E10 opcode closure is itemised in
docs/game3/e10-opcode-spec.md (18 ops wired, 2 reclassified to the seam class).

Representative ROM-grounded changes:
- The POKéMON start-menu entry is now gated on FLAG_SYS_POKEMON_GET the way
  retail does it (pokefirered/src/start_menu.c:217-218, flag 0x828).
- Money ops read their amount raw and gate the change on the disable byte
  (pokefirered/src/scrcmd.c:1798-1830, pokefirered/asm/macros/event.inc:1166-1186);
  random and the warp family VarGet theirs
  (pokefirered/src/scrcmd.c:455-461, :719-731).
- The HM table matches FRLG: there is no Whirlpool HM
  (pokefirered/include/constants/items.h:411-418).
- The day-care party-full guard follows src/daycare.c:525, :1081.
- Knock Off keeps its battle-scoped send-out mask
  (pokefirered/src/battle_script_commands.c:2730-2752, :4489), carried from
  the first batch.

Structural work: the I6/I9/J1 architecture items landed as seams (profile
selector, capability flags, font provider, virtual-object layer), the adopted
footprint register keeps a KEEP verdict, the quantizer target is met, and the
30 drain items plus carves 1-4 are folded in.

Tests: T6 now runs 282 top-level suites (273 + 9 new), the re-sweep ends at
267 pass / 0 fail, the engine tier is 634 suites, modkit 37, and the full
gate ran green twice with T3 active on an imported Red cache. Documented
skips are the lua5.4 oversize-save oracle where lua5.4 is absent and the
config-partials listed in docs/game3/game3-artifact-conversions-v3.md.

Docs shipped: review-v3-triage.md, e10-opcode-spec.md, rse-seams.md,
game3-suite-sweep-v113.md, game3-artifact-conversions-v3.md,
test-baseline-v3.md (plus the first-batch docs already on the branch).
2026-09-22 18:23:06 +01:00

152 lines
6.9 KiB
Lua

-- FireRed profile row: resolution rules + the value contract the wiring
-- tickets in docs/game3/rse-seams.md depend on.
--
-- The profile is deliberately inert until the handoff tickets wire it, so
-- this suite is the guard that keeps its FireRed row equal to the constants
-- the engine reads today. When a wiring ticket lands, the assertion it
-- replaces moves from a literal to a comparison against the live module.
-- luajit tests/engine/game3_profile_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 Profile = require("src.core.game3.profile")
local MapIds = require("src.core.game3.map_ids")
local prevVersion = GameVersion.get()
-- ---------------------------------------------------------------- resolution
Profile.reset()
GameVersion.set("firered")
local active = Profile.active()
eq(active.id, "firered", "active() resolves the active Gen 3 game")
check(Profile.of("firered") == active, "for(id) and active() share one cached row")
check(Profile.isGame3Version("firered") == true, "isGame3Version(firered) is true")
check(Profile.isGame3Version("red") == false, "isGame3Version(red) is false")
check(Profile.isGame3Version("not-a-game") == false, "isGame3Version(unknown) is false")
-- Cached identity: repeated lookups return the same table.
check(Profile.of("firered") == active, "resolution is cached")
-- Unknown game ids fail closed to the active game, not to nil.
local unknown = Profile.of("not-a-game")
eq(unknown.id, "firered", "an unknown game id falls back to FireRed")
-- A fresh process with no Gen 3 game selected still resolves FireRed, so
-- shared code can require the module during boot and in headless suites.
Profile.reset()
GameVersion.set("red")
eq(Profile.active().id, "firered", "non-Gen3 process fails closed to FireRed")
-- Session-scoped capability lookup reads session.version, not the process.
local caps = Profile.capabilitiesFor({ version = "firered" })
check(caps.fameChecker == true, "capabilitiesFor(session) reads the session game")
check(Profile.has({ version = "firered" }, "vsSeeker") == true, "has() reads a flag")
check(Profile.has({ version = "firered" }, "contests") == false, "has() is false for absent flags")
-- ---------------------------------------------------- FireRed value contract
Profile.reset()
GameVersion.set("firered")
local row = Profile.active()
-- Map identity: the profile describes exactly what MapIds accepts today.
for _, prefix in ipairs(row.map.prefixes) do
check(MapIds.isGame3Map(prefix .. "ANYTHING") == true,
"profile prefix " .. prefix .. " is a Game3 map for MapIds")
end
check(MapIds.isGame3Map("NOT_A_MAP") == false, "a foreign id is not a Game3 map")
eq(row.map.enginePrefix, "FR_", "map id synthesis prefix is FR_")
eq(row.map.legacyPrefixes[1], "SEVII_", "Sevii is the one legacy namespace")
eq(row.map.newGameStart.map, MapIds.NEW_GAME_START.map, "new-game start map matches MapIds")
eq(row.map.newGameStart.x, MapIds.NEW_GAME_START.x, "new-game start x matches MapIds")
eq(row.map.newGameStart.y, MapIds.NEW_GAME_START.y, "new-game start y matches MapIds")
eq(row.map.newGameStart.healMap, MapIds.NEW_GAME_START.healMap, "heal map matches MapIds")
eq(row.map.newGameStart.healX, MapIds.NEW_GAME_START.healX, "heal x matches MapIds")
eq(row.map.newGameStart.healY, MapIds.NEW_GAME_START.healY, "heal y matches MapIds")
eq(row.optionsBlock, "firered", "options block is the FireRed key")
eq(row.species.num, 412, "species count is FRLG's 412")
eq(row.species.egg, 412, "egg species is FRLG's 412")
eq(row.dexArea.defaultKey, "kanto", "dex area default is kanto")
eq(row.dexArea.dexMax, 151, "dex area max is 151")
local okGroups, groups = pcall(require, row.dexArea.mapGroups)
check(okGroups and type(groups) == "table" and next(groups) ~= nil,
"dexArea.mapGroups names a loadable map-group module")
eq(row.badges.count, 8, "eight badges")
eq(row.badges.flagBase, 0x820, "badge flag base is 0x820")
local badgeNames = {
"BOULDER", "CASCADE", "THUNDER", "RAINBOW",
"SOUL", "MARSH", "VOLCANO", "EARTH",
}
for i, name in ipairs(badgeNames) do
eq(row.badges.names[i], name, "badge " .. i .. " name is " .. name)
end
eq(row.trainers.rivalIds.squirtle, 326, "rival squirtle id")
eq(row.trainers.rivalIds.bulbasaur, 327, "rival bulbasaur id")
eq(row.trainers.rivalIds.charmander, 328, "rival charmander id")
eq(row.trainers.fallback.class, 81, "rival fallback class")
eq(row.trainers.fallback.pic, 106, "rival fallback pic")
eq(row.trainers.fallback.name, "TERRY", "rival fallback name")
eq(row.trainers.music.encounter.girl, 284, "encounter music girl")
eq(row.trainers.music.encounter.rocket, 283, "encounter music rocket")
eq(row.trainers.music.encounter.boy, 285, "encounter music boy")
eq(row.trainers.music.battle.gym, 296, "gym battle music")
eq(row.trainers.music.victory.gym, 312, "gym victory music")
eq(row.trainers.music.victory.trainer, 310, "trainer victory music")
eq(row.regionMap.switchFlag, "FLAG_SYS_SEVII_MAP_123", "region-map switch flag")
-- FRLG-only features are on for FireRed, off by default for anything else.
for _, flag in ipairs({ "fameChecker", "teachyTV", "vsSeeker", "trainerTower", "seagallop" }) do
check(row.capabilities[flag] == true, "FireRed capability " .. flag .. " is on")
end
-- The native module list the registry merges today (natives.lua:803-821).
local expectedNatives = {
"natives_corner", "natives_cutscene", "natives_daycare", "natives_elevator",
"natives_events", "natives_fame", "natives_fan_club", "natives_gift",
"natives_link", "natives_listmenu", "natives_moveteach", "natives_queries",
"natives_seagallop", "natives_size_record", "natives_tower", "natives_trade",
"natives_wireless",
}
eq(#row.nativeModules, #expectedNatives, "native module count matches the registry")
for i, name in ipairs(expectedNatives) do
eq(row.nativeModules[i], name, "native module " .. i .. " is " .. name)
end
-- ------------------------------------------------------------ static guards
-- FireRed must stay the fallback row: a missing profiles/firered.lua is a
-- hard error, not a silent nil.
Profile.reset()
local okMissing = pcall(function()
local warn = Profile.of("not-a-game")
return warn
end)
check(okMissing == true, "unknown ids resolve without raising")
-- The profile names the modules the wiring tickets will create; keep them
-- syntactically valid package names so a typo fails here, not at boot.
for _, rel in ipairs({
row.font.module,
row.dexArea.mapGroups,
row.saveRules,
}) do
check(type(rel) == "string" and rel:match("^[%w_%.]+$") ~= nil,
"module path is well-formed: " .. tostring(rel))
end
check(type(row.font.widths) == "string" and #row.font.widths > 0, "font widths path is set")
check(type(row.font.smallWidths) == "string" and #row.font.smallWidths > 0, "small font path is set")
check(type(row.extractors) == "table" and #row.extractors > 0, "aux extractor list is set")
GameVersion.set(prevVersion)
T.finish("game3_profile_test")