Files
gen1recomp/tests/emerald_contest_ui_test.lua
T
bryanthaboi ba24266192 quick fix
2026-09-28 07:21:38 -04:00

461 lines
22 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local ROM_PATH = os.getenv("POKEPORT_EMERALD_ROM") or "../pokeemerald/pokeemerald.gba"
local f = io.open(ROM_PATH, "rb")
if not f then
print("emerald_contest_ui_test: skipped (no ROM at " .. ROM_PATH .. ")")
os.exit(0)
end
local data = f:read("*a")
f:close()
if data:sub(0xAD, 0xB0) ~= "BPEE" then
print("emerald_contest_ui_test: skipped (" .. ROM_PATH .. " is not Emerald)")
os.exit(0)
end
local GV = require("src.core.GameVersion")
local function stubAnims(fn)
local Anim = require("src.core.game3.battle.anim")
local launchMove = Anim.launchMove
Anim.launchMove = function(_, opts)
if opts and opts.onEnd then opts.onEnd() end
return true
end
local ok, err = pcall(fn)
Anim.launchMove = launchMove
if not ok then error(err, 0) end
end
local Versions = require("src.import.gba.versions")
local Rom = require("src.import.gba.rom")
GV.set("emerald")
Versions.select("emerald")
local sha = GV.VERSIONS.emerald.sha1
local rom = assert(Rom.open({
info = function() return { size = #data, md5 = sha } end,
read = function(_, _, off, len) return data:sub(off + 1, off + len) end,
}, GV.forSha1(sha)))
local ROOT = "data/generated/gba"
local files = {}
local cache = {
write = function(_, rel, bytes) files[rel] = bytes; return true end,
read = function(_, rel) return files[rel] end,
}
local home = os.getenv("HOME") or ""
local identity = os.getenv("POKEPORT_IDENTITY")
local IDROOT
if identity and identity ~= "" then
for _, r in ipairs({ home .. "/Library/Application Support/LOVE/" .. identity .. "/emerald/",
home .. "/.local/share/love/" .. identity .. "/emerald/" }) do
local h = io.open(r .. ROOT .. "/scripts/text.lua", "rb")
if h then h:close(); IDROOT = r; break end
end
end
local function readFile(path)
if files[path] then return files[path] end
if not IDROOT then return nil end
local h = io.open(IDROOT .. path, "rb")
if not h then return nil end
local s = h:read("*a")
h:close()
return s
end
package.loaded["src.core.game3.dataset"] = {
cache = function() return { read = function(_, rel) return readFile(rel) end, exists = function(_, rel) return readFile(rel) ~= nil end } end,
mountExtractRoots = function() end,
}
print("[test] extractors (pokeemerald/src/contest.c:1298, contest_painting.c:66)")
local GX = require("src.import.gba.rse.contest_gfx_extract")
local PX = require("src.import.gba.rse.contest_painting_extract")
local CX = require("src.import.gba.rse.contest_extract")
eq((GX.run(rom, cache, { cacheRoot = ROOT, game = "emerald" })), true, "contest stage gfx extractor ran")
eq((PX.run(rom, cache, { cacheRoot = ROOT, game = "emerald" })), true, "contest painting extractor ran")
CX.run(rom, cache, { cacheRoot = ROOT, game = "emerald" })
for _, rel in ipairs(GX.REQUIRED) do check(files[ROOT .. "/" .. rel] ~= nil, rel .. " written") end
for _, rel in ipairs(PX.REQUIRED) do check(files[ROOT .. "/" .. rel] ~= nil, rel .. " written") end
check(GX.ready(cache, ROOT) and PX.ready(cache, ROOT), "both manifests are ready")
local Vram = require("src.ui.game3.rse.contest_vram")
Vram.reset()
Vram.reader = readFile
local gm = Vram.manifest("rse/contest_gfx")
eq(#files[gm.gfx.interface.path], 0x2000, "interface tiles decompress to 8 KB (256 tiles)")
eq(#files[gm.gfx.audience.path], 0x2000, "audience tiles hold two 0x1000 frames")
eq(gm.maps.interface.entries, 32 * 64, "interface tilemap is 32x64")
eq(gm.sliderHeartY[0] .. "," .. gm.sliderHeartY[3], "36,156", "slider heart rows (contest.c:369)")
eq(gm.windows[4].left .. "," .. gm.windows[4].top .. "," .. gm.windows[4].width .. "," .. gm.windows[4].height, "1,15,17,4",
"general text window template (contest.c:896)")
eq(gm.windows[10].left, 11, "move description window starts at column 11")
eq(#gm.palettes.interface_audience, 256, "interface + audience palette is 256 colors")
eq(gm.sine[64], 256, "gSineTable[64] is 256")
eq(#gm.affine.sliderHeart[1], 3, "slider heart disappear anim has two frames + end")
local pm = Vram.manifest("rse/contest_painting")
eq(pm.frames.lobby.entries, 1024, "lobby frame tilemap RL-decompresses to 32x32")
eq(pm.frames.cool.tiles, 176, "cool frame RL-decompresses to 176 tiles")
eq(pm.pointillism.count, 3200, "3200 pointillism points")
eq(#pm.framePalette, 128, "eight frame palettes")
eq(pm.captions[4], "gContestPaintingBeauty2", "museum caption table order")
print("[test] image processing (pokeemerald/src/image_processing_effects.c)")
local Fx = require("src.ui.game3.rse.contest_image_fx")
local function canvas(fill)
local px = {}
for i = 0, 64 * 64 - 1 do px[i] = fill end
return px
end
local px = canvas(Fx.ALPHA)
px[10 * 64 + 10] = Fx.rgb2(10, 20, 30)
local ctx = Fx.context(px, { effect = Fx.EFFECT.GRAYSCALE_LIGHT, quantizeEffect = Fx.QUANTIZE.GRAYSCALE })
Fx.apply(ctx)
eq(px[10 * 64 + 10], Fx.rgb2(21, 21, 21), "grayscale light: Q8.8 weights then red + 3")
Fx.quantize(ctx)
eq(px[10 * 64 + 10], 22, "grayscale quantize maps gray 21 to index 22")
eq(px[0], 0, "transparent pixels quantize to the palette start")
eq(ctx.palette[22], Fx.rgb2(21, 21, 21), "grayscale preset palette")
px = canvas(Fx.ALPHA)
for y = 20, 22 do for x = 20, 22 do px[y * 64 + x] = Fx.rgb2(28, 28, 28) end end
ctx = Fx.context(px, { effect = Fx.EFFECT.OUTLINE_COLORED, personality = 3 })
Fx.apply(ctx)
eq(px[20 * 64 + 20], Fx.rgb2(23, 0, 0), "outline pixel takes the personality color (type 3 red, strength 0)")
eq(px[21 * 64 + 21], 0x7FFF, "interior light pixel turns white")
eq(px[0], Fx.ALPHA, "outline keeps transparency")
px = canvas(Fx.rgb2(3, 5, 7))
ctx = Fx.context(px, { effect = Fx.EFFECT.INVERT })
Fx.apply(ctx)
eq(px[100], Fx.rgb2(28, 26, 24), "invert")
px = canvas(Fx.rgb2(5, 9, 13))
ctx = Fx.context(px, { quantizeEffect = Fx.QUANTIZE.STANDARD_LIMITED_COLORS })
Fx.quantize(ctx)
eq(px[5], 1, "standard quantize: first color gets index 1")
eq(ctx.palette[1], Fx.rgb2(8, 12, 16), "standard quantize rounds channels up to multiples of 4")
eq(ctx.palette[0xDF], Fx.rgb2(15, 15, 15), "limited palette overflow gray at 0xDF")
local points = {}
local pb = files[pm.pointillism.path]
for i = 1, #pb do points[i - 1] = pb:byte(i) end
px = canvas(Fx.rgb2(16, 16, 16))
ctx = Fx.context(px, { effect = Fx.EFFECT.POINTILLISM, pointillism = points, pointillismCount = pm.pointillism.count })
Fx.apply(ctx)
local changed = 0
for i = 0, 64 * 64 - 1 do if px[i] ~= Fx.rgb2(16, 16, 16) then changed = changed + 1 end end
check(changed > 2000, "pointillism touches most of the canvas (" .. changed .. " pixels)")
eq(px[29 * 64 + 0] ~= Fx.rgb2(16, 16, 16), true, "first pointillism point lands at column 0 row 29")
print("[test] contest stage + results play the engine's events (pokeemerald/src/contest.c:1726)")
local Contest = require("src.core.game3.rse.contest")
local Util = require("src.core.game3.rse.contest_util")
local man = assert(load(files[ROOT .. "/" .. CX.SUB .. "/manifest.lua"], "@m", "t", {}))()
local moves = require("src.import.gba.contest_moves_extract").extract(rom)
local D = Contest.buildData(moves, man)
local function lcg(seed)
local Rng = require("src.core.game3.rng")
local v = seed
return function()
v = (Rng.mulU32(v, 1103515245) + 24691) % 4294967296
return math.floor(v / 65536)
end
end
local Stage = require("src.ui.game3.rse.contest")
local textTable
if IDROOT then
local chunk = loadfile(IDROOT .. ROOT .. "/scripts/text.lua")
textTable = chunk and chunk() or nil
end
Stage.ir = function(key)
if textTable and textTable[key] then return textTable[key] end
return { { t = "text", s = key }, { t = "eos" } }
end
Stage.has = function(key) return true end
local C = require("src.core.game3.constants").of("emerald")
local function mv(name) return C:require("moves", name) end
local PLAYER = {
species = C:require("species", "SPECIES_SALAMENCE"), nickname = "SALAMENCE", trainerName = "NICK",
moves = { mv("MOVE_HEADBUTT"), mv("MOVE_EMBER"), mv("MOVE_DRAGON_BREATH"), mv("MOVE_FLY") },
cool = 120, beauty = 40, cute = 10, smart = 20, tough = 80, sheen = 30, personality = 12345, otId = 1,
}
local function newContest(seed, category)
local c = Contest.new({ data = D, category = category or 0, rank = 0, rng = lcg(seed) })
c:setContestants({ gameClear = false, player = PLAYER })
c:calculateRound1Points()
return c
end
local function heartsOf(appeal)
local q = appeal / 10
local h = q >= 0 and math.floor(q) or -math.floor(-q)
if h > 16 then h = 16 elseif h < -16 then h = -16 end
return h
end
local function countHearts(screen, i)
local row = screen.c.turnOrder[i] * 5 + 2
local red, black = 0, 0
local base = ({ [0] = 0x5011, 0x6011, 0x7011 })[i] or 0x8011
for y = row, row + 1 do
for x = 22, 29 do
local e = screen.bg[0]:get(x, y)
if e == base + 1 then red = red + 1 elseif e == base + 3 then black = black + 1 end
end
end
return red, black
end
for _, case in ipairs({ { seed = 0x1234, sched = { 2, 0, 2, 3, 1 }, cat = 0 }, { seed = 0xBEEF, sched = { 1, 1, 0, 2, 3 }, cat = 0 },
{ seed = 0x77, sched = { 3, 2, 1, 0, 2 }, cat = 4 } }) do
local ref = newContest(case.seed, case.cat)
ref:init()
ref:run(case.sched)
local c = newContest(case.seed, case.cat)
local turns, heartsOk, heartsChecked = 0, true, 0
local screen
local sched = case.sched
local Anim = require("src.core.game3.battle.anim")
local launchMove, launched = Anim.launchMove, {}
Anim.launchMove = function(move, opts)
launched[#launched + 1] = { move = move, opts = opts }
if opts.onEnd then opts.onEnd() end
return true
end
screen = Stage.open({
contest = c, headless = true, moveName = function(m) return "MOVE" .. m end,
autoInput = function(s)
if s.m.tasks:isActive(s:func("taskHandleMoveSelectInput")) then
c.contest.playerMoveChoice = sched[c.contest.appealNumber + 1]
end
return { new = { a = true }, held = { a = true }, rep = {} }
end,
})
Anim.launchMove = launchMove
eq(screen.done, true, string.format("seed %04X: the stage finishes all five appeals", case.seed))
eq(c.contest.appealNumber, 5, "five appeal rounds ran through the UI")
local same = true
for i = 0, 3 do
if c.totals[i] ~= ref.totals[i] or c.standings[i] ~= ref.standings[i] or c.round2[i] ~= ref.round2[i] then same = false end
end
check(same, string.format("seed %04X: UI playback leaves the engine result unchanged (totals %d %d %d %d)", case.seed,
c.totals[0], c.totals[1], c.totals[2], c.totals[3]))
check(screen.frames > 2000 and screen.frames < 20000, "stage ran " .. screen.frames .. " frames")
check(#launched > 0, "appeal turns launch the shared move-animation VM")
local sample = launched[1]
eq(sample.opts.ctx.isContest, true, "contest animations enter jumpifcontest context")
eq(sample.opts.attackerId .. "/" .. sample.opts.targetId, "2/3", "contest move animation uses battlers 2 and 3")
eq(sample.opts.coordinateOverrides[2].x .. "/" .. sample.opts.coordinateOverrides[3].x, "112/48",
"contest animation uses opponent-right and player-right coordinates")
end
print("[test] hearts on BG0 track every contestant's appeal at each turn end (pokeemerald/src/contest.c:3737)")
do
local c = newContest(0x4242, 0)
local mismatches, checks = 0, 0
local turnEnd = Stage.turnEndStep
Stage.turnEndStep = function(self, t)
for i = 0, 3 do
local red, black = countHearts(self, i)
local h = heartsOf(self.c.status[i].appeal)
checks = checks + 1
if (h >= 0 and (red ~= h or black ~= 0)) or (h < 0 and (black ~= -h or red ~= 0)) then mismatches = mismatches + 1 end
end
return turnEnd(self, t)
end
stubAnims(function() Stage.open({ contest = c, headless = true, moveName = function(m) return "M" .. m end }) end)
Stage.turnEndStep = turnEnd
eq(checks, 80, "checked 4 contestants at 20 turn ends")
eq(mismatches, 0, "heart tiles match hearts(appeal) for every contestant")
end
local Results = require("src.ui.game3.rse.contest_results")
do
local c = newContest(0x99, 0)
stubAnims(function() Stage.open({ contest = c, headless = true, moveName = function(m) return "M" .. m end }) end)
local sess = { gameStats = {}, party = {} }
local r = Results.open({ contest = c, session = sess, headless = true, noFieldHooks = true })
eq(r.done, true, "results screen reaches the end")
eq(sess.gameStats[36], 1, "GAME_STAT_ENTERED_CONTEST incremented")
eq(sess.gameStats[37] or 0, c.standings[c.playerIndex] == 0 and 1 or 0, "GAME_STAT_WON_CONTEST follows the standings")
local rd = Util.resultsData(c)
for i = 0, 3 do
local want = rd[i].barLengthPreliminary + (rd[i].lostPoints and -rd[i].barLengthRound2 or rd[i].barLengthRound2)
eq(r.d.barLength[i], want, "result bar " .. i .. " ends at its last target (contest_util.c:1856)")
end
eq(r.d.numStandingsPrinted, 4, "four final standing numbers drawn")
local winner = Util.winnerId(c)
local L = r.bg[2]
eq(math.floor(L:get(5, winner * 3 + 5) / 4096), 9, "the winner's box row is switched to palette 9")
end
print("[test] painting build (pokeemerald/src/contest_painting.c:474)")
do
local Painting = require("src.ui.game3.rse.contest_painting")
Painting.resetCache()
local sp = C:require("species", "SPECIES_SALAMENCE")
local winner = { species = sp, personality = 7, trainerId = 1, contestCategory = 3 * 1 + 2, contestRank = 3,
monName = "SALAMENCE", trainerName = "NICK" }
local st = Painting.build({ winner = winner, saveIdx = 9, isForArtist = false })
eq(st.mon.effect, Fx.EFFECT.SHIMMER, "beauty museum painting uses the shimmer effect")
eq(st.frameKey, "beauty", "beauty museum frame")
local maxIdx = 0
for i = 0, 64 * 64 - 1 do if st.mon.indices[i] > maxIdx then maxIdx = st.mon.indices[i] end end
check(maxIdx < 0xE0, "limited quantization stays below 0xE0 (" .. maxIdx .. ")")
if IDROOT then check(maxIdx > 4, "the Salamence front pic quantizes to several colors") end
local art = Painting.build({ winner = winner, saveIdx = 9, isForArtist = true })
eq(art.map[0], 0x1015, "artist painting background tile (contest_painting.c:418)")
eq(art.caption, nil, "artist painting has no caption")
local hall = Painting.build({ winner = { species = sp, personality = 7, trainerId = 1, contestCategory = 4, contestRank = 0,
monName = "SALAMENCE", trainerName = "NICK" }, saveIdx = 0 })
eq(hall.frameKey, "lobby", "hall painting uses the lobby frame")
eq(hall.mon.effect, Fx.EFFECT.GRAYSCALE_LIGHT, "tough hall painting is light grayscale")
if IDROOT then
local spinda = C:require("species", "SPECIES_SPINDA")
local a = Painting.monPixels({ species = spinda, personality = 0x12345678, trainerId = 0x00010001 })
local b = Painting.monPixels({ species = spinda, personality = 0xFEDCBA98, trainerId = 0x00010001 })
local changed = 0
for i = 0, 64 * 64 - 1 do if a[i] ~= b[i] then changed = changed + 1 end end
check(changed > 0, "contest painting applies each Spinda personality's spot pattern (" .. changed .. " pixels)")
end
if textTable then
check(type(hall.caption) == "string" and hall.caption:find("NICK", 1, true) ~= nil, "hall caption names the trainer")
end
end
print("[test] contest specials (pokeemerald/src/contest_util.c:1958)")
do
local Pokeblock = require("src.core.game3.rse.pokeblock")
local sess = { version = "emerald", name = "NICK", gender = "male", party = {}, gameStats = {} }
local mon = { species = C:require("species", "SPECIES_SALAMENCE"), level = 50, hp = 100, maxHp = 100,
moves = { mv("MOVE_HEADBUTT"), mv("MOVE_EMBER"), mv("MOVE_DRAGON_BREATH"), mv("MOVE_FLY") },
personality = 12345, otId = 1, nickname = IDROOT and "" or "SALAMENCE" }
local cond = Pokeblock.contest(mon)
cond.cool, cond.tough, cond.beauty, cond.sheen = 200, 100, 100, 50
sess.party[1] = mon
package.loaded["src.core.game3.runtime"] = { getSession = function() return sess end }
local Contest2 = require("src.core.game3.rse.contest")
local realData = Contest2.data
Contest2.data = function() return D end
local NC = require("src.core.game3.scripting.natives_contest")
local Rse = require("src.core.game3.rse.init")
local ctx = { specialVars = {}, stringVars = { "", "", "" } }
local function set(id, v) ctx.specialVars[id] = v end
local function get(id) return ctx.specialVars[id] or 0 end
NC.partyIndex = 0
set(0x8011, 0)
set(0x8010, 0)
NC.BY_NAME.TryEnterContestMon(ctx)
eq(get(0x800D), 1, "TryEnterContestMon: ribbonless mon, Normal rank -> CAN_ENTER_CONTEST_EQUAL_RANK")
local c = Util.current()
check(c ~= nil and c.mons[3].species == mon.species, "the party mon becomes contestant 4")
check(c.mons[3].nickname ~= "", "an unnamed mon enters under its species name (contest.c:2798)")
set(0x8010, 1)
NC.BY_NAME.TryEnterContestMon(ctx)
eq(get(0x800D), 0, "Super rank without a Normal ribbon -> CANT_ENTER_CONTEST")
set(0x8010, 0)
NC.BY_NAME.TryEnterContestMon(ctx)
c = Util.current()
set(0x8006, 3)
NC.BY_NAME.GetContestMonCondition(ctx)
eq(get(0x8004), c.round1[3], "GetContestMonCondition reads round 1 points")
NC.BY_NAME.BufferContestTrainerAndMonNames(ctx)
eq(ctx.stringVars[1], "NICK", "BufferContestTrainerAndMonNames: STR_VAR_1 trainer")
eq(get(0x8004), mon.species, "BufferContestTrainerAndMonNames: VAR_0x8004 species")
c:init()
c:run({ 0, 1, 2, 3, 0 })
NC.BY_NAME.GetContestWinnerId(ctx)
eq(get(0x8005), Util.winnerId(c), "GetContestWinnerId")
NC.BY_NAME.BufferContestWinnerMonName(ctx)
eq(ctx.stringVars[1], Util.monName(c.mons[Util.winnerId(c)]), "BufferContestWinnerMonName")
NC.BY_NAME.ShouldReadyContestArtist(ctx)
eq(get(0x8004), 0, "ShouldReadyContestArtist is false below Master rank")
local _, n = NC.BY_NAME.CountPlayerMuseumPaintings(ctx)
eq(n, 0, "no museum paintings yet (winners initialized from gDefaultContestWinners)")
check(type(sess.contestWinners) == "table" and (sess.contestWinners[1].species or 0) ~= 0, "hall defaults filled on first use")
set(0x800D, 8)
NC.BY_NAME.GenerateContestRand(ctx)
check(get(0x800D) < 8, "GenerateContestRand reduces VAR_RESULT modulo itself")
NC.BY_NAME.TryContestGModeLinkup(ctx)
eq(get(0x800D), 5, "link contest linkup reports LINKUP_FAILED")
local _, won = NC.BY_NAME.HasMonWonThisContestBefore(ctx)
eq(won, 0, "HasMonWonThisContestBefore is false without a ribbon")
Contest2.data = realData
Rse.reset()
end
print("[test] paintings match pygba captures pixel for pixel (pokeemerald/src/contest_painting.c:474)")
if not IDROOT then
print(" skipped: POKEPORT_IDENTITY cache with mon pics not found")
else
local Painting = require("src.ui.game3.rse.contest_painting")
Painting.resetCache()
local CASES = {
{ name = "museum_cool", species = 397, personality = 0x12345678, trainerId = 0x00010001, category = 2, rank = 3, saveIdx = 8, artist = false, fnv = 0x183FBBA8 },
{ name = "museum_beauty", species = 329, personality = 0x0BADBEEF, trainerId = 0x00020002, category = 3, rank = 3, saveIdx = 9, artist = false, fnv = 0x7CCDE6FC },
{ name = "museum_cute", species = 315, personality = 0x00000007, trainerId = 0x00030003, category = 7, rank = 3, saveIdx = 10, artist = false, fnv = 0xDA489FEC },
{ name = "museum_smart", species = 65, personality = 0x0000FF10, trainerId = 0x00040004, category = 11, rank = 3, saveIdx = 11, artist = false, fnv = 0x39AC0EEC },
{ name = "museum_tough", species = 68, personality = 0x00ABCDEF, trainerId = 0x00050005, category = 12, rank = 3, saveIdx = 12, artist = false, fnv = 0xDA6AB98D },
{ name = "hall_cute", species = 25, personality = 0x00000042, trainerId = 0x00060006, category = 2, rank = 1, saveIdx = 0, artist = false, fnv = 0x753BA90D },
{ name = "artist_cool", species = 394, personality = 0x7777AAAA, trainerId = 0x00070007, category = 1, rank = 3, saveIdx = 8, artist = true, fnv = 0x653DAC72 },
}
local bit = require("bit")
local function render(st)
local W = 240
local out = {}
for i = 0, W * 112 * 3 - 1 do out[i] = 0 end
local gfx, pal = st.gfx, st.framePalette
local tiles = math.floor(#gfx / 32)
for ty = 0, 13 do for tx = 0, 29 do
local e = st.map[ty * 32 + tx] or 0
local tile, hf, vf, bank = e % 1024, math.floor(e / 1024) % 2 == 1, math.floor(e / 2048) % 2 == 1, math.floor(e / 4096) % 16
for py = 0, 7 do for px = 0, 7 do
local sx, sy = hf and 7 - px or px, vf and 7 - py or py
local v = 0
if tile < tiles then
local b = gfx:byte(tile * 32 + sy * 4 + math.floor(sx / 2) + 1) or 0
v = sx % 2 == 0 and b % 16 or math.floor(b / 16)
end
if v ~= 0 then
local c = pal[bank * 16 + v + 1] or 0
local o = ((ty * 8 + py) * W + tx * 8 + px) * 3
out[o], out[o + 1], out[o + 2] = c % 32, math.floor(c / 32) % 32, math.floor(c / 1024) % 32
end
end end
end end
for i = 0, 64 * 64 - 1 do
local v = st.mon.indices[i]
if v ~= 0 then
local c = st.mon.palette[v] or 0
local o = ((24 + math.floor(i / 64)) * W + 88 + i % 64) * 3
out[o], out[o + 1], out[o + 2] = c % 32, math.floor(c / 32) % 32, math.floor(c / 1024) % 32
end
end
local h = 0x811C9DC5
for i = 0, W * 112 * 3 - 1 do
h = bit.bxor(h, out[i]) % 4294967296
h = require("src.core.game3.rng").mulU32(h, 0x01000193)
end
return h
end
for _, cs in ipairs(CASES) do
local st = Painting.build({ winner = { species = cs.species, personality = cs.personality, trainerId = cs.trainerId,
contestCategory = cs.category, contestRank = cs.rank, monName = "TESTMON", trainerName = "NICK" },
saveIdx = cs.saveIdx, isForArtist = cs.artist })
eq(string.format("%08X", render(st)), string.format("%08X", cs.fnv), cs.name .. " frame + processed mon match the ROM")
end
local st = Painting.build({ winner = { species = 397, personality = 0x12345678, trainerId = 0x00010001, contestCategory = 2,
contestRank = 3, monName = "TESTMON", trainerName = "NICK" }, saveIdx = 8 })
eq(st.caption, "The marvelous, wonderful, and\nvery great TESTMON", "museum caption 3 of the cool set")
end
T.finish()