The Kit/Patterns

Momentum Curve & Incidents

A derived session-intensity 'momentum' curve (weighted, smoothed, 0..1 across 200 bins) + kinematic incident detection (cars stopped on track) fused with race control. Memoized.

Reference implementation: this source keeps imports to modules from its home codebase. Read it for the technique, adapt the imports to yours; it is not drop-in compile-ready, on purpose.

events.ts
// ============================================================================
// Track 2 / Phase 6 — event engine + momentum.
// A derived session-INTENSITY curve (the "race narrative") under the scrubber,
// computed by replaying the real event log and weighting what's happening:
// incidents (red/SC/yellow), fastest-lap pushes, stewards/car events, radio, pits.
// Plus kinematic incident detection (a car going stationary on track) fused with
// the official Race Control text. Pure (no React). Memoized on (bundle, events.length).
//
// HONEST: every weight comes from a real fed event; intensity is a derived index
// (badged "est."), not an official metric. Kinematic "stopped on track" is detected
// from real position data, confirmed against Race Control / Retired flags.
// ============================================================================

import type { FeedEvent, SessionBundle } from "../shared/types";
import { asEntries } from "../shared/merge";

export interface MomentumBin {
  f: number; // 0..1 fraction of the session timeline
  v: number; // 0..1 normalized intensity
}
export interface Momentum {
  bins: MomentumBin[];
  peakF: number; // fraction of the single most intense moment (auto-highlight anchor)
}

interface EngineLike {
  bundle: SessionBundle | null;
}

let cacheKey = -1;
let cacheBundle: unknown = null;
let cache: Momentum | null = null;

const BINS = 200;

/** weight a single event by how much it moves the session narrative */
function weightOf(feed: string, patch: unknown): number {
  const p = patch as Record<string, unknown>;
  if (feed === "TrackStatus") {
    const st = String(p?.Status ?? "");
    return st === "5" ? 12 : st === "4" || st === "6" ? 9 : st === "2" ? 6 : st === "7" ? 2 : 0.5;
  }
  if (feed === "RaceControlMessages") {
    let w = 0;
    for (const [, m] of asEntries<Record<string, unknown>>(p?.Messages)) {
      const cat = String(m?.Category ?? "");
      const flag = String(m?.Flag ?? "").toUpperCase();
      if (cat === "SafetyCar") w += 8;
      else if (cat === "CarEvent") w += 6;
      else if (flag === "RED") w += 10;
      else if (flag.includes("YELLOW")) w += 4;
      else if (flag === "CHEQUERED") w += 3;
      else if (cat === "Drs") w += 0.4;
      else w += 3; // stewards / incident notes / other
    }
    return w;
  }
  if (feed === "TimingData") {
    let w = 0;
    for (const [, line] of asEntries<Record<string, any>>(p?.Lines)) {
      if (line?.LastLapTime?.OverallFastest === true || line?.LastLapTime?.OverallFastest === "true") w += 4;
      else if (line?.LastLapTime?.Value) w += 0.6; // a completed lap = mild activity
      if (typeof line?.InPit !== "undefined") w += 0.5;
    }
    return w;
  }
  if (feed === "TeamRadio") return 2;
  if (feed === "PitLaneTimeCollection") return 1;
  return 0;
}

/** smoothed, normalized session-intensity curve (memoized per bundle) */
export function momentumCurve(engine: EngineLike): Momentum {
  const b = engine.bundle;
  if (!b) return { bins: [], peakF: 0 };
  if (b === cacheBundle && b.events.length === cacheKey && cache) return cache;
  const t1 = b.meta.t1 || (b.events.length ? b.events[b.events.length - 1][0] : 1);
  const raw = new Float64Array(BINS);
  for (const [t, feed, patch] of b.events as FeedEvent[]) {
    const w = weightOf(feed, patch);
    if (!w) continue;
    const bi = Math.min(BINS - 1, Math.max(0, Math.floor((t / t1) * BINS)));
    raw[bi] += w;
  }
  // gaussian-ish smoothing (3 box passes)
  let cur = raw;
  for (let pass = 0; pass < 3; pass++) {
    const next = new Float64Array(BINS);
    for (let i = 0; i < BINS; i++) {
      const a = cur[Math.max(0, i - 1)];
      const c = cur[Math.min(BINS - 1, i + 1)];
      next[i] = (a + cur[i] * 2 + c) / 4;
    }
    cur = next;
  }
  let max = 0, peakI = 0;
  for (let i = 0; i < BINS; i++) if (cur[i] > max) { max = cur[i]; peakI = i; }
  const bins: MomentumBin[] = [];
  for (let i = 0; i < BINS; i++) bins.push({ f: (i + 0.5) / BINS, v: max > 0 ? cur[i] / max : 0 });
  const out = { bins, peakF: (peakI + 0.5) / BINS };
  cacheKey = b.events.length;
  cacheBundle = b;
  cache = out;
  return out;
}

// ---- kinematic incident detection (cars stopping on track), fused with Race Control ----
export interface Incident {
  t: number; // stream-clock ms
  num: string;
  kind: "stopped" | "retired";
  rc: string | null; // matched Race Control / steward text, when available
}

/**
 * Cars that go stationary out on track (not in the pit lane), detected from the raw
 * position column (no movement over a window), cross-referenced with Retired flags.
 * The official Race Control text (turn/driver) is attached when a nearby message exists.
 */
export function incidents(engine: EngineLike): Incident[] {
  const b = engine.bundle;
  if (!b) return [];
  const out: Incident[] = [];
  const WIN = 12_000; // ms stationary
  const MOVE = 250; // world units (≈25 m)
  for (const [num, c] of Object.entries(b.pos)) {
    let flagged = false;
    for (let i = 0; i < c.t.length; i++) {
      if (c.st[i] !== 1 || (c.x[i] === 0 && c.y[i] === 0)) continue;
      // find a sample ~WIN ago
      const t0 = c.t[i] - WIN;
      let j = i;
      while (j > 0 && c.t[j] > t0) j--;
      if (c.t[i] - c.t[j] < WIN * 0.7) continue;
      if (c.x[j] === 0 && c.y[j] === 0) continue;
      const moved = Math.hypot(c.x[i] - c.x[j], c.y[i] - c.y[j]);
      if (moved < MOVE) {
        if (!flagged) {
          out.push({ t: c.t[i], num, kind: "stopped", rc: rcNear(b, c.t[i], num) });
          flagged = true;
        }
      } else {
        flagged = false;
      }
    }
  }
  return out.sort((a, z) => a.t - z.t);
}

function rcNear(b: SessionBundle, t: number, num: string): string | null {
  for (const [et, feed, patch] of b.events as FeedEvent[]) {
    if (feed !== "RaceControlMessages") continue;
    if (Math.abs(et - t) > 30_000) continue;
    for (const [, m] of asEntries<Record<string, unknown>>((patch as Record<string, unknown>)?.Messages)) {
      const rn = String(m?.RacingNumber ?? "");
      const msg = String(m?.Message ?? "");
      if (rn === num || msg.includes(`(${num})`)) return msg;
    }
  }
  return null;
}
← The whole kit