CoachEvents
app/features/scanner/core/CoachEvents.tsCoach events: the moments of a scanned match worth reviewing, always from the POV player's team's side (an enemy push is the POV team's PUSH_DEFENSE, an enemy death streak is nothing). A cast has no POV player: its events are told from the side of the team picked to coach, and the types that follow the POV player (followsPovPlayer) are left out, as the camera and its kill feed hop between players. Config driven: DEFINITIONS names each event type and the rule it is detected by; the interpreter below analyzes the match once and runs every definition whose modes apply. A new type is a new row, a new kind of moment a new rule kind. Definitions of one rule differing only in how much it takes are its tiers, later ones stricter (STAGGER_20 over STAGGER_15): a moment shows under every tier it reaches.
Every event is a window into the footage (start..end, seconds into the video/stream) cut to show the lead-up: an objective taken counts from the first splat on the losing side within KILL_LEAD_WINDOW_S before it, less LEAD_BUFFER_S.
- Imports
- no other modules
- Used by
- 1 module2 components1 other
Types
export type CoachEventType = (typeof DEFINITIONS)[number]["type"];
export type CoachEventCategory = (typeof DEFINITIONS)[number]["category"];
export interface CoachEvent { type: CoachEventType; /** seconds into the video/stream the window starts at, lead-up included */ start: number; /** seconds into the video/stream the window ends at */ end: number; /** the match timer at `start`; null when the clock was never read */ time: number | null; }
A team picked to coach a cast from, in the game it was picked in.
export interface CastPick { match: ScannerMatch; team: Team; }
export interface CoachLife { /** seconds into the video/stream the life starts at: the game's start, else the respawn */ start: number; /** what happened in the life; null without a POV team (a cast) */ summary: CoachLifeSummary | null; }
export interface CoachLifeSummary { /** seconds alive, the first life counted from the game clock's start when read */ duration: number; /** the POV player's splats in the life (a trade on the death's second included); null without a kill feed read */ kills: ScannerMatchKill[] | null; /** null without the POV seat */ specialsUsed: number | null; /** null in Turf War or when no objective read falls in the life */ control: CoachLifeControl | null; }
SZ: whole seconds each side held the zone; TC/RM/CB: how far each side's count went down.
export interface CoachLifeControl { unit: "SECONDS" | "POINTS"; ours: number; theirs: number; }
Functions & values
export const DEFINITIONS = [ { type: "SPECIAL_USED", label: "Special used", category: "Special", variant: "Used", followsPovPlayer: true, rule: { kind: "specialUsed" }, }, { type: "OPENING_WON", label: "Opening won",
The match's coach events, ordered by start. povDeaths are the POV player's death reads (seconds into the video/stream, e.g. povDeathTimes of the match's sources); reads of one death may repeat. A cast's are told from castTeam's side. POV footage whose POV team is unknown (a results screen without the POV seat) has none.
export function ofMatch( match: ScannerMatch, povDeaths: readonly number[], castTeam: Team = 0, ): CoachEvent[]
The events less those a stricter tier of the same rule also covers, so each moment is left once, under the highest tier it reached (a 22s stagger as STAGGER_20, not also STAGGER_15). events of one match.
export function highestTiers(events: readonly CoachEvent[]): CoachEvent[]
The definition's display label.
export function label(type: CoachEventType): string
The category the type is grouped under.
export function category(type: CoachEventType): CoachEventCategory
Whether every type of the category needs the POV player, so casts have none of it.
export function followsPovPlayer(eventCategory: CoachEventCategory): boolean
The team the match is coached from. POV footage: its POV team (povTeamOf). A cast: the team picked in it, else the side sharing more player names with the latest pick's team (casts keep teams by ink color, so a team can change sides between games), else the latest pick's side, else teams[0]. castPicks are oldest first.
export function coachedTeam( match: ScannerMatch, castPicks: readonly CastPick[], ): Team | null
The POV player's lives in the match, chronological: the first starts at the game's start, then one at every respawn. A respawn is the first icon-strip read showing the player back after reading them splatted, else RESPAWN_FALLBACK_S after the death. The last life ends at the last HUD read (counter or icon strip, both re-confirmed every few seconds), else at match.endsAt — that is the results screen, often well after "Game!". povDeaths as in ofMatch; without a POV team only the game's start, with no summary.
export function lives( match: ScannerMatch, povDeaths: readonly number[], ): CoachLife[]
teams[pov.team] when a results screen named the seat; otherwise POV footage keeps the POV side as teams[0] (minimap own side, the left plate) until a results screen reorders teams winner-first — without the seat then, and on a cast, there is no POV team.
export function povTeamOf(match: ScannerMatch): Team | null
The winner of a game whose results screen was missed, off its objective samples (chronological, teams order): the side whose count went further down. Null on a tie, and when the last read has the clock running out with the trailing side holding the objective — overtime, whose end the counter reads don't show.
export function winnerByCount( objective: readonly ScannerMatchObjectiveSample[], ): Team | null