mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-28 18:06:09 +02:00
Add Pokemon Crystal as a sixth supported version
Crystal boots from a user-supplied ROM, imports a full cache and is playable: copyright, the Crystal intro movie, the animated title, gender select, Oak, and out into Johto. 122 of the cart's 169 script specials are implemented. Import and data - tools/make_crystal_manifest.py derives the manifest by importing make_gold_manifest as a library, with three additive keyword seams. Gold and Silver still regenerate byte-identical, which is the standing requirement for touching that generator. - crystal_symbol_deltas.py and crystal_movie_symbols.py carry the symbol delta: Crystal renames the credits mons, splits the trainer card, Pokegear and pack-pal blocks by gender, and replaces the intro and title outright. - Crystal-only manifest keys: engineFlagOrder (162 flags to Gold's 93, so the badge block sits one higher) and unownCharmap (the main charmap parser stops at the first newcharmap so the two cannot contaminate each other). Extractor - RomExtractorGen2 becomes three-edition. Crystal corrections: PAL_MAP_BANK 0x13, a flat PICS_FIX pic bank, audio bank 0x5e, the mapSongs id-100 hole, seven NPC trades, a TradeTexts stride of 8, the five Crystal tileset anim steps with per-row degrade, and the column-major trainer card portraits. - New: animated front sprites (frames, bitmasks, play and idle scripts), the Battle Tower roster, Kris assets, Mobile System GB art, and the Crystal intro and title via src/import/CrystalMovie.lua. Engine - GameVersion gains engine(id) and fixes(id). Gold and Silver keep their original bugs where the bug is not hardware dependent; Crystal gets the fixes Crystal shipped: Lucky Number boxes 10-14, surfing onto an NPC, and the Reflect and Light Screen defence overflow. - Crystal story: Suicune and Eusine, Celebi behind the GS Ball flag, the Ruins of Alph chambers, Buena, the Move Tutor, the Poke Seer, and the Battle Tower including the wInBattleTowerBattle badge-boost guard. - Kris and the gender flag, animated fronts in battle and the summary screen, and mon caught data. Verification - Every extracted asset is pixel-compared against pret's own source PNGs. - Gold caches are byte-identical before and after, file for file. - New Crystal suites plus a T2 Gen 2 tier; the full suite passes.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
-- Headless Gold save-editor rules. Run from repo root:
|
||||
-- Headless Gen 2 save-editor rules. Run from repo root:
|
||||
-- luajit tests/save_editor_gen2_tests.lua
|
||||
package.path = package.path .. ";./?.lua;./?/init.lua;./tools/save-editor/?.lua"
|
||||
.. ";./tools/save-editor/panels/?.lua"
|
||||
@@ -90,6 +90,7 @@ do
|
||||
eq(Gen.of({ generation = 2 }), 2, "Gen.of generation field")
|
||||
eq(Gen.of({ version = "gold" }), 2, "Gen.of version gold")
|
||||
eq(Gen.of({ version = "silver" }), 2, "Gen.of version silver")
|
||||
eq(Gen.of({ version = "crystal" }), 2, "Gen.of version crystal")
|
||||
eq(Gen.of(SaveData.newGame()), 1, "Gen.of gen1 newGame")
|
||||
eq(Gen.of(Save2.newGame()), 2, "Gen.of gold newGame")
|
||||
end
|
||||
@@ -116,7 +117,7 @@ do
|
||||
check(not Ops.speciesUsable(S1, "BROKEN"), "partial record is not usable")
|
||||
end
|
||||
|
||||
for _, version in ipairs({ "gold", "silver" }) do
|
||||
for _, version in ipairs({ "gold", "silver", "crystal" }) do
|
||||
local S = newState(version)
|
||||
Ops.partyAdd(S)
|
||||
eq(#S.save.party, 1, "partyAdd on " .. version)
|
||||
@@ -424,5 +425,398 @@ do
|
||||
GameVersion.set("red")
|
||||
end
|
||||
|
||||
do
|
||||
GameVersion.set("red")
|
||||
local crystal = Gen.newGame("crystal")
|
||||
eq(crystal.version, "crystal", "Gen.newGame('crystal') stamps crystal")
|
||||
eq(crystal.generation, 2, "crystal stub is generation 2")
|
||||
eq(crystal.player.name, "CHRIS", "crystal stub uses Crystal's preset name")
|
||||
eq(Gen.newGame("silver").version, "silver", "Gen.newGame('silver') stamps silver")
|
||||
eq(Gen.newGame("silver").player.name, "SILVER", "silver stub keeps SILVER")
|
||||
eq(Gen.newGame("gold").player.name, "GOLD", "gold stub keeps GOLD")
|
||||
eq(Gen.newGame("blue").version, "blue", "Gen.newGame('blue') stamps blue")
|
||||
eq(Gen.newGame("yellow").version, "yellow", "Gen.newGame('yellow') stamps yellow")
|
||||
eq(Gen.newGame(nil).version, "red", "no version falls back to the active game")
|
||||
eq(Gen.newGame("nonsense").version, "red", "an unknown id is ignored")
|
||||
end
|
||||
|
||||
do
|
||||
local crystal = Gen.newGame("crystal")
|
||||
local gold = Gen.newGame("gold")
|
||||
eq(Gen.versionOf(crystal), "crystal", "versionOf reads the save")
|
||||
eq(Gen.engineOf(crystal), "crystal", "crystal saves are the crystal lineage")
|
||||
eq(Gen.engineOf(gold), "gs", "gold saves are the gs lineage")
|
||||
eq(Gen.engineOf(Gen.newGame("silver")), "gs", "silver saves are the gs lineage")
|
||||
eq(Gen.editionLabel(crystal), "CRYSTAL", "edition label follows the save")
|
||||
eq(Gen.editionLabel(gold), "GOLD", "gold still labels GOLD")
|
||||
check(Gen.hasCaughtData(crystal), "crystal has caught data")
|
||||
check(not Gen.hasCaughtData(gold), "gold has no caught data")
|
||||
check(not Gen.hasCaughtData(SaveData.newGame()), "gen 1 has no caught data")
|
||||
check(Gen.hasPlayerGender(crystal), "crystal has a player gender")
|
||||
check(not Gen.hasPlayerGender(gold), "gold has no player gender")
|
||||
end
|
||||
|
||||
do
|
||||
local Gen2Flags = require("Gen2Flags")
|
||||
local gs = Gen2Flags.byName("gs")
|
||||
local crys = Gen2Flags.byName("crystal")
|
||||
eq(gs.EVENT_BEAT_FALKNER, crys.EVENT_BEAT_FALKNER,
|
||||
"a shared event keeps one id in both lineages")
|
||||
check(gs.EVENT_BEAT_FALKNER ~= nil, "the gs table is populated")
|
||||
eq(gs.EVENT_GOT_RAINBOW_WING, 120, "Gold's EVENT_GOT_RAINBOW_WING")
|
||||
eq(crys.EVENT_GOT_RAINBOW_WING, 822, "Crystal renumbers EVENT_GOT_RAINBOW_WING")
|
||||
eq(gs.EVENT_TIN_TOWER_1F_SUICUNE, nil, "a Crystal-only event is absent on gs")
|
||||
eq(crys.EVENT_TIN_TOWER_1F_SUICUNE, 1970, "and resolves on crystal")
|
||||
eq(crys.EVENT_WADE_READY_FOR_REMATCH, nil, "a retired Gold event is absent on crystal")
|
||||
check(gs.EVENT_WADE_READY_FOR_REMATCH ~= nil, "and still resolves on gs")
|
||||
check(Gen2Flags.byName("crystal") == crys, "byName caches per lineage")
|
||||
check(Gen2Flags.byName("gold") == gs, "an unknown lineage reads as gs")
|
||||
end
|
||||
|
||||
do
|
||||
local crystalNames = Catalog.gen2EventList("crystal")
|
||||
local goldNames = Catalog.goldEventList()
|
||||
local function has(list, name)
|
||||
for _, n in ipairs(list) do if n == name then return true end end
|
||||
return false
|
||||
end
|
||||
check(has(crystalNames, "EVENT_TIN_TOWER_1F_SUICUNE"),
|
||||
"the crystal event list names Crystal's own flags")
|
||||
check(not has(crystalNames, "EVENT_WADE_READY_FOR_REMATCH"),
|
||||
"the crystal event list drops Gold's retired flags")
|
||||
check(has(goldNames, "EVENT_WADE_READY_FOR_REMATCH"),
|
||||
"the gold event list keeps them")
|
||||
check(has(goldNames, "EVENT_BEAT_FALKNER"), "gold event list still has Falkner")
|
||||
check(#crystalNames > #goldNames, "Crystal has more event flags than Gold")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState("crystal")
|
||||
local name = "EVENT_TIN_TOWER_1F_SUICUNE"
|
||||
Ops.setFlag(S, name, true)
|
||||
check(Gen.getFlag(S.save, name), "a Crystal-only event reads back set")
|
||||
eq(S.save.flags[name], nil, "and never lands as a string key")
|
||||
check(next(S.save.events) ~= nil, "it wrote the numeric bitfield")
|
||||
|
||||
local G = newState("gold")
|
||||
Ops.setFlag(G, name, true)
|
||||
eq(G.save.flags[name], true, "the same name on Gold has no id and stays named")
|
||||
|
||||
local moved = newState("crystal")
|
||||
Ops.setFlag(moved, "EVENT_GOT_RAINBOW_WING", true)
|
||||
local Events2 = require("src.world.gen2.Events")
|
||||
local ev = Events2.new()
|
||||
ev:restore(moved.save.events)
|
||||
check(ev:get(822), "EVENT_GOT_RAINBOW_WING sets Crystal's bit 822")
|
||||
check(not ev:get(120), "not Gold's bit 120")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState("crystal")
|
||||
Ops.partyAdd(S)
|
||||
local mon = S.save.party[1]
|
||||
|
||||
check(Ops.setCaughtTime(S, mon, 2), "caught time set")
|
||||
eq(mon.caughtTime, 2, "caught time DAY")
|
||||
check(S.dirty, "caught time dirties the save")
|
||||
check(not Ops.setCaughtTime(S, mon, 2), "re-setting the same time only says")
|
||||
Ops.setCaughtTime(S, mon, 9)
|
||||
eq(mon.caughtTime, 3, "caught time clamps to NITE")
|
||||
Ops.setCaughtTime(S, mon, -1)
|
||||
eq(mon.caughtTime, 0, "caught time clamps to unknown")
|
||||
|
||||
Ops.setCaughtLevel(S, mon, 40)
|
||||
eq(mon.caughtLevel, 40, "caught level 40")
|
||||
Ops.setCaughtLevel(S, mon, 99)
|
||||
eq(mon.caughtLevel, 63, "caught level clamps to CAUGHT_LEVEL_MASK")
|
||||
|
||||
Ops.setCaughtLocation(S, mon, 12)
|
||||
eq(mon.caughtLocation, 12, "caught location 12")
|
||||
Ops.setCaughtLocation(S, mon, -1)
|
||||
eq(mon.caughtLocation, 127, "stepping below 0 wraps to LANDMARK_EVENT")
|
||||
Ops.setCaughtLocation(S, mon, 128)
|
||||
eq(mon.caughtLocation, 0, "and stepping past the mask wraps back to unknown")
|
||||
|
||||
Ops.setCaughtByGender(S, mon, "girl")
|
||||
eq(mon.caughtByGender, "girl", "caught by girl")
|
||||
Ops.setCaughtByGender(S, mon, "none")
|
||||
eq(mon.caughtByGender, nil, "caught by clears to nil, the Gold-save value")
|
||||
|
||||
local Mon = require("src.battle.gen2.Mon")
|
||||
Ops.setCaughtTime(S, mon, 3)
|
||||
Ops.setCaughtLevel(S, mon, 20)
|
||||
Ops.setCaughtLocation(S, mon, 5)
|
||||
Ops.setCaughtByGender(S, mon, "girl")
|
||||
local byte0, byte1 = Mon.packCaughtData(mon)
|
||||
eq(byte0, 3 * 0x40 + 20, "the editor's fields pack into byte 0")
|
||||
eq(byte1, 0x80 + 5, "and into byte 1")
|
||||
end
|
||||
|
||||
do
|
||||
local G = newState("gold")
|
||||
Ops.partyAdd(G)
|
||||
local mon = G.save.party[1]
|
||||
G.dirty = false
|
||||
check(not Ops.setCaughtTime(G, mon, 2), "Gold refuses caught time")
|
||||
eq(mon.caughtTime, nil, "and writes nothing")
|
||||
check(not Ops.setCaughtLevel(G, mon, 20), "Gold refuses caught level")
|
||||
check(not Ops.setCaughtLocation(G, mon, 5), "Gold refuses caught location")
|
||||
check(not Ops.setCaughtByGender(G, mon, "boy"), "Gold refuses caught gender")
|
||||
check(not G.dirty, "a refused caught edit never dirties")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState("crystal")
|
||||
eq(Gen.playerGender(S.save), "male", "a new Crystal save starts male")
|
||||
check(Ops.setPlayerGender(S, "female"), "player gender set")
|
||||
eq(S.save.player.gender, "female", "written to save.player.gender")
|
||||
check(not Ops.setPlayerGender(S, "female"), "re-setting the same gender only says")
|
||||
Ops.setPlayerGender(S, "male")
|
||||
eq(Gen.playerGender(S.save), "male", "and back")
|
||||
|
||||
local G = newState("gold")
|
||||
check(not Ops.setPlayerGender(G, "female"), "Gold refuses a gender change")
|
||||
eq(G.save.player.gender, "male", "Gold's field is left alone")
|
||||
end
|
||||
|
||||
do
|
||||
local landmarks = {
|
||||
gen2Landmarks = {
|
||||
landmarks = {
|
||||
LANDMARK_AZALEA_TOWN = { id = "LANDMARK_AZALEA_TOWN", index = 12,
|
||||
name = "AZALEA TOWN" },
|
||||
LANDMARK_BURNED_TOWER = { id = "LANDMARK_BURNED_TOWER", index = 24,
|
||||
name = "BURNED\nTOWER" },
|
||||
},
|
||||
},
|
||||
}
|
||||
eq(Gen.landmarkName(landmarks, 12), "AZALEA TOWN", "landmark by index")
|
||||
eq(Gen.landmarkName(landmarks, 24), "BURNED TOWER", "two-line names flatten")
|
||||
eq(Gen.landmarkName(landmarks, 0), "UNKNOWN", "0 reads as unknown")
|
||||
eq(Gen.landmarkName(landmarks, 0x7e), "GIFT", "LANDMARK_GIFT")
|
||||
eq(Gen.landmarkName(landmarks, 0x7f), "EVENT", "LANDMARK_EVENT")
|
||||
eq(Gen.landmarkName(landmarks, 99), "#99", "an unmapped index shows its number")
|
||||
end
|
||||
|
||||
do
|
||||
local Kit = require("Kit")
|
||||
local MonEditor = require("MonEditor")
|
||||
local ItemsPanel = require("Items")
|
||||
|
||||
local function clipped(r)
|
||||
local x1, y1, x2, y2 = r.x, r.y, r.x + r.w, r.y + r.h
|
||||
if r.clip then
|
||||
x1 = math.max(x1, r.clip.x); y1 = math.max(y1, r.clip.y)
|
||||
x2 = math.min(x2, r.clip.x + r.clip.w); y2 = math.min(y2, r.clip.y + r.clip.h)
|
||||
end
|
||||
if x2 - x1 <= 1 or y2 - y1 <= 1 then return nil end
|
||||
return x1, y1, x2, y2
|
||||
end
|
||||
|
||||
local function overlap(a, b)
|
||||
local ax1, ay1, ax2, ay2 = clipped(a)
|
||||
if not ax1 then return false end
|
||||
local bx1, by1, bx2, by2 = clipped(b)
|
||||
if not bx1 then return false end
|
||||
return math.min(ax2, bx2) - math.max(ax1, bx1) > 1
|
||||
and math.min(ay2, by2) - math.max(ay1, by1) > 1
|
||||
end
|
||||
|
||||
local function auditFrame(label, W, H)
|
||||
local controls = {}
|
||||
for _, r in ipairs(Kit.audit or {}) do
|
||||
if r.class == "control" then controls[#controls + 1] = r end
|
||||
end
|
||||
check(#controls > 0, label .. ": the frame dispatched controls at all")
|
||||
local collisions, escapes = 0, 0
|
||||
for i = 1, #controls do
|
||||
local a = controls[i]
|
||||
local x1, y1, x2, y2 = clipped(a)
|
||||
if x1 and (x1 < -0.5 or y1 < -0.5 or x2 > W + 0.5 or y2 > H + 0.5) then
|
||||
escapes = escapes + 1
|
||||
print((" escape: %s (%.0f,%.0f %.0fx%.0f)")
|
||||
:format(a.label, a.x, a.y, a.w, a.h))
|
||||
end
|
||||
for j = i + 1, #controls do
|
||||
if overlap(a, controls[j]) then
|
||||
collisions = collisions + 1
|
||||
print((" overlap: '%s' vs '%s' at (%.0f,%.0f) / (%.0f,%.0f)")
|
||||
:format(a.label, controls[j].label, a.x, a.y,
|
||||
controls[j].x, controls[j].y))
|
||||
end
|
||||
end
|
||||
end
|
||||
check(collisions == 0, label .. ": no two controls overlap")
|
||||
check(escapes == 0, label .. ": every control stays inside the panel")
|
||||
end
|
||||
|
||||
local sizes = { { 420, 700 }, { 640, 768 }, { 1280, 720 } }
|
||||
for _, version in ipairs({ "gold", "crystal" }) do
|
||||
for _, size in ipairs(sizes) do
|
||||
local W, H = size[1], size[2]
|
||||
local S = newState(version)
|
||||
Ops.partyAdd(S)
|
||||
Ops.selectParty(S, 1)
|
||||
Ops.addToBag(S, S.cat.items[1])
|
||||
Ops.addToPc(S, S.cat.items[2])
|
||||
Kit.layout(W, H)
|
||||
for _, panel in ipairs({ { "inspector", MonEditor }, { "items", ItemsPanel } }) do
|
||||
local label = ("%s %dx%d %s"):format(version, W, H, panel[1])
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
local ok, err = pcall(panel[2].draw, S, Kit, 0, 0, W, H)
|
||||
check(ok, label .. " draws: " .. tostring(err))
|
||||
if ok then
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
ok, err = pcall(panel[2].draw, S, Kit, 0, 0, W, H)
|
||||
check(ok, label .. " redraws: " .. tostring(err))
|
||||
end
|
||||
if ok then auditFrame(label, W, H) end
|
||||
Kit.audit = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local S = newState("crystal")
|
||||
Ops.partyAdd(S)
|
||||
Ops.selectParty(S, 1)
|
||||
Kit.layout(1280, 720)
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
MonEditor.draw(S, Kit, 0, 0, 1280, 720)
|
||||
local labels = {}
|
||||
for _, r in ipairs(Kit.audit) do labels[r.label] = true end
|
||||
Kit.audit = nil
|
||||
check(labels.MORN and labels.NITE, "the Crystal inspector offers the caught times")
|
||||
check(labels.BOY and labels.GIRL, "and the OT gender chips")
|
||||
|
||||
local G = newState("gold")
|
||||
Ops.partyAdd(G)
|
||||
Ops.selectParty(G, 1)
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
MonEditor.draw(G, Kit, 0, 0, 1280, 720)
|
||||
local goldLabels = {}
|
||||
for _, r in ipairs(Kit.audit) do goldLabels[r.label] = true end
|
||||
Kit.audit = nil
|
||||
check(not goldLabels.MORN, "the Gold inspector has no caught row")
|
||||
|
||||
local function itemsLabels(state)
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
ItemsPanel.draw(state, Kit, 0, 0, 1280, 720)
|
||||
local seen = {}
|
||||
for _, r in ipairs(Kit.audit) do seen[r.label] = true end
|
||||
Kit.audit = nil
|
||||
return seen
|
||||
end
|
||||
check(itemsLabels(S).GIRL, "the Crystal Items tab carries the TRAINER card")
|
||||
check(not itemsLabels(G).GIRL, "the Gold Items tab does not")
|
||||
|
||||
local function bottomOverflow(state)
|
||||
local H = 380
|
||||
Kit.layout(640, H)
|
||||
state.inspectorScroll = 100000
|
||||
for _ = 1, 2 do
|
||||
Kit.beginFrame(-100, -100, false, 0)
|
||||
Kit.audit = {}
|
||||
MonEditor.draw(state, Kit, 0, 0, 640, H)
|
||||
end
|
||||
local lowest = 0
|
||||
for _, r in ipairs(Kit.audit) do
|
||||
if r.class == "control" then lowest = math.max(lowest, r.y + r.h) end
|
||||
end
|
||||
Kit.audit = nil
|
||||
return math.floor(lowest - H + 0.5)
|
||||
end
|
||||
local goldOver = bottomOverflow(G)
|
||||
eq(bottomOverflow(S), goldOver,
|
||||
"the caught rows are fully counted in the inspector's scroll height")
|
||||
check(goldOver <= 4, ("the inspector reaches its last control (%d px short)")
|
||||
:format(goldOver))
|
||||
end
|
||||
|
||||
local function readable(path)
|
||||
local fh = io.open(path, "r")
|
||||
if not fh then return false end
|
||||
fh:close()
|
||||
return true
|
||||
end
|
||||
|
||||
do
|
||||
local Gen2Flags = require("Gen2Flags")
|
||||
local goldAsm = (os.getenv("POKEGOLD") or "../pokegold")
|
||||
.. "/constants/event_flags.asm"
|
||||
local crystalAsm = (os.getenv("POKECRYSTAL") or "../pokecrystal")
|
||||
.. "/constants/event_flags.asm"
|
||||
if readable(goldAsm) and readable(crystalAsm) and readable("tools/goldwalk/flags.lua") then
|
||||
local Flags = dofile("tools/goldwalk/flags.lua")
|
||||
local gold = Flags.parse(goldAsm)
|
||||
local crys = Flags.parse(crystalAsm)
|
||||
local addedBad, removedBad, missing = 0, 0, 0
|
||||
for name, id in pairs(crys) do
|
||||
if gold[name] ~= id and Gen2Flags.CRYSTAL_ADDED[name] ~= id then
|
||||
missing = missing + 1
|
||||
end
|
||||
end
|
||||
for name, id in pairs(Gen2Flags.CRYSTAL_ADDED) do
|
||||
if crys[name] ~= id then addedBad = addedBad + 1 end
|
||||
end
|
||||
for name in pairs(Gen2Flags.CRYSTAL_REMOVED) do
|
||||
if crys[name] ~= nil or gold[name] == nil then removedBad = removedBad + 1 end
|
||||
end
|
||||
eq(missing, 0, "every Crystal event Gold disagrees on is in CRYSTAL_ADDED")
|
||||
eq(addedBad, 0, "every CRYSTAL_ADDED id matches pokecrystal")
|
||||
eq(removedBad, 0, "every CRYSTAL_REMOVED name is Gold-only")
|
||||
local byName = Gen2Flags.byName("crystal")
|
||||
local wrong = 0
|
||||
for name, id in pairs(crys) do
|
||||
if byName[name] ~= id then wrong = wrong + 1 end
|
||||
end
|
||||
eq(wrong, 0, "the resolved crystal table is pokecrystal's whole event list")
|
||||
else
|
||||
check(true, "pokegold / pokecrystal absent : flag delta cross-check SKIPPED")
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local Gen2Flags = require("Gen2Flags")
|
||||
local cache = os.getenv("CRYSTAL_CACHE")
|
||||
or ((os.getenv("HOME") or "") ..
|
||||
"/Library/Application Support/LOVE/lead-crystal/crystal")
|
||||
local initialPath = cache .. "/data/generated/initial_events.lua"
|
||||
local stdScripts = (os.getenv("POKECRYSTAL") or "../pokecrystal")
|
||||
.. "/engine/events/std_scripts.asm"
|
||||
if readable(initialPath) and readable(stdScripts) then
|
||||
local Flags = dofile("tools/goldwalk/flags.lua")
|
||||
local romIds = assert(loadfile(initialPath))().flags
|
||||
-- InitializeEventsScript sets EVENT_AZALEA_TOWN_KURT twice in a row
|
||||
-- (../pokecrystal/engine/events/std_scripts.asm:575-576).
|
||||
local names = {}
|
||||
for _, n in ipairs(Flags.setEventsOf(stdScripts, "InitializeEventsScript")) do
|
||||
if names[#names] ~= n then names[#names + 1] = n end
|
||||
end
|
||||
local byName = Gen2Flags.byName("crystal")
|
||||
eq(#names, #romIds, "InitializeEventsScript sets as many flags as the cart")
|
||||
local mismatches, lo, hi = 0, math.huge, -math.huge
|
||||
for i, name in ipairs(names) do
|
||||
local got = romIds[i]
|
||||
if got then lo, hi = math.min(lo, got), math.max(hi, got) end
|
||||
if byName[name] ~= got then
|
||||
mismatches = mismatches + 1
|
||||
if mismatches <= 5 then
|
||||
print((" #%d %s table=%s cart=%s")
|
||||
:format(i, name, tostring(byName[name]), tostring(got)))
|
||||
end
|
||||
end
|
||||
end
|
||||
eq(mismatches, 0,
|
||||
("crystal names == cart ids across %d..%d"):format(lo, hi))
|
||||
else
|
||||
check(true, "crystal cache absent : cart cross-check SKIPPED")
|
||||
end
|
||||
end
|
||||
|
||||
print(string.format("save editor gen2 tests: %d passed, %d failed", passed, failed))
|
||||
if failed > 0 then os.exit(1) end
|
||||
|
||||
Reference in New Issue
Block a user