Skip to content

Key Data Flows

Live Capture (LMU)

rF2 shared memory
  → LmuSharedMemorySampler.TrySample() at 40 Hz
  → LmuLiveRecorder: accumulate TelemetryPoint[], detect lap change
  → LapCompleted event → LiveCaptureService accumulates Laps
  → Auto-save (5 laps or 10s idle)
  → CsvTelemetryWriter → Sessions/sess_{guid}.csv
  → SessionCatalogStore.RegisterFile() → sessions.db
  → SyncService → POST /v1/sessions → Neon Postgres
  → OutboxDispatcher → FeedService + AchievementService

Session Analysis

User selects reference lap + current lap
  → AnalysisPipeline detects selection change
  → AnalysisEngine.Analyze(session, refLap, currentLap)
      → TrackSegmentation.BuildSegments(refLap)
      → LapResampler.Resample(refLap, currentLap)
      → Corner-by-corner insight detection
  → AnalysisState.SetResult(result)
  → Compare tab: ranked insights list
  → Telemetry tab: ScottPlot (Speed, Throttle, Brake, Delta)
  → Segments tab: per-corner speed/brake/throttle table

OAuth Login (Desktop PKCE)

User clicks "Login with Google"
  → AuthService: generate code_verifier + code_challenge (SHA256)
  → Start local HTTP listener on random port
  → Open browser: /v1/auth/login/google?redirect_uri=localhost:{port}&code_challenge=...
  → API: set apex_redirect cookie, redirect to Google
  → User authorizes → Google redirects to /v1/auth/callback/google
  → API: authenticate, find/create UserEntity, issue one-time code
  → Browser redirects to localhost:{port}/callback?code=...
  → AuthService: POST /v1/auth/exchange → JWT + refresh token
  → Store tokens in auth.dat → LoginCompleted event
  → ShellViewModel shows authenticated UI

AI Coach Chat

User types message in Coach tab
  → AIService: POST /v1/ai/chat
      body: { SessionContext: {track, lap times, insights, segment deltas},
              Messages: [{role, content}, ...] }
  → AiController → AiService
  → Build system prompt ("You are ApexLab Coach...")
  → Call Claude Messages API (claude-sonnet-4-6, max 1024 tokens)
  → Return reply → display in Coach tab
  → User appends follow-up → full history sent each turn

Session Upload & Events

POST /v1/sessions received
  → Create SessionEntity + LapEntity rows
  → If TrackPoints: enqueue TrackProcessingJobEntity
  → Write OutboxMessageEntity("session.uploaded")
  → Return { sessionId, receivedAt }

OutboxDispatcher (5s poll):
  → FeedService.PostAsync() → FeedEntryEntity (1h dedup)
  → AchievementService.CheckSessionUploadAsync()
      → check: first session, 10/50 sessions, multi-game, quality, time-of-day
      → TryEarn() for each matching achievement

TrackProcessingService (3s poll):
  → Normalize → Align → Resample → Fuse → Check convergence
  → On stable: freeze TrackVersionEntity (GeoJSON centerline)