File size: 5,540 Bytes
91677d6
 
 
5a67aa1
91677d6
 
 
 
 
5a67aa1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91677d6
 
 
 
 
 
 
 
 
 
 
95e3d2a
91677d6
 
e66c992
91677d6
 
 
 
 
 
 
 
 
 
95e3d2a
91677d6
 
e66c992
91677d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e66c992
91677d6
 
 
 
 
 
5a67aa1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91677d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e66c992
 
 
 
 
 
91677d6
 
 
 
 
 
 
 
 
 
e66c992
91677d6
 
 
 
 
 
 
 
95e3d2a
e66c992
 
91677d6
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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;
}