Port the trainer card and the play time

The player's name in the start menu opens TrainerCard: page 1 with the
name, ID, money, Pokédex count and play time, page 2 with the Johto
leaders and the badges owned, turned and closed with the original
buttons. The card picture, leader faces and animated badges come from the
ROM with _CGB_TrainerCard's palettes. GameTimer now counts the play time
from the end of the introduction, capped at 999:59:59, and saves keep it.

Closes #23

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-10-01 13:32:14 +02:00
1 parent 38b9562bd3
commit 47698565ab
24 files changed
+954 -30

No files matched your search

+14 -6
View File
@@ -202,8 +202,8 @@ for (const [key, symbol] of Object.entries(ramNames)) {
if (!match || Number(match[1]) !== ram.get(symbol))
throw new Error(`Profile RAM mismatch: ${key} / ${symbol}`);
}
// BATTLE, OVERWORLD, PC, INTRO, STATS, MENU, POKEGEAR and MOM entries are keyed by their PRET labels.
for (const table of ["BATTLE", "OVERWORLD", "PC", "INTRO", "STATS", "MENU", "POKEGEAR", "MOM"]) {
// BATTLE, OVERWORLD, PC, INTRO, STATS, MENU, POKEGEAR, MOM and TRAINER_CARD entries are keyed by their PRET labels.
for (const table of ["BATTLE", "OVERWORLD", "PC", "INTRO", "STATS", "MENU", "POKEGEAR", "MOM", "TRAINER_CARD"]) {
const block = new RegExp(`export const ${table} = \\{([\\s\\S]*?)\\} as const;`).exec(profile)?.[1];
if (!block) throw new Error(`${table} symbol table is missing.`);
for (const [, quoted, bare, literal] of block.matchAll(/^\s+(?:"([^"]+)"|(\w+)):\s*(0x[0-9a-f]+),/gm)) {
@@ -265,8 +265,9 @@ for (const [name, file] of [
])
if (!profile.includes(`${name} = ${constantCount(file, name)};`))
throw new Error(`${name} does not match the reference.`);
// PRET constants: const_def/const/const_skip/const_next/shift_const and
// numeric or aliased DEF ... EQU, plus the battle command macro list.
// PRET constants: const_def/const/const_skip/const_next/shift_const,
// trainerclass and numeric or aliased DEF ... EQU, plus the battle command
// macro list.
const pretConstants = new Map();
{
const number = (text) =>
@@ -284,7 +285,8 @@ const pretConstants = new Map();
];
for (const file of files) {
let value = 0,
step = 1;
step = 1,
trainerClass = 0;
for (const raw of readFileSync(file, "utf8").split("\n")) {
const line = raw.split(";")[0].trim();
let match;
@@ -293,7 +295,13 @@ const pretConstants = new Map();
step = match[2] ? Number(match[2]) : 1;
} else if ((match = /^const_next\s+([$%]?[0-9a-fA-F]+)$/.exec(line))) value = number(match[1]);
else if ((match = /^const_skip(?:\s+(\d+))?$/.exec(line))) value += step * Number(match[1] ?? 1);
else if ((match = /^(const|shift_const|command)\s+(\w+)$/.exec(line))) {
else if ((match = /^DEF __trainer_class__ = (\d+)$/.exec(line))) trainerClass = Number(match[1]);
else if ((match = /^trainerclass\s+(\w+)$/.exec(line))) {
// DEF \1 EQU __trainer_class__, then const_def 1 for its trainers.
pretConstants.set(match[1], trainerClass++);
value = 1;
step = 1;
} else if ((match = /^(const|shift_const|command)\s+(\w+)$/.exec(line))) {
const stored = match[1] === "shift_const" ? 1 << value : value;
pretConstants.set(match[2], stored);
if (match[1] === "shift_const") pretConstants.set(`${match[2]}_F`, value);