mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-06 17:10:55 -04:00
fix(game3): PC panel species scope fix and trainer-card var key fallback
Three follow-ups to the merge snapshot, kept as their own commit so the review of the wave commit stays intact: - The PC panel's left data panel broke in the merge: upstream inlines the egg-aware species read into frontPic while the M8 dedup had removed the duplicate local, so `sp` was referenced with no binding and the luacheck gate failed. One shared local sp = Pokemon.speciesOrEgg(hoveredMon) serves both the TV sprite and the stats card (pokefirered/src/pokemon_storage_system_data.c:1034, :1057, MON_DATA_SPECIES_OR_EGG). - Trainer-card vars fall back to the stringified key: persist_sidecar and Flags.serialize write tostring(id) keys into session.vars, so a numeric miss used to read every var as 0. - tests/game3_trainer_card_photo_tint_test.lua drives the whole Game Corner photo -> card tint chain (setvar -> UpdateTrainerCardPhotoIcons -> Flags.serialize/loadInto -> cardData) and pins c.monIconTint/c.monSpecies.
This commit is contained in:
@@ -219,7 +219,12 @@ function PcChrome.drawLeftDataPanel(hoveredMon, hoverFrame)
|
||||
|
||||
-- 1. Front Sprite in TV Screen (X: 10..73, Y: 19..80, W: 64, H: 61)
|
||||
-- pokefirered/src/pokemon_storage_system_data.c:1034, :1057 MON_DATA_SPECIES_OR_EGG
|
||||
local sprite = Pokemon.frontPic(Pokemon.speciesOrEgg(hoveredMon))
|
||||
-- Merge fix: upstream inlined the species read into frontPic (egg-aware) while
|
||||
-- our M8 dedup removed block 2's duplicate local — one shared `sp` above
|
||||
-- satisfies both sides (speciesOrEgg == speciesOf for every non-egg, and the
|
||||
-- stats card never prints spName for an egg).
|
||||
local sp = Pokemon.speciesOrEgg(hoveredMon)
|
||||
local sprite = Pokemon.frontPic(sp)
|
||||
if sprite and sprite.image then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
local sw, sh = sprite.image:getDimensions()
|
||||
|
||||
@@ -342,7 +342,13 @@ local function get_var(session, varId)
|
||||
end
|
||||
if type(store.vars) == "table" then return tonumber(store.vars[varId]) or 0 end
|
||||
end
|
||||
if session and type(session.vars) == "table" then return tonumber(session.vars[varId]) or 0 end
|
||||
if session and type(session.vars) == "table" then
|
||||
-- persist_sidecar/Flags.serialize write tostring(id) keys into session.vars;
|
||||
-- a numeric miss must fall back to the string key or every var reads 0.
|
||||
local v = session.vars[varId]
|
||||
if v == nil then v = session.vars[tostring(varId)] end
|
||||
return tonumber(v) or 0
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
-- End-to-end proof for the Game Corner photo -> trainer-card tint chain.
|
||||
-- Mirrors the real in-game wiring:
|
||||
-- setvar VAR_0x8004 (ctx.specialVars) -> special UpdateTrainerCardPhotoIcons (0x167)
|
||||
-- -> Flags.setVar into THE SAME store the card reads (Space.store in-game)
|
||||
-- -> Flags.serialize (game.save) -> Flags.loadInto (next boot)
|
||||
-- -> TrainerCard.cardData/gather -> c.monIconTint / c.monSpecies.
|
||||
-- Ground truth: saves/firered/slot1.lua (Sep 22 18:24) carries
|
||||
-- ["16450"]=2, ["16451"]=4, ["16452".."16456"]=0 with a 1-mon party (Charmander).
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local Std = require("src.core.game3.scripting.stdscripts")
|
||||
local Natives = require("src.core.game3.scripting.natives")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Schema = require("src.core.game3.save_schema_firered")
|
||||
local TrainerCard = require("src.ui.game3.trainer_card")
|
||||
|
||||
local passed, failed = 0, 0
|
||||
local function check(cond, msg)
|
||||
if cond then
|
||||
passed = passed + 1
|
||||
print("[ok] " .. msg)
|
||||
else
|
||||
failed = failed + 1
|
||||
print("[FAIL] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkEq(got, expected, msg)
|
||||
if got == expected then
|
||||
passed = passed + 1
|
||||
print(string.format("[ok] %s (got %s)", msg, tostring(got)))
|
||||
else
|
||||
failed = failed + 1
|
||||
print(string.format("[FAIL] %s: expected %s, got %s", msg, tostring(expected), tostring(got)))
|
||||
end
|
||||
end
|
||||
|
||||
local SPACE_KEY = "src.core.game3.scripting.space"
|
||||
local RT_KEY = "src.core.game3.runtime"
|
||||
|
||||
local realLoadedSpace = package.loaded[SPACE_KEY]
|
||||
local realLoadedRt = package.loaded[RT_KEY]
|
||||
|
||||
local function teardown()
|
||||
package.loaded[SPACE_KEY] = realLoadedSpace
|
||||
package.loaded[RT_KEY] = realLoadedRt
|
||||
end
|
||||
|
||||
print("=== 1. Photo script path writes tint + species into THE store the card reads ===")
|
||||
local session, store, ctx
|
||||
do
|
||||
session = Schema.newGame({ name = "RED" })
|
||||
session.party = { { speciesId = 4, species = 4 } } -- user's save: one Charmander
|
||||
|
||||
store = Flags.newStore()
|
||||
-- In-game both sides resolve Space from package.loaded; share one table so
|
||||
-- scriptStore(ctx) (natives) and script_store() (trainer card) see ONE store.
|
||||
package.loaded[SPACE_KEY] = { store = store, getStore = function() return store end }
|
||||
package.loaded[RT_KEY] = { getSession = function() return session end }
|
||||
|
||||
ctx = {
|
||||
flags = session.flags,
|
||||
vars = session.vars,
|
||||
specialVars = {},
|
||||
}
|
||||
|
||||
-- Real setvar opcode path: Flags.setVar routes 0x80xx to ctx.specialVars.
|
||||
Flags.setVar(store, ctx, 0x8004, 2) -- MON_ICON_TINT_PINK
|
||||
local yielded = Natives.special(ctx, Std.SPECIAL.UpdateTrainerCardPhotoIcons)
|
||||
check(yielded == false, "UpdateTrainerCardPhotoIcons completes without yielding")
|
||||
|
||||
checkEq(Flags.getVar(store, ctx, 0x4042), 2, "store 0x4042 tint idx is 2 (PINK)")
|
||||
checkEq(Flags.getVar(store, ctx, 0x4043), 4, "store 0x4043 icon 1 is species 4")
|
||||
for id = 0x4044, 0x4048 do
|
||||
checkEq(Flags.getVar(store, ctx, id), 0, string.format("store 0x%04X empty slot", id))
|
||||
end
|
||||
checkEq(session.vars[0x4042], nil, "special vars do NOT leak into session.vars")
|
||||
|
||||
local c = TrainerCard.cardData(session)
|
||||
checkEq(c.monIconTint, 2, "gather reads monIconTint=2 from the live store")
|
||||
checkEq(c.monSpecies[1], 4, "gather reads monSpecies[1]=4 from the live store")
|
||||
end
|
||||
|
||||
print("=== 2. Persist round trip: serialize (game.save) -> loadInto (next boot) ===")
|
||||
do
|
||||
local snap = Flags.serialize(store)
|
||||
checkEq(snap.vars["16450"], 2, "serialize writes string key [\"16450\"]=2 (save format)")
|
||||
checkEq(snap.vars["16451"], 4, "serialize writes string key [\"16451\"]=4")
|
||||
|
||||
local bootStore = Flags.newStore()
|
||||
Flags.loadInto(bootStore, snap)
|
||||
checkEq(bootStore.vars[0x4042], 2, "loadInto restores numeric 0x4042=2")
|
||||
checkEq(bootStore.vars[0x4043], 4, "loadInto restores numeric 0x4043=4")
|
||||
|
||||
package.loaded[SPACE_KEY] = { store = bootStore, getStore = function() return bootStore end }
|
||||
local c = TrainerCard.cardData(session)
|
||||
checkEq(c.monIconTint, 2, "card gather after reload reads monIconTint=2")
|
||||
checkEq(c.monSpecies[1], 4, "card gather after reload reads monSpecies[1]=4")
|
||||
end
|
||||
|
||||
print("=== 3. Store unavailable: session.vars fallback must read string keys ===")
|
||||
do
|
||||
local snap = Flags.serialize(store)
|
||||
session.vars = snap.vars -- save-format string keys, numeric miss
|
||||
package.loaded[SPACE_KEY] = nil -- Space not resolvable -> script_store() nil
|
||||
|
||||
local c = TrainerCard.cardData(session)
|
||||
checkEq(c.monIconTint, 2, "fallback reads [\"16450\"] -> monIconTint=2")
|
||||
checkEq(c.monSpecies[1], 4, "fallback reads [\"16451\"] -> monSpecies[1]=4")
|
||||
end
|
||||
|
||||
print("=== 4. Multi-mon party: all six species keys match snapshot order ===")
|
||||
do
|
||||
local session4 = Schema.newGame({ name = "RED" })
|
||||
session4.party = {
|
||||
{ speciesId = 1, species = 1 },
|
||||
{ speciesId = 4, species = 4 },
|
||||
{ speciesId = 7, species = 7 },
|
||||
{ speciesId = 25, species = 25 },
|
||||
{ speciesId = 133, species = 133 },
|
||||
{ speciesId = 152, species = 152 },
|
||||
}
|
||||
local store4 = Flags.newStore()
|
||||
package.loaded[SPACE_KEY] = { store = store4, getStore = function() return store4 end }
|
||||
package.loaded[RT_KEY] = { getSession = function() return session4 end }
|
||||
|
||||
local ctx4 = { flags = session4.flags, vars = session4.vars, specialVars = {} }
|
||||
Flags.setVar(store4, ctx4, 0x8004, 2) -- MON_ICON_TINT_PINK
|
||||
Natives.special(ctx4, Std.SPECIAL.UpdateTrainerCardPhotoIcons)
|
||||
|
||||
checkEq(Flags.getVar(store4, ctx4, 0x4042), 2, "multi-mon: store 0x4042 tint idx is 2")
|
||||
local want = { 1, 4, 7, 25, 133, 152 }
|
||||
for i = 1, 6 do
|
||||
checkEq(Flags.getVar(store4, ctx4, 0x4042 + i), want[i],
|
||||
string.format("multi-mon: store 0x%04X icon %d snapshot order", 0x4042 + i, i))
|
||||
end
|
||||
local c4 = TrainerCard.cardData(session4)
|
||||
checkEq(c4.monIconTint, 2, "multi-mon: gather monIconTint=2")
|
||||
for i = 1, 6 do
|
||||
checkEq(c4.monSpecies[i], want[i], string.format("multi-mon: gather monSpecies[%d]", i))
|
||||
end
|
||||
end
|
||||
|
||||
teardown()
|
||||
|
||||
print(string.format("Total: %d passed, %d failed", passed, failed))
|
||||
os.exit(failed == 0 and 0 or 1)
|
||||
Reference in New Issue
Block a user