Spaces:
Running
Running
| import type { Match, Round } from '$lib/types'; | |
| const TICK_RATE = 64; | |
| const PLAYERS_PER_ROUND = 10; | |
| export type MapRow = Match & { | |
| duration_s: number; | |
| }; | |
| export type PovRow = { | |
| match_id: number; | |
| map_name: string; | |
| round: number; | |
| player: number; | |
| duration_s: number; | |
| event: string; | |
| team1: string; | |
| team2: string; | |
| score1: number; | |
| score2: number; | |
| winner: Match['winner']; | |
| format: string; | |
| match_date: string; | |
| uploaded_at: string; | |
| rounds_played: number; | |
| // When this row is materialized from an SQL filter, the timestamp inside | |
| // the POV's video to seek to on open. Otherwise undefined → opens at 0. | |
| start_t?: number; | |
| }; | |
| export type RoundRow = { | |
| match_id: number; | |
| map_name: string; | |
| round: number; | |
| demo_round: number; | |
| duration_s: number; | |
| event: string; | |
| team1: string; | |
| team2: string; | |
| score1: number; | |
| score2: number; | |
| winner: Match['winner']; | |
| format: string; | |
| match_date: string; | |
| uploaded_at: string; | |
| rounds_played: number; | |
| }; | |
| export type MatchRow = { | |
| match_id: number; | |
| event: string; | |
| team1: string; | |
| team2: string; | |
| score1: number; | |
| score2: number; | |
| winner: Match['winner'] | ''; | |
| format: string; | |
| match_date: string; | |
| uploaded_at: string; | |
| maps: string[]; | |
| maps_played: number; | |
| first_map: string; | |
| rounds_played: number; | |
| duration_s: number; | |
| }; | |
| export function buildMapRows(matches: Match[], rounds: Round[]): MapRow[] { | |
| const durationByMap = new Map<string, number>(); | |
| for (const r of rounds) { | |
| const k = `${r.match_id}|${r.map_name}`; | |
| durationByMap.set(k, (durationByMap.get(k) ?? 0) + (r.round_duration_ticks || 0) / TICK_RATE); | |
| } | |
| return matches.map((m) => ({ | |
| ...m, | |
| duration_s: durationByMap.get(`${m.match_id}|${m.map_name}`) ?? 0 | |
| })); | |
| } | |
| export function buildRoundRows(matches: Match[], rounds: Round[]): RoundRow[] { | |
| const matchByMap = new Map<string, Match>(); | |
| for (const m of matches) matchByMap.set(`${m.match_id}|${m.map_name}`, m); | |
| const out: RoundRow[] = []; | |
| for (const r of rounds) { | |
| const m = matchByMap.get(`${r.match_id}|${r.map_name}`); | |
| if (!m) continue; | |
| out.push({ | |
| match_id: r.match_id, | |
| map_name: r.map_name, | |
| round: r.round, | |
| demo_round: r.demo_round, | |
| duration_s: (r.round_duration_ticks || 0) / TICK_RATE, | |
| event: m.event, | |
| team1: m.team1, | |
| team2: m.team2, | |
| score1: m.score1, | |
| score2: m.score2, | |
| winner: m.winner, | |
| format: m.format, | |
| match_date: m.match_date, | |
| uploaded_at: r.uploaded_at, | |
| rounds_played: m.rounds_played | |
| }); | |
| } | |
| return out; | |
| } | |
| /** | |
| * Synthesize 10 rows per round (one per player slot). No per-POV metadata | |
| * is loaded eagerly — side/weapon would require fetching per-(match, map) | |
| * chunks-preview.parquet for every visible match, which doesn't pay back on | |
| * the home page. Per-POV detail surfaces inside the match viewer. | |
| */ | |
| export function buildPovRows(matches: Match[], rounds: Round[]): PovRow[] { | |
| const matchByMap = new Map<string, Match>(); | |
| for (const m of matches) matchByMap.set(`${m.match_id}|${m.map_name}`, m); | |
| const out: PovRow[] = []; | |
| for (const r of rounds) { | |
| const m = matchByMap.get(`${r.match_id}|${r.map_name}`); | |
| if (!m) continue; | |
| const duration_s = (r.round_duration_ticks || 0) / TICK_RATE; | |
| for (let player = 0; player < PLAYERS_PER_ROUND; player++) { | |
| out.push({ | |
| match_id: r.match_id, | |
| map_name: r.map_name, | |
| round: r.round, | |
| player, | |
| duration_s, | |
| event: m.event, | |
| team1: m.team1, | |
| team2: m.team2, | |
| score1: m.score1, | |
| score2: m.score2, | |
| winner: m.winner, | |
| format: m.format, | |
| match_date: m.match_date, | |
| uploaded_at: r.uploaded_at, | |
| rounds_played: m.rounds_played | |
| }); | |
| } | |
| } | |
| return out; | |
| } | |
| export function buildMatchRows(matches: Match[], rounds: Round[]): MatchRow[] { | |
| const durationByMap = new Map<string, number>(); | |
| for (const r of rounds) { | |
| const k = `${r.match_id}|${r.map_name}`; | |
| durationByMap.set(k, (durationByMap.get(k) ?? 0) + (r.round_duration_ticks || 0) / TICK_RATE); | |
| } | |
| const grouped = new Map<number, Match[]>(); | |
| for (const m of matches) { | |
| const arr = grouped.get(m.match_id); | |
| if (arr) arr.push(m); | |
| else grouped.set(m.match_id, [m]); | |
| } | |
| const rows: MatchRow[] = []; | |
| for (const [matchId, mapsForMatch] of grouped) { | |
| const sorted = [...mapsForMatch].sort((a, b) => (a.map_index ?? 0) - (b.map_index ?? 0)); | |
| const head = sorted[0]; | |
| const team1MapWins = sorted.filter((m) => m.winner === 'team1').length; | |
| const team2MapWins = sorted.filter((m) => m.winner === 'team2').length; | |
| const totalRounds = sorted.reduce((acc, m) => acc + m.rounds_played, 0); | |
| const totalDuration = sorted.reduce( | |
| (acc, m) => acc + (durationByMap.get(`${m.match_id}|${m.map_name}`) ?? 0), | |
| 0 | |
| ); | |
| // Use the latest upload across the match's maps so the row reflects when | |
| // the whole series finished landing in the dataset. | |
| const latestUpload = sorted.reduce( | |
| (acc, m) => (m.uploaded_at > acc ? m.uploaded_at : acc), | |
| head.uploaded_at | |
| ); | |
| rows.push({ | |
| match_id: matchId, | |
| event: head.event, | |
| team1: head.team1, | |
| team2: head.team2, | |
| score1: team1MapWins, | |
| score2: team2MapWins, | |
| winner: team1MapWins > team2MapWins ? 'team1' : team2MapWins > team1MapWins ? 'team2' : '', | |
| format: head.format, | |
| match_date: head.match_date, | |
| uploaded_at: latestUpload, | |
| maps: sorted.map((m) => m.map_name), | |
| maps_played: sorted.length, | |
| first_map: head.map_name, | |
| rounds_played: totalRounds, | |
| duration_s: totalDuration | |
| }); | |
| } | |
| rows.sort( | |
| (a, b) => | |
| new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime() || | |
| b.match_id - a.match_id | |
| ); | |
| return rows; | |
| } | |