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

144 lines
6.2 KiB
Lua

-- #2319 (second report): the nickname screen's title ran past the right edge of
-- the text box. The catch flow hand-wrote "YOUR POKEMON'S NICKNAME?" (141px in a
-- 127px window), where pret composes gSpeciesNames[mon] + gText_PkmnsNickname:
-- pokefirered/src/naming_screen.c:1712 DrawMonTextEntryBox
-- pokefirered/src/strings.c:772 gText_PkmnsNickname = "'s nickname?"
-- sWindowTemplates[WIN_TEXT_ENTRY_BOX] = {tilemapLeft 9, width 16} → screen
-- x 72..200, text printed at (1,1) → x 73, so the budget is 127px.
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local ROM = { gText_PkmnsNickname = "'s nickname?", gText_YourName = "YOUR NAME?" }
package.loaded["src.core.game3.rom_text"] = {
plain = function(key) return ROM[key] or key end, box = function(key) return ROM[key] or key end,
ascii = function(key) return ROM[key] or key end, has = function() return true end,
key = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
at = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
count = function() return 0 end, list = function() return {} end,
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
}
-- --- exact GBA advance widths, from the extracted FRLG latin font ------------
local function loadTable(path, pattern)
local f = io.open(path, "r")
if not f then return nil end
local src = f:read("*a")
f:close()
local t = {}
for a, b in src:gmatch(pattern) do t[tonumber(a)] = tonumber(b) end
return t
end
local widths = loadTable("src/import/gba/chrome/fonts/latin_widths.lua", "%[(%d+)%]%s*=%s*(%d+)")
check(widths ~= nil and next(widths) ~= nil, "latin glyph widths are loadable")
-- TextIR.CHARMAP: glyph id -> character, inverted to character -> glyph id.
local charmap = require("src.core.game3.scripting.text_ir").CHARMAP
local glyphOf = {}
for gid, ch in pairs(charmap) do
if type(gid) == "number" and type(ch) == "string" and #ch == 1 then
glyphOf[ch] = gid
end
end
check(next(glyphOf) ~= nil, "TextIR.CHARMAP is invertible to character -> glyph id")
local function px(s)
local total = 0
for i = 1, #s do
local gid = glyphOf[s:sub(i, i)]
if gid then total = total + (widths[gid] or 0) end
end
return total
end
local LEFT, BUDGET = 73, 127 -- text-entry window starts at x=73, 127px of room
local function endsAt(s) return LEFT + px(s) end
-- --- the reported string genuinely overflowed -------------------------------
local reported = "YOUR POKEMON'S NICKNAME?"
check(px(reported) > BUDGET,
("the old hardcoded title (%dpx) really did overflow the %dpx window"):format(px(reported), BUDGET))
check(endsAt(reported) > 200,
"the old title ran past the window's right edge (x=200)")
-- --- Naming.monTitle mirrors pret -------------------------------------------
local Naming = require("src.ui.game3.naming")
eq(type(Naming.monTitle), "function", "Naming.monTitle exists")
eq(Naming.monTitle("CHARMANDER"), "CHARMANDER's nickname?",
"mon title is gSpeciesNames[mon] + gText_PkmnsNickname")
eq(Naming.monTitle("BULBASAUR"), "BULBASAUR's nickname?", "title tracks the species")
check(Naming.monTitle("CHARMANDER") ~= Naming.monTitle("SQUIRTLE"),
"title is species-derived, not a fixed caption")
eq(Naming.monTitle(nil), "'s nickname?", "a missing name prints gText_PkmnsNickname alone")
-- --- every real species title fits the window -------------------------------
-- Longest real species names (both 10 characters) plus the widest 10-character
-- name the font can produce: a strict upper bound over all caps names.
for _, name in ipairs({ "CHARMANDER", "FORRETRESS", "MASQUERAIN", "BUTTERFREE" }) do
local title = Naming.monTitle(name)
check(endsAt(title) <= 200,
("%q ends at x=%d, inside the box (<=200)"):format(title, endsAt(title)))
end
local widest = {}
for ch in pairs(glyphOf) do
if ch:match("^[A-Z]$") then widest[#widest + 1] = ch end
end
table.sort(widest, function(a, b)
local wa, wb = widths[glyphOf[a]] or 0, widths[glyphOf[b]] or 0
if wa == wb then return a < b end
return wa > wb
end)
local worstName = table.concat({ widest[1], widest[2], widest[3], widest[4], widest[5],
widest[6], widest[7], widest[8], widest[9], widest[10] })
local worstTitle = Naming.monTitle(worstName)
check(endsAt(worstTitle) <= 200,
("worst-case 10-char name %q ends at x=%d, still inside the box"):format(worstTitle, endsAt(worstTitle)))
-- --- the screen clamps the title to the window ------------------------------
-- pret blits glyphs into the window buffer (CopyGlyphToWindow), which clips at
-- the window edge; the port must pass that window as maxWidth rather than the
-- generic 240 default, which would let text run over the frame art.
eq(Naming.L.titleMaxW, BUDGET, "L.titleMaxW is the text-entry window's usable width")
eq(Naming.L.titleX, LEFT, "the title still starts at the window's left edge")
local FrlgFont = require("src.ui.game3.frlg_font")
local realDraw = FrlgFont.draw
local seen = {}
FrlgFont.draw = function(text, x, y, opts)
seen[#seen + 1] = { text = text, x = x, y = y, maxWidth = opts and opts.maxWidth }
return 0
end
package.loaded["src.core.game3.pokemon"] = {
icon = function() return nil end,
frontPic = function() return nil end,
picSpecies = function(sp) return sp end,
}
package.loaded["src.ui.game3.stack"] = {
push = function() end, pop = function() end,
busy = function() return false end, drawOrder = function() return {} end,
}
local title = Naming.monTitle("BUTTERFREE")
Naming.open({ template = "CAUGHT_MON", species = 12, maxLen = 10, title = title })
local ok, err = pcall(Naming.draw)
Naming.dismiss()
FrlgFont.draw = realDraw
check(ok, "nickname draw runs headless: " .. tostring(err))
local drawn
for _, s in ipairs(seen) do
if s.text == title then drawn = s end
end
check(drawn ~= nil, "the title is drawn as its own run")
eq(drawn and drawn.x, LEFT, "title drawn at the window's left edge")
eq(drawn and drawn.y, 33, "title drawn at the template's y")
eq(drawn and drawn.maxWidth, BUDGET,
"title is clamped to the window, not the 240px default that overran the frame")
T.finish()