No overview comment at the top of the file.
- Imports
- Matches
- Used by
- 1 action1 repository1 component1 other
Types
The match context an ingest request was resolved to belong to.
export type IngestContext =
| { type: "tournament"; tournamentId: number }
| { type: "sendouq"; groupMatchId: number };The reported game result an ingested match can link to.
export type IngestableGameTarget =
| {
type: "tournament";
matchGameResultId: number;
tournamentMatchId: number;
}
| { type: "sendouq"; groupMatchMapId: number; groupMatchId: number };interfaceIngestableGame
:51 A game of a tournament or SendouQ match that ingested matches can be linked to.
export interface IngestableGame {
target: IngestableGameTarget;
/** 0-based index of the game within its match */
mapIndex: number;
mode: ModeShort;
stageId: StageId;
/** user ids who played the game on the winning side (a tournament game's active roster, a SendouQ group); only they can link a scan to it */
winnerUserIds: number[];
/** user ids who played the game on the losing side, see `winnerUserIds` */
loserUserIds: number[];
/** both teams' known in-game names by user id, tells whose seat a POV read marks */
inGameNameByUserId: Map<number, string>;
/** timestamp of the game's report: the chronological key and what a scan's play time is measured against */
playedAt: number;
/** winner-first row-order names of the game's earliest linked ingest that read enough names to recognize a re-detection, null when none; lets matching skip taken games yet recognize re-detections */
linkedPlayerNames: string[] | null;
}interfaceIngestableGameWithContext
:70 A candidate game for content resolution, tagged with its context.
export interface IngestableGameWithContext extends IngestableGame {
context: IngestContext;
}interfaceIngestedScoreboardPlayer
no JSDoc:213 export interface IngestedScoreboardPlayer {
name: string;
tournamentTeamId: number | null;
weaponSplId: MainWeaponId | null;
ka: number | null;
d: number | null;
s: number | null;
paint: number | null;
/** [head, clothes, shoes] ability rows gathered from the match's death screens */
abilities?: AbilityWithUnknown[][];
/** set via POV attribution of a linked ingested match */
userId?: number;
}interfaceIngestedScoreboardData
:228 A game's scoreboard derived from its linked ingested matches, the shape match pages render. Derived at read time, not stored.
export interface IngestedScoreboardData {
/** game scores [winner, loser] (0-100; a knockout's winner is 100) */
scores: [number | null, number | null];
/** in scoreboard order: rows 0-3 winning team, rows 4-7 losing team */
players: IngestedScoreboardPlayer[];
/** objective-counter progress winner-first (per-team values [winner, loser], `control` 0 = winner), `t` in seconds since the game's first read (the source video is not stored). Absent when no counter was read. */
objective?: ScannerMatchObjective;
/** per-player special/death samples, teams winner-first, `t` on the same origin as `objective`. Absent when the icon strip was never read. */
playerStatus?: ScannerMatchPlayerStatus;
}Functions & values
constPLAYED_AT_TOLERANCE_MS
:34 Max distance between a scan's play time and a game's report for them to be the same game: headroom for a slow reporter and the client's clock, still far inside the gap between two plays of one map. Outside every candidate the scan stays unlinked (re-sendable) rather than silently putting strangers on a match page.
export const PLAYED_AT_TOLERANCE_MS = 30 * 60 * 1000;
Stable grouping/equality key for an {@link IngestContext}.
export function contextKey(context: IngestContext): string
functionresolveContext
:92 Resolves which context (tournament or SendouQ match) a request's matches belong to from content alone: the POV user's reported games are grouped by context and each is scored by how many matches matchedGames aligns with its games.
export function resolveContext({
matches,
games,
povUserId,
}: {
matches: ScannerMatch[];
games: IngestableGameWithContext[];
povUserId: number | null;
}): IngestContext | nullUnit tests (5)
Decides which game result each ingested match links to.
Only matches with a known winner, two full teams and a play time qualify (minimap-only and VoD reads never link). Matches and games are walked chronologically: each match takes the unassigned game of the same mode+stage reported nearest it that the sender played on the POV seat's side, none beyond PLAYED_AT_TOLERANCE_MS.
A scan only links to a game its sender played: one without a POV seat (cast footage) or sent by someone outside the game's active rosters never does. Other lobbies, unreadable mode/stage and duplicate detections are skipped.
Matches arrive over many requests, so games already linked are skipped — unless the incoming match is a re-detection of the linked one, which lands on the same game so re-sends stay idempotent and another POV's scan joins it.
export function matchedGames({
matches,
games,
povUserId,
}: {
matches: ScannerMatch[];
games: IngestableGame[];
/** the sender, who is the POV player of the request's non-cast matches */
povUserId: number | null;
}): MatchedGame[]Unit tests (22)
The weapon the sender played in a linked game, read off the scan's POV seat. Null unless the sender played on the seat's side and the seat's name isn't another roster member's: a teammate's recording marks someone else's seat.
export function povWeaponId({
match,
game,
povUserId,
}: {
match: ScannerMatch;
game: IngestableGame;
povUserId: number;
}): MainWeaponId | nullUnit tests (3)
functionderiveScoreboardData
:245 Derives a game's scoreboard from its linked ingests: earliest link is the base, later ones enrich it field-wise (Matches.mergeMatches), projected winner-first, with every linked POV seat attributing its row to the POV user. winnerTeamId/loserTeamId are the result's sides (tournament team or SendouQ group ids) stamped onto the rows.
export function deriveScoreboardData({
linked,
winnerTeamId,
loserTeamId,
}: {
/** in link order, earliest first */
linked: Array<{ data: ScannerMatch; povUserId: number | null }>;
winnerTeamId: number;
loserTeamId: number | null;
}): IngestedScoreboardData | nullUnit tests (18)
functionrecognizablePlayerNames
:295 A linked match's players winner-first in row order (unread names as ""), the game's linkedPlayerNames. Null when it can't form a view or read too few names to ever recognize a re-detection: such a read must not lock the game against every other POV's scan.
export function recognizablePlayerNames(match: ScannerMatch): string[] | null
Unit tests (3)
Not shown, only used by tests: MatchedGame