mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 15:01:11 +02:00
ca4d3d283c
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.
108 lines
4.8 KiB
Lua
108 lines
4.8 KiB
Lua
-- Parity test: text that has to wait for the player.
|
|
-- Self-contained: run via `luajit tests/parity_text_markers.lua`; also
|
|
-- dofile'd by tests/run_tests.lua's aggregator.
|
|
--
|
|
-- The extractor emits three different break markers and they are not
|
|
-- interchangeable (src/render/TextBox.lua): \n opens the box's second line,
|
|
-- \v scrolls one line in after the down-arrow and a button press
|
|
-- (pokered ContText), and \f breaks the page, clearing and waiting.
|
|
--
|
|
-- Hand-written strings that spell `cont` or `para` as a plain \n put more
|
|
-- lines on a page than the two-line box can hold and give the player
|
|
-- nothing to press, so the text pours past by itself. Two of those shipped:
|
|
--
|
|
-- #239 the trainer-battle run refusal, which lost "trainer battle!"
|
|
-- #250 the Viridian caterpillar description, which scrolled six lines by
|
|
--
|
|
-- Pagination is what the box actually consumes, so asserting on it (rather
|
|
-- than on the raw string) is what ties these to the observable symptom.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
local Data = require("src.core.Data")
|
|
if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end
|
|
|
|
local S = require("tests.harness").suite("parity text markers")
|
|
local check, eq = S.check, S.eq
|
|
|
|
require("src.render.Font").load(Data)
|
|
local TextBox = require("src.render.TextBox")
|
|
|
|
-- a page the box can actually show, and every line past the second on it
|
|
-- reached there by a \v the player has to release
|
|
local function pageIsPlayable(page, conts)
|
|
if #page <= 2 then return true end
|
|
for i = 3, #page do
|
|
if not (conts and conts[i]) then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function assertPlayable(label, text)
|
|
local pages = TextBox.paginate(text)
|
|
for i, page in ipairs(pages) do
|
|
check(pageIsPlayable(page, pages.contBefore and pages.contBefore[i]),
|
|
("%s: page %d never runs past the box unattended"):format(label, i))
|
|
end
|
|
return pages
|
|
end
|
|
|
|
-- === #239: "No! There's no running from a trainer battle!" ===
|
|
-- The generated string is the authority; the port's literal is only a
|
|
-- fallback for a cache that predates it.
|
|
do
|
|
local generated = Data.text._NoRunningText
|
|
check(type(generated) == "string", "_NoRunningText is in the generated text")
|
|
if generated then
|
|
local pages = assertPlayable("_NoRunningText", generated)
|
|
eq(#pages, 1, "the refusal is one page")
|
|
eq(#pages[1], 3, "the refusal is three lines in a two-line box")
|
|
check(pages.contBefore[1][3] == true,
|
|
"'trainer battle!' scrolls in on a button press, not on its own (#239)")
|
|
check(generated:find("trainer battle!", 1, true) ~= nil,
|
|
"the third line is the part the bug swallowed")
|
|
end
|
|
|
|
-- the in-code fallback has to carry the same markers, or a pre-#239 cache
|
|
-- reintroduces the bug
|
|
local fallback = "No! There's no\nrunning from a\vtrainer battle!"
|
|
local pages = assertPlayable("run-refusal fallback", fallback)
|
|
eq(#pages[1], 3, "the fallback is three lines too")
|
|
check(pages.contBefore[1][3] == true, "the fallback waits on line 3")
|
|
end
|
|
|
|
-- === #250: the CATERPIE / WEEDLE description ===
|
|
-- pokered/text/ViridianCity.asm declares this one without the leading
|
|
-- underscore, and the extractor now keys on both spellings, so the port's
|
|
-- literal has to keep agreeing with what the cart actually says.
|
|
do
|
|
local desc = "CATERPIE has no\npoison, but\vWEEDLE does.\fWatch out for its\nPOISON STING!"
|
|
local extracted = Data.text.ViridianCityYoungster2CaterpieAndWeedleDescriptionText
|
|
or Data.text._ViridianCityYoungster2CaterpieAndWeedleDescriptionText
|
|
if extracted then eq(extracted, desc, "the literal still matches the cart") end
|
|
|
|
local pages = assertPlayable("caterpillar description", desc)
|
|
eq(#pages, 2, "para breaks the description into two pages (#250)")
|
|
eq(#pages[1], 3, "page 1 is text + line + cont")
|
|
eq(#pages[2], 2, "page 2 is para + line")
|
|
check(pages.contBefore[1][3] == true, "'WEEDLE does.' waits for a button")
|
|
|
|
-- and the shape the bug had: one page, six lines, nothing to press
|
|
local broken = "CATERPIE has no\npoison, but\nWEEDLE does.\n\nWatch out for its\nPOISON STING!"
|
|
local brokenPages = TextBox.paginate(broken)
|
|
eq(#brokenPages, 1, "the old spelling collapsed to a single page")
|
|
check(#brokenPages[1] > 2, "the old spelling overfilled that page")
|
|
check(not pageIsPlayable(brokenPages[1], brokenPages.contBefore[1]),
|
|
"the old spelling is exactly what this suite is here to catch")
|
|
end
|
|
|
|
-- the ask that precedes it uses the generated string, which already carries
|
|
-- \v, so it is a live example of the marker done right
|
|
do
|
|
local ask = Data.text._ViridianCityYoungster2YouWantToKnowAboutText
|
|
check(type(ask) == "string", "the caterpillar question is in generated text")
|
|
if ask then assertPlayable("caterpillar question", ask) end
|
|
end
|
|
|
|
S.finish()
|