 function formatRow(
   row: ResultRow,
   context: NationContext,
   allNames: string[],
   allScores: string[],
   allKds: string[],
   allAtks: string[],
   allDefs: string[]
 ): string {
   const char    = row.character;
   const goal    = Config.get({ section: "wrank", key: "goal" });
   const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1);
   const classKey = (typeof char.class === "object" ? char.class?.key : char.class) as ClassKey;
 
   const scoreEmoji = Emoji.get("score") || "📊";
   const scoreText  = row.score ? format.scoreBold(row.score.pts) : "—";
   const kdText     = row.score && (row.score.k || row.score.d) ? format.kd(row.score.k ?? 0, row.score.d ?? 0) : "—";
 
   // Column targets combine primary + secondary stat widths, so the main
   // row makes room for the stats line beneath it.
   const scoreColumn = [...allScores, ...allAtks];
   const kdColumn    = [...allKds, ...allDefs];
 
   const tokens: Record<string, string> = {
     wrank:      Layout.wrank(wrEntry, goal, context),
     class:      Emoji.class(classKey) || classKey || "?",
     name:       TextAlign.padToMax(char.name, allNames),
     indicators: Layout.indicators(char as any, { historyKey: row.historyKey }),
     score:      `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`,
     kd:         TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn),
   };
 
   const mainLine = Layout.formatRow(TEMPLATE, tokens);
 
   const hasStats = row.score && (row.score.atk || row.score.def || row.score.heal);
   log.debug(`formatRow stats check: char=${char.name} score=${JSON.stringify(row.score)} hasStats=${hasStats}`);
   if (!hasStats) return mainLine;
 
   const prefixText = `${tokens.wrank} ${tokens.class} ${tokens.name}${tokens.indicators}`;
   const prefixGap  = TextAlign.padLeft("", TextAlign.estimateWidth(prefixText));
   const atkText  = format.statText("anima_atk",  row.score!.atk,  "⚔️");
   const defText  = format.statText("anima_def",  row.score!.def,  "🛡️");
   const healText = format.statText("circle_massheal_purple", row.score!.heal, "💚");
 
   const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMaxOffset(defText, kdColumn, DEF_WIDTH_OFFSET)} ${TextAlign.gap(HEAL_GAP)}${healText}`;
 
   return `${mainLine}\n${statsLine}`;
 }
 
 function buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder {
   const { date, slot } = TGKey.parse(historyKey);
 
   const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella);
   const procyonRows = rows.filter((r) => r.character.nation === Nation.Procyon);
 
   const capContext = Layout.nationContext(capellaRows);
   const proContext = Layout.nationContext(procyonRows);
 
   const sortByScore = (a: ResultRow, b: ResultRow) => (b.score?.pts ?? 0) - (a.score?.pts ?? 0);
 
   const sortedCapella = [...capellaRows].sort(sortByScore);
   const sortedProcyon = [...procyonRows].sort(sortByScore);
 
   const capellaNames  = sortedCapella.map((r) => r.character.name);
   const procyonNames  = sortedProcyon.map((r) => r.character.name);
   const capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
   const procyonScores = sortedProcyon.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
   const capellaKds    = sortedCapella.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—");
   const procyonKds    = sortedProcyon.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—");
 
   const atkEmoji = Emoji.get("atk") || "⚔️";
   const defEmoji = Emoji.get("def") || "🛡️";
   const capellaAtks = sortedCapella.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : "");
   const procyonAtks = sortedProcyon.map((r) => r.score?.atk ? `${atkEmoji} ${format.number.abbrev(r.score.atk)}` : "");
   const capellaDefs = sortedCapella.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : "");
   const procyonDefs = sortedProcyon.map((r) => r.score?.def ? `${defEmoji} ${format.number.abbrev(r.score.def)}` : "");
 
   const capK = capellaRows.reduce((s, r) => s + (r.score?.k ?? 0), 0);
   const capD = capellaRows.reduce((s, r) => s + (r.score?.d ?? 0), 0);
   const proK = procyonRows.reduce((s, r) => s + (r.score?.k ?? 0), 0);
   const proD = procyonRows.reduce((s, r) => s + (r.score?.d ?? 0), 0);
 
   const capellaEmoji = Emoji.get("capella");
   const procyonEmoji = Emoji.get("procyon");
 
   const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNames, capellaScores, capellaKds, capellaAtks, capellaDefs));
   const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNames, procyonScores, procyonKds, procyonAtks, procyonDefs));
 
   const embed = new EmbedBuilder()
     .setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)
     .setColor(0xe8a317)
     .setFooter({ text: `TG Result · ${TGKey.toDisplay(historyKey)}` })
     .setTimestamp();
 
   EmbedHelpers.addPerPlayerColumn(embed, `${capellaEmoji} Capella${(capK || capD) ? ` — ${format.kd(capK, capD)}` : ""}`, capellaFormatted);
   embed.addFields({ name: "\u200b", value: "\u200b", inline: false });
   EmbedHelpers.addPerPlayerColumn(embed, `${procyonEmoji} Procyon${(proK || proD) ? ` — ${format.kd(proK, proD)}` : ""}`, procyonFormatted);
 
   return embed;
 }