Files
gen1recomp/tests/game3_stitchimp_braille_text_test.lua
bryanthaboi dee935a4ea CLOSES #2430
2026-09-23 11:58:32 -04:00

273 lines
9.6 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 Extract = require("src.import.gba.extract_scripts")
local TextIR = require("src.core.game3.scripting.text_ir")
local Opcodes = require("src.core.game3.scripting.opcodes")
local Braille = require("src.ui.game3.braille")
local BATTLE = { battle = setmetatable({}, { __index = function() return "" end }) }
local function plain(ir)
return TextIR.toPlain(ir or {}, BATTLE) or ""
end
local BRAILLE_INC = "../pokefirered/data/text/braille.inc"
local PRET_LABEL, PRET_BODIES = {}, {}
do
local f = io.open(BRAILLE_INC, "rb")
if f then
local src = f:read("*a")
f:close()
for label, body in src:gmatch("(Braille_Text_[%w_]+)::%s*\n%s*%.braille%s+\"(.-)%$\"") do
PRET_LABEL[label] = body
PRET_BODIES[#PRET_BODIES + 1] = body
end
end
end
local havePret = #PRET_BODIES > 0
local function checkText(got, label, msg)
local want = PRET_LABEL[label]
if not want then
print("[skip] " .. msg .. ": no " .. BRAILLE_INC)
return
end
check(got == want, msg .. ", got " .. string.format("%q", got))
end
print("[test] 1. the braille charmap is the inverse of pret's braille encoder")
local map = Extract.BRAILLE_CHARMAP
check(type(map) == "table", "ExtractScripts.BRAILLE_CHARMAP exists")
check(type(Extract.decodeBraille) == "function", "ExtractScripts.decodeBraille exists")
if type(map) ~= "table" or type(Extract.decodeBraille) ~= "function" then finish() end
local entries = 0
local badInverse = nil
for code, ch in pairs(map or {}) do
entries = entries + 1
if Braille.CODE[ch] ~= code then
badInverse = badInverse or string.format("0x%02X -> %q re-encodes to %s", code, ch,
tostring(Braille.CODE[ch]))
end
end
-- include/characters.h:285
check(entries == 39, "39 meaningful braille codes mapped (got " .. entries .. ")")
check(badInverse == nil, "every code round-trips through Braille.CODE: " .. tostring(badInverse))
check(map[0x00] == " " and map[0x01] == "A" and map[0x39] == "Z" and map[0x3A] == "#",
"space, A, Z and the number indicator")
check(map[0x02] == nil and map[0x08] == nil and map[0x3D] == nil,
"the meaningless dot combinations stay unmapped")
print("[test] 2. decodeBraille on raw cart bytes")
-- data/text/braille.inc:47
local EVERYTHING = { 0x09, 0x35, 0x09, 0x1D, 0x3B, 0x1E, 0x0D, 0x06, 0x1B, 0x0F, 0xFF }
local ir = Extract.decodeBraille(EVERYTHING)
checkText(plain(ir), "Braille_Text_Everything", "0x09 0x35 ... 0x0F decodes to pret's literal")
check(ir[#ir] and ir[#ir].t == "eos", "0xFF terminates the string")
-- data/text/braille.inc:50
local HAS_MEANING = { 0x0D, 0x01, 0x16, 0x00, 0x13, 0x09, 0x01, 0x1B, 0x06, 0x1B, 0x0F, 0xFF }
checkText(plain(Extract.decodeBraille(HAS_MEANING)), "Braille_Text_HasMeaning1",
"0x00 is a space, not a terminator")
local nl = Extract.decodeBraille({ 0x31, 0x17, 0xFE, 0x0B, 0x19, 0x2E, 0x1B, 0xFF })
if havePret then
check(plain(nl) == PRET_LABEL.Braille_Text_Up .. "\n" .. PRET_LABEL.Braille_Text_Down,
"0xFE is a line break, got " .. string.format("%q", plain(nl)))
else
print("[skip] the 0xFE line break check: no " .. BRAILLE_INC)
end
check(plain(Extract.decodeBraille({ 0x3D, 0xFF })) == "?",
"an unmapped dot combination falls through to ?")
print("[test] 3. the same bytes through the latin charmap are the old garbage")
check(plain(TextIR.decode(EVERYTHING)) ~= plain(Extract.decodeBraille(EVERYTHING)),
"TextIR.decode still reads braille bytes as latin, so the braille path has to be separate")
print("[test] 4. bfsFromSeeds remaps both braille opcodes")
local BRAILLE_AT = 0x40
local LATIN_AT = 0x60
local buf = {}
local function put(off, ...)
local bytes = { ... }
for i = 1, #bytes do buf[off + i] = bytes[i] end
end
local function putPtr(off, addr)
put(off, addr % 256, math.floor(addr / 256) % 256,
math.floor(addr / 65536) % 256, math.floor(addr / 16777216) % 256)
end
-- data/script_cmd_table.inc:107,124,215
put(0, 0x78); putPtr(1, 0x08000000 + BRAILLE_AT)
put(5, 0xD3); putPtr(6, 0x08000000 + BRAILLE_AT)
put(10, 0x67); putPtr(11, 0x08000000 + LATIN_AT)
put(15, 0x02)
for i = 1, #EVERYTHING do buf[BRAILLE_AT + i] = EVERYTHING[i] end
put(LATIN_AT, 0xBB, 0xBC, 0xBD, 0xFF)
local rom = {}
rom.size = 0x100
function rom:get(off)
if off < 0 or off >= self.size then error(("ROM OOB 0x%X"):format(off)) end
return buf[off + 1] or 0
end
function rom:readBytes(off, len)
local out = {}
for i = 0, len - 1 do
if off + i >= self.size then break end
out[i + 1] = self:get(off + i)
end
return out
end
function rom:ptrOffset(ptr)
if ptr < 0x08000000 or ptr >= 0x0A000000 then return nil end
return ptr - 0x08000000
end
local bfs = Extract.bfsFromSeeds(rom, { 0x08000000 })
local rows = bfs.scripts[Opcodes.key(0x08000000)]
check(type(rows) == "table" and #rows == 4, "the stub script disassembled into 4 rows")
local bKey = Opcodes.key(0x08000000 + BRAILLE_AT)
local lKey = Opcodes.key(0x08000000 + LATIN_AT)
check(rows[1] and rows[1].op == "braillemessage" and rows[1][1] == bKey and rows[1].ptr == bKey,
"braillemessage operand remapped to " .. bKey)
check(rows[2] and rows[2].op == "getbraillestringwidth" and rows[2][1] == bKey
and rows[2].ptr == bKey,
"getbraillestringwidth operand remapped instead of staying a raw ROM pointer")
checkText(plain(bfs.text[bKey]), "Braille_Text_Everything",
"the braille body decoded with the braille charmap")
check(plain(bfs.text[lKey]) == "ABC",
"the neighbouring latin message still decodes with the latin charmap, got "
.. plain(bfs.text[lKey]))
print("[test] 5. the cart itself")
local romPath = "../pokefirered/pokefirered.gba"
local f = io.open(romPath, "rb")
if not f then
print("[skip] cart byte check: no " .. romPath)
else
-- data/text/braille.inc:46
f:seek("set", 0x1A92C5)
local raw = f:read(11) or ""
f:close()
local bytes = {}
for i = 1, #raw do bytes[i] = raw:byte(i) end
checkText(plain(Extract.decodeBraille(bytes)), "Braille_Text_Everything",
"ROM 0x1A92C5 decodes to pret's literal")
end
print("[test] 6. the imported cache")
local Cache = require("tests.game3_cache")
local bundle = Cache.bundle("scripts/scripts.lua")
if not bundle then
print("[skip] game3_stitchimp_braille_text_test cache checks: " .. tostring(Cache.reason))
finish()
end
local brailleKeys, order = {}, {}
for _, script in pairs(bundle.scripts or {}) do
if type(script) == "table" then
for _, row in ipairs(script) do
if type(row) == "table"
and (row.op == "braillemessage" or row.op == "getbraillestringwidth") then
local key = row.ptr or row[1]
if not brailleKeys[key] then
brailleKeys[key] = {}
order[#order + 1] = key
end
brailleKeys[key][row.op] = (brailleKeys[key][row.op] or 0) + 1
end
end
end
end
table.sort(order)
if havePret then
check(#order == #PRET_BODIES,
#PRET_BODIES .. " distinct braille strings reached by the BFS (got " .. #order .. ")")
else
print("[skip] the braille string count: no " .. BRAILLE_INC)
end
local rawOperand, missing, unmapped, mismatch = nil, nil, nil, nil
local seen = {}
for _, key in ipairs(order) do
if type(key) ~= "string" then
rawOperand = rawOperand or tostring(key)
else
local body = plain(bundle.text[key])
if body == "" then
missing = missing or key
else
seen[body] = (seen[body] or 0) + 1
for ch in body:gmatch("[^\n]") do
if Braille.CODE[ch] == nil then
unmapped = unmapped or (key .. " has " .. string.format("%q", ch))
end
end
local glyphs = Braille.countGlyphs(body)
local want = select(2, body:gsub("[^\n]", ""))
if glyphs ~= want then
mismatch = mismatch or (key .. " " .. glyphs .. " glyphs for " .. want .. " chars")
end
end
end
end
check(rawOperand == nil, "no braille operand left as a raw ROM pointer: " .. tostring(rawOperand))
check(missing == nil, "every braille key has a body in scripts/text.lua: " .. tostring(missing))
check(unmapped == nil,
"every decoded glyph is a braille character the wall can draw: " .. tostring(unmapped))
check(mismatch == nil,
"Braille.encode gives one glyph per decoded character: " .. tostring(mismatch))
if havePret then
local wantCount = {}
for _, body in ipairs(PRET_BODIES) do wantCount[body] = (wantCount[body] or 0) + 1 end
local absent, duped = nil, nil
for body, n in pairs(wantCount) do
if not seen[body] then
absent = absent or body
elseif seen[body] ~= n then
duped = duped or string.format("%q cached %d times, pret writes it %d", body, seen[body], n)
end
end
check(absent == nil,
"every pret .braille literal is in the cache verbatim: " .. tostring(absent))
check(duped == nil,
"each repeated literal is its own string in the cache: " .. tostring(duped))
else
print("[skip] the cache against pret's literals: no " .. BRAILLE_INC)
end
-- asm/macros/event.inc:1845
local paired = 0
for _, key in ipairs(order) do
local ops = brailleKeys[key]
if ops.braillemessage and ops.getbraillestringwidth then paired = paired + 1 end
end
check(paired == 22, "22 braillemessage_wait pairs share a text key (got " .. paired .. ")")
local lowercase = 0
for _, t in pairs(bundle.text or {}) do
if type(t) == "table" and plain(t):find("%l") then lowercase = lowercase + 1 end
end
check(lowercase > 1000,
"the latin text pack is untouched, " .. lowercase .. " entries still have lowercase")
finish()