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

130 lines
4.2 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local PRET = "../pokefirered/data/specials.inc"
local function readPret()
local fh = io.open(PRET, "r")
if not fh then return nil end
local names, index = {}, 0
for line in fh:lines() do
local nm = line:match("^%s*def_special%s+([%w_]+)")
if nm and nm ~= "ptr" then
names[index] = nm
index = index + 1
end
end
fh:close()
return names, index
end
local pretNames, pretCount = readPret()
if not pretNames then
print("[skip] ../pokefirered not present; nothing to diff against")
os.exit(0)
end
local Std = require("src.core.game3.scripting.stdscripts")
local Natives = require("src.core.game3.scripting.natives")
local ENGINE_BASE = Std.SPECIAL_ENGINE_BASE or 0xF000
local ALIASES = Std.SPECIAL_ALIASES or {}
local NAME_BY_ID = Std.SPECIAL_NAME_BY_ID or {}
print("[test] 1. specials.inc parsed")
check(pretCount == 444, "444 def_special entries (got " .. tostring(pretCount) .. ")")
check(pretNames[0] == "HealPlayerParty", "index 0 is HealPlayerParty")
print("[test] 2. Std.SPECIAL names match their pret index")
local seen = {}
for name, id in pairs(Std.SPECIAL) do
if id < ENGINE_BASE then
local canonical = ALIASES[name] or name
check(pretNames[id] == canonical, string.format(
"Std.SPECIAL.%s = 0x%X is %s in pret (want %s)",
name, id, tostring(pretNames[id]), canonical))
check(pretNames[id] ~= "NullFieldSpecial", string.format(
"Std.SPECIAL.%s = 0x%X is not a NullFieldSpecial slot", name, id))
seen[id] = true
end
end
print("[test] 3. every Natives.ALLOW special key is a known cart id")
local bound = {}
for key in pairs(Natives.ALLOW) do
local rest = key:match("^special:(.+)$")
if rest then
local id = tonumber(rest)
check(id ~= nil, "ALLOW key '" .. key .. "' is a decimal special id")
if id and id < ENGINE_BASE then
bound[#bound + 1] = id
local declared = NAME_BY_ID[id]
check(declared ~= nil, string.format(
"special 0x%X is declared in Std.SPECIAL (pret calls it %s)", id, tostring(pretNames[id])))
if declared then
check(pretNames[id] == declared, string.format(
"bound special 0x%X runs %s and pret names it %s", id, declared, tostring(pretNames[id])))
end
end
end
end
check(#bound > 20, "at least 20 cart specials bound (got " .. #bound .. ")")
print("[test] 4. the ids the audit called mis-bound")
local WANT = {
ChoosePartyMon = 0x9F,
ChangePokemonNickname = 0x9E,
BufferMonNickname = 0x7C,
IsMonOTIDNotPlayers = 0x7D,
GetBattleOutcome = 0xB4,
GetDaycareState = 0xB6,
SetUnlockedPokedexFlags = 0x181,
EnableNationalPokedex = 0x16F,
IsNationalPokedexEnabled = 0x193,
StartOldManTutorialBattle = 0x9D,
Script_IsFanClubMemberFanOfPlayer = 0xA3,
Script_GetNumFansOfPlayerInTrainerFanClub = 0xA4,
Script_BufferFanClubTrainerName = 0xA5,
Script_TryLoseFansFromPlayTimeAfterLinkBattle = 0xA6,
Script_TryLoseFansFromPlayTime = 0xA7,
Script_SetPlayerGotFirstFans = 0xA8,
Script_UpdateTrainerFanClubGameClear = 0xA9,
Script_TryGainNewFanFromCounter = 0xAA,
GetHeracrossSizeRecordInfo = 0x77,
CompareHeracrossSize = 0x78,
GetMagikarpSizeRecordInfo = 0x79,
CompareMagikarpSize = 0x7A,
GetProfOaksRatingMessage = 0xD5,
}
for name, id in pairs(WANT) do
check(Std.SPECIAL[name] == id, string.format("Std.SPECIAL.%s == 0x%X (got %s)",
name, id, tostring(Std.SPECIAL[name])))
check(Natives.ALLOW["special:" .. id] ~= nil, string.format("%s (0x%X) has a handler", name, id))
end
print("[test] 5. ids pret never calls stay unbound")
for id = 0, pretCount - 1 do
if pretNames[id] == "NullFieldSpecial" then
check(Natives.ALLOW["special:" .. id] == nil,
string.format("0x%X (NullFieldSpecial) is unbound", id))
end
end
check(Natives.ALLOW["special:" .. 0x18B] ~= nil, "0x18B OpenMuseumFossilPic is bound (display-only, no dex flags)")
check(Natives.ALLOW["special:" .. 0x19D] ~= nil, "0x19D RemoveBerryPowderVendorMenu is bound")
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")