Files
gen1recomp/tests/game3_easy_chat_test.lua
T
1jamie c93f035359 fix(game3): hidden items, itemfinder, battle hud caught marker, moveset learnsets, and easy chat
- Hidden Items & Itemfinder (fixes #2314):
  * Extract BG_EVENT_HIDDEN_ITEM (kind 7) from GBA ROM with proper flag calculation (0x3E8 + id), underfoot flags, and quantities.
  * Implement overworld Itemfinder scan logic (7-tile radius, directional response, underfoot detection, SFX 65, and OEM text).
  * Wire up Bag Menu Itemfinder dispatch to overworld field runtime.

- Opponent Battle HUD Caught Marker (fixes #2313):
  * Add authentic Pokédex caught Poké Ball icon (B_INTERFACE_GFX_BALL_CAUGHT / tile 70) to opponent healthbox.
  * Match pokefirered parity in TryAddPokeballIconToHealthbox (wild encounters only, non-tutorial, non-ghost, checked against Dex).
  * Ensure status ailment icons (PSN, PAR, SLP, FRZ, BRN) take rendering priority over the caught ball marker.

- Level-Up Learnset & Movepool Assignment (fixes #2315):
  * Fix wild and generated Pokémon moveset assignment to assign the 4 most recent level-up moves up to the current level instead of getting stuck on basic moves (e.g. Tackle).

- Easy Chat / Profile Quick Chat:
  * Extract full word groups, categories, and dictionary entries from ROM.
  * Build Easy Chat interface with group selection, paginated word lists, and alphabetized filtering.
  * Fix dialog text overflowing when exiting profile / quick chat prompts.

- Nurse Joy & PC Storage / Map Polishing:
  * Implement Nurse Joy healing tray animation flow and audio cues in standard scripts.
  * Fix PC storage box header/wallpaper rendering and storage data extraction.
  * Correct Sevii Islands / Island 1 map section lookups and region map coordinate handling.
  * Fix circular dependency in battle residual handlers (partialTrap).

fixes #2325
fixes #2322
fixes #2315
fixes #2314
fixes #2313
2026-09-19 15:12:28 -05:00

188 lines
6.2 KiB
Lua

-- Unit test for Game 3 Easy Chat system, Profile Screen, Specials 0x60/0x61,
-- and the Pewter City Pokemon Center MysteryEventClub woman script behavior.
local function test(name, fn)
local ok, err = pcall(fn)
if ok then
print("[ok] " .. name)
else
print("[FAIL] " .. name .. ": " .. tostring(err))
error(err)
end
end
local EasyChatData = require("src.core.game3.easy_chat_data")
local Schema = require("src.core.game3.save_schema_firered")
local Std = require("src.core.game3.scripting.stdscripts")
local Natives = require("src.core.game3.scripting.natives")
local Adapters = require("src.core.game3.scripting.adapters")
local Flags = require("src.core.game3.scripting.flags")
test("decodes authentic word IDs and passphrases", function()
-- Default profile
local defText = EasyChatData.formatPhrase(EasyChatData.DEFAULT_PROFILE, 2, 2)
assert(defText:find("I AM A"), "Default profile line 1")
assert(defText:find("POKéMON FRIEND"), "Default profile line 2")
-- Mystery Event passphrase
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_MYSTERY_EVENT[1]) == "MYSTERY")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_MYSTERY_EVENT[2]) == "EVENT")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_MYSTERY_EVENT[3]) == "IS")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_MYSTERY_EVENT[4]) == "EXCITING")
-- Questionnaire passphrase
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_QUESTIONNAIRE[1]) == "LINK")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_QUESTIONNAIRE[2]) == "TOGETHER")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_QUESTIONNAIRE[3]) == "WITH")
assert(EasyChatData.getWord(EasyChatData.PASSPHRASE_QUESTIONNAIRE[4]) == "ALL")
end)
test("persists easyChatProfile in save schema", function()
local session = Schema.newGame()
assert(session.easyChatProfile ~= nil)
assert(#session.easyChatProfile == 4)
-- Modify profile
session.easyChatProfile = { 5178, 6167, 4107, 8207 }
local save = Schema.toSaveTable(session)
assert(save.easyChatProfile ~= nil)
assert(save.easyChatProfile[1] == 5178)
-- Restore
local loaded = Schema.fromSaveTable(save)
assert(loaded.easyChatProfile ~= nil)
assert(loaded.easyChatProfile[1] == 5178)
assert(loaded.easyChatProfile[4] == 8207)
end)
test("handles ShowEasyChatScreen special with custom profile (VAR_RESULT=1, VAR_0x8004=1, flag 0x82D)", function()
local session = Schema.newGame()
local store = Flags.newStore()
session.store = store
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
}
local ctx = {
vars = {},
specialVars = { [0x8004] = 0 }, -- EASY_CHAT_TYPE_PROFILE
}
local customWords = { 2601, 4128, 526, 2611 } -- standard "I AM A POKéMON FRIEND"
local adapters = Adapters.stub({
openEasyChat = function(opts, done)
assert(opts.type == 0)
done(true, customWords)
end,
})
local yielded = Natives.ALLOW["special:" .. Std.SPECIAL.ShowEasyChatScreen](ctx, adapters)
-- Poll until done
if ctx.nativePoll then
while not ctx.nativePoll() do end
end
-- Assertions:
-- 1. VAR_RESULT (0x800D) = 1 (TRUE)
assert(Flags.getVar(nil, ctx, 0x800D) == 1)
-- 2. VAR_0x8004 = 1 (custom phrase, does not match MYSTERY EVENT IS EXCITING)
assert(Flags.getVar(nil, ctx, 0x8004) == 1)
-- 3. FLAG_SYS_SET_TRAINER_CARD_PROFILE (0x82D) is set
assert(Flags.getFlag(store, ctx, 0x82D) == true)
assert(session.flags[0x82D] == true)
-- 4. session.easyChatProfile is updated
assert(session.easyChatProfile[1] == customWords[1])
end)
test("handles ShowEasyChatScreen special with mystery event passphrase (VAR_0x8004=0)", function()
local session = Schema.newGame()
local store = Flags.newStore()
session.store = store
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
}
local ctx = {
vars = {},
specialVars = { [0x8004] = 0 }, -- EASY_CHAT_TYPE_PROFILE
}
local mysteryWords = EasyChatData.PASSPHRASE_MYSTERY_EVENT
local adapters = Adapters.stub({
openEasyChat = function(opts, done)
done(true, mysteryWords)
end,
})
Natives.ALLOW["special:" .. Std.SPECIAL.ShowEasyChatScreen](ctx, adapters)
if ctx.nativePoll then
while not ctx.nativePoll() do end
end
-- VAR_RESULT = 1 (TRUE)
assert(Flags.getVar(nil, ctx, 0x800D) == 1)
-- VAR_0x8004 = 0 (matches special passphrase!)
assert(Flags.getVar(nil, ctx, 0x8004) == 0)
assert(Flags.getFlag(store, ctx, 0x82D) == true)
end)
test("handles ShowEasyChatScreen special when cancelled (VAR_RESULT=0)", function()
local session = Schema.newGame()
local store = Flags.newStore()
session.store = store
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
}
local ctx = {
vars = {},
specialVars = { [0x8004] = 0 },
}
local adapters = Adapters.stub({
openEasyChat = function(opts, done)
done(false, opts.words)
end,
})
Natives.ALLOW["special:" .. Std.SPECIAL.ShowEasyChatScreen](ctx, adapters)
if ctx.nativePoll then
while not ctx.nativePoll() do end
end
-- VAR_RESULT = 0 (FALSE)
assert(Flags.getVar(nil, ctx, 0x800D) == 0)
-- VAR_0x8004 = 1 (not 0, so doesn't take special phrase branch)
assert(Flags.getVar(nil, ctx, 0x8004) == 1)
-- FLAG_SYS_SET_TRAINER_CARD_PROFILE not set
assert(not Flags.getFlag(store, ctx, 0x82D))
end)
test("formats and displays phrase with ShowEasyChatMessage", function()
local session = Schema.newGame()
session.easyChatProfile = { 5178, 6167, 4107, 8207 }
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
}
local openedMsg = nil
local adapters = Adapters.stub({
openMessage = function(text)
openedMsg = text
end,
})
Natives.ALLOW["special:" .. Std.SPECIAL.ShowEasyChatMessage]({}, adapters)
assert(openedMsg ~= nil)
assert(openedMsg:find("MYSTERY EVENT"), "Contains line 1 words")
assert(openedMsg:find("IS EXCITING"), "Contains line 2 words")
end)
test("extracts easy chat data from ROM via EasyChatExtract", function()
local EasyChatExtract = require("src.import.gba.easy_chat_extract")
assert(EasyChatExtract.extractFromRom ~= nil)
assert(EasyChatExtract.run ~= nil)
end)
print("[test] All Easy Chat tests passed!")