function buildRows(historyKey: TGKey): ResultRow[] { const { slot, date } = TGKey.parse(historyKey); let players: UserKey[] = Attendance.players(historyKey); const rows: ResultRow[] = []; const weekKey = WRank.weekKey(new Date(date)); // Fallback — read from history file directly if (players.length === 0) { const history = Store.read<{ scores: TGScore[] }>(TGKey.toHistoryPath(historyKey)); if (history?.scores) { players = [...new Set(history.scores.map((s: TGScore) => s.userKey))]; } } for (const userKey of players) { const chars = CharacterRegistry.forUser(userKey); // Find which character this user played in this TG // by checking score history for (const char of chars) { const score = Score.get({ character: char, slot, historyKey }); if (!score) continue; // skip chars with no score for this TG const wrEntry = WRank.entry(char.name, char.nation, weekKey); rows.push({ character: char, score, position: wrEntry ? { currentRank: wrEntry.currentRank, previousRank: wrEntry.previousRank, } : undefined, leavesCount: Leaves.countForChar({ characterName: char.name }), }); break; // found the char for this user, move to the next user } if (!rows.find(r => r.character.ownerKey === userKey)) { // Build from score directly const history = Store.read<{ scores: TGScore[] }>(TGKey.toHistoryPath(historyKey)); const score = history?.scores.find((s: TGScore) => s.userKey === userKey); if (score) { const foundChar = CharacterRegistry.find(score.characterName); const char: Character = foundChar ?? { name: score.characterName, class: CLASSES[score.class as ClassKey] ?? { key: score.class as ClassKey, name: score.class, shortName: score.class }, level: 0, nation: score.nation, ownerKey: score.userKey, }; rows.push({ character: char, score, leavesCount: 0 }); } } } return rows; }