Files
gen1recomp/tests/game3_import_cache_version_test.lua
thibautbus 3252a3f2c9 Draw Japanese text in FireRed with the cart's own Japanese fonts
game3 prints every string with the US cart's Latin font, and FrlgFont has
no glyph for a kana, so a Japanese translation of FireRed would print
blank. The US cart still carries its Japanese fonts, which its text
printer draws for a string in Japanese mode (pokefirered/src/text.c:141,
:227): pret builds them into the US ROM, with the normal font's width
table.

The font extractor now bakes both, the normal font from its eight-to-a-row
layout (DecompressGlyph_Normal, text.c:1452) with its widths and the small
one sixteen to a row (DecompressGlyph_Small, text.c:1372), as
chrome/fonts/japanese_{normal,small}_{fg,shadow}.rgba and
japanese_widths.lua. LeafGreen's addresses follow the same -0x24 shift as
the rest of text.o. Cache version 120 re-imports existing caches.

FrlgFont numbers a Japanese character by its byte in the Japanese block of
pokefirered/charmap.txt (kana 01-A0, the full-width space, !?。ー), plus
the symbols the font keeps at the Latin block's codes in their full-width
forms (digits, letters, 『』「」, 円, /, :), offset past the Latin
glyphs. A character with no Latin glyph draws from the Japanese sheet, at
the width the cart gives it (the table for the normal font, 8px for the
small one). Latin text is drawn as before.
2026-09-23 18:07:32 +02:00

151 lines
5.3 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function finish()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local Versions = require("src.import.gba.versions")
local CacheContract = require("src.import.CacheContract")
local ROUND_KEYS = {
"data/generated/gba/scripts/multichoice.lua",
"data/generated/gba/pokemon/battle/terrain_grass.rgba",
"data/generated/gba/pokemon/battle/terrain_cave.rgba",
"data/generated/gba/pokemon/battle/terrain_water.rgba",
"data/generated/gba/pokemon/battle/terrain_champion.rgba",
"data/generated/gba/pokemon/battle/terrain_bg_cave.rgba",
"data/generated/gba/pokemon/pokedex/paper_bg.rgba",
"data/generated/gba/pokemon/pokedex/footprints/1.rgba",
"data/generated/gba/pokemon/pokedex/footprints/bulbasaur.rgba",
"data/generated/gba/pokemon/pokedex/footprints/question_mark.rgba",
"data/generated/gba/chrome/fonts/japanese_normal_fg.rgba",
"data/generated/gba/chrome/fonts/japanese_small_fg.rgba",
"data/generated/gba/chrome/fonts/japanese_widths.lua",
"data/generated/gba/chrome/fonts/braille_fg.rgba",
"data/generated/gba/chrome/fonts/braille_shadow.rgba",
"data/generated/gba/chrome/fonts/braille.lua",
"data/generated/gba/seagallop/manifest.lua",
"data/generated/gba/seagallop/water.4bpp",
"data/generated/gba/seagallop/ferry.4bpp",
"data/generated/gba/seagallop/wake.4bpp",
"data/generated/gba/seagallop/wb_tilemap.bin",
"data/generated/gba/seagallop/eb_tilemap.bin",
"data/generated/gba/seagallop/wb.rgba",
"data/generated/gba/seagallop/eb.rgba",
}
print("[test] 1. the cache version carries this round's importer output")
local CURRENT = Versions.CACHE_VERSION
check(type(CURRENT) == "number" and CURRENT >= 106,
"Versions.CACHE_VERSION carries the round (" .. tostring(CURRENT) .. ")")
print("[test] 2. every key this round added is in the firered required list")
local required, isOverride = CacheContract.requiredFilesFor("firered")
check(isOverride == true, "firered has its own required list")
local have = {}
for _, path in ipairs(required) do have[path] = true end
for _, path in ipairs(ROUND_KEYS) do
check(have[path] == true, "the firered contract requires " .. path)
end
print("[test] 3. a cache missing one of them is incomplete")
for _, path in ipairs(ROUND_KEYS) do
local fs = {
prefix = "",
exists = function(candidate) return candidate ~= path end,
}
local complete, missing = CacheContract.allRequiredFilesExist("firered", fs)
check(complete == false and missing == path,
"a cache without " .. path .. " is incomplete (" .. tostring(missing) .. ")")
end
print("[test] 4. the staleness gate rejects an older cache")
local META = "data/generated/gba/meta.json"
local stamp = CURRENT
local stampFs = {
prefix = "",
exists = function() return true end,
read = function(rel)
if rel == CacheContract.MARKER_PATH then return CacheContract.markerFor("firered") end
if rel == META then
return '{"md5":"x","cache_version":' .. tostring(stamp) .. ',"native_version":6}'
end
return nil
end,
}
check(CacheContract.cacheVersionCurrent("firered", stampFs) == true,
"a current meta.json is current")
check(CacheContract.isReady("firered", stampFs) == true,
"a complete current cache is ready")
stamp = CURRENT - 1
check(CacheContract.cacheVersionCurrent("firered", stampFs) == false,
"a meta.json one version back is stale")
check(CacheContract.isReady("firered", stampFs) == false,
"a complete cache one version back is not ready, so it re-imports")
stamp = CURRENT - 2
check(CacheContract.isReady("firered", stampFs) == false,
"a complete cache two versions back is not ready either")
stamp = CURRENT
print("[test] 5. an imported cache walks the whole required list")
local Cache = require("tests.game3_cache")
local root = Cache.root("meta.json")
if not root then
print("[skip] cache walk: " .. tostring(Cache.reason))
finish()
end
print("[info] FireRed cache at " .. root)
local function readFile(rel)
local f = io.open(root .. "/" .. rel, "rb")
if not f then return nil end
local data = f:read("*a")
f:close()
return data
end
local metaRaw = readFile("meta.json") or ""
check(metaRaw:find('"cache_version"%s*:%s*' .. tostring(CURRENT)) ~= nil,
"the imported cache is stamped " .. tostring(CURRENT))
local base = root:gsub("/data/generated/gba$", "")
check(base ~= root, "the cache root sits under a version directory (" .. base .. ")")
local function present(rel)
local f = io.open(base .. "/" .. rel, "rb")
if f then f:close(); return true end
return false
end
local missing, walked = {}, 0
for _, path in ipairs(required) do
walked = walked + 1
if not present(path) then missing[#missing + 1] = path end
end
check(walked >= 100, "the walk covered the whole firered contract (" .. walked .. ")")
check(#missing == 0, "no required key is missing (" .. (missing[1] or "none") .. ")")
local liveFs = { prefix = "", exists = present }
local complete, firstMissing = CacheContract.allRequiredFilesExist("firered", liveFs)
check(complete == true,
"CacheContract calls the imported cache complete (" .. tostring(firstMissing) .. ")")
finish()