"Outsmart the Grid"
Hacklytics 2026 β A real-time, interactive F1 pit wall simulator with a dual-screen architecture. Built in 36 hours at Georgia Tech's Hacklytics 2026 hackathon.
Apex was inspired by the new F1 movie and by the intensity of race-day decision-making it brings to life. Watching the pit wall pressure, split-second calls, and constant strategy adjustments made us want to build something that captures that same feeling β where users experience what it is like to be the one making the call under pressure, not just watching the race. That idea prompted us to create a simulator that combines the drama of F1 with data-driven strategy and live-race visuals.
Apex is a real-time F1 pit wall simulator where you act as the strategist. You get a live Monza track map with 20 cars moving in real time, plus a full timing board showing gaps, intervals, sector times, tyre compounds, and pit counts updating constantly as the race unfolds.
The live race UI (timing board, pit windows, undercut/overcut hints) is driven by frontend simulation logic seeded from real 2023 Monza data. When a chaos event fires from the iPad, the Python backend runs 10,000 Monte Carlo simulations via the trained two-stage model and returns win probability plus a strategy recommendation. A WebSocket chaos engine can trigger rain, crashes, safety cars, and penalties β which instantly force strategy changes and trigger context-aware race engineer radio updates (via Gemini). Judges and spectators can follow everything through a synced iPad steward view while you manage the player car APX.
βββββββββββββββββββββββ WebSocket ββββββββββββββββββββββββββββββββ WebSocket βββββββββββββββββββββββ
β iPad (Steward) β βββββββββββββββ β Python FastAPI Server β βββββββββββββββ β Laptop (Pit Wall) β
β /ipad route β chaos event β β results β / route β
β β β Ridge β XGBoost β Monte β β β
β Rain | Crash | SC β β Carlo β LLM β β Telemetry Canvas β
β buttons β β β β Timing Board β
βββββββββββββββββββββββ β /ws/chaos endpoint β β Strategy Panel β
ββββββββββββββββββββββββββββββββ β Weather / Tires β
βββββββββββββββββββββββ
Race visualization data (timing, weather, track shape, race control) is loaded from static JSON in web/lib/, exported once by get_f1_data.py. The backend handles chaos events and ML inference only.
- User taps "Rain" on the iPad
- Frontend sends a chaos payload via WebSocket to FastAPI (e.g.
{"event": "rain", "compound": "MEDIUM", "current_tire_age": 15, "laps_left": 30}) - Stage 1: Ridge regression estimates circuit base pace from track geometry (length + corners)
- Stage 2: XGBoost predicts the per-lap residual from tyre age, compound, position, fuel load, stint, driver, weather, and circuit geometry β anchored to the GP median pace (87.0s at Monza)
- NumPy Monte Carlo runs 10,000 simulations with event penalties + Gaussian noise in <0.2s
- Gemini (via OpenRouter) formats the math output into a radio call
- Result is broadcast to all connected Pit Wall clients via WebSocket
| Layer | Tech | Purpose |
|---|---|---|
| Frontend | Next.js App Router, Tailwind CSS | Pit Wall dashboard + iPad chaos console |
| Animation | GSAP | Smooth car position transitions on track map |
| Canvas | HTML5 <canvas>, requestAnimationFrame |
20-car track animation (car positions in refs, minimal React re-renders) |
| Backend | Python FastAPI | WebSocket server for chaos events + ML inference |
| ML β Stage 1 | Ridge Regression + Polynomial Features (degree 2) | Circuit-level base lap time prediction (generalizes across tracks) |
| ML β Stage 2 | XGBoost + sklearn ColumnTransformer |
Per-lap residual prediction from race-state features |
| Simulation | NumPy vectorized Monte Carlo | 10K race simulations in <0.2s |
| LLM | Gemini 2.5 Flash via OpenRouter | Text formatting only (zero math) |
| Data | FastF1 | Historical F1 timing, weather, telemetry |
Apex/
βββ Commands.md # Useful dev commands
βββ Info.md # Internal project notes
βββ Instructions.md # Setup instructions
βββ README.md
β
βββ web/ # Next.js frontend
β βββ app/
β β βββ page.tsx # Pit Wall dashboard (laptop screen)
β β βββ ipad/page.tsx # Steward chaos console (iPad screen)
β β βββ api/generate-portrait/ # Driver portrait API route
β β βββ layout.tsx
β βββ components/
β β βββ LandingPage.tsx # Onboarding / driver portrait flow
β β βββ TrackCanvas.tsx # HTML5 canvas car animation
β β βββ DriverCard.tsx # Driver info panel
β β βββ CarTimings.tsx # Live timing board + lap simulation
β β βββ StrategyPanel.tsx # Strategy recommendations (from WebSocket)
β β βββ PitWindow.tsx # Pit stop window calculator (frontend heuristics)
β β βββ WeatherPanel.tsx # Weather conditions
β β βββ TireDegradation.tsx# Tire wear charts
β β βββ RaceHistory.tsx # Event history feed
β βββ lib/
β βββ ChaosContext.tsx # WebSocket state management
β βββ useWebSocket.ts # WebSocket hook with auto-reconnect
β βββ types.ts # TypeScript interfaces (FastF1-aligned)
β βββ mock-data.ts # Loads JSON data + lookup helpers
β βββ format.ts # Time formatting utilities
β βββ timing_data.json # Monza lap timing (exported)
β βββ weather_data.json # Race weather time-series
β βββ monza.json # Track GPS coordinates
β βββ race_events_data.json
β βββ track_status_data.json
β
βββ server/ # Python FastAPI backend
βββ main.py # FastAPI server (WebSocket only)
βββ simulator.py # Monte Carlo simulation engine
βββ train_engine.py # Two-stage model training pipeline (Ridge + XGBoost)
βββ get_f1_data.py # FastF1 data extraction script
βββ test_ws.py # WebSocket testing utility
βββ notebooks/
β βββ feature_engineering.ipynb # Feature engineering notebook
βββ models/
β βββ engine_v2.joblib # Trained Stage 2 XGBoost model (generated)
β βββ pace_model_v2.joblib # Trained Stage 1 Ridge model (generated)
β βββ preprocessor_v2.joblib # Sklearn preprocessor (generated)
β βββ feature_columns_v2.json # Feature metadata (generated)
β βββ circuit_medians.json # Per-GP median lap anchors (generated)
βββ data/
βββ laps_train.csv # Lap training data (generated)
βββ laps_test.csv # Lap test data β Italy/Monza held out (generated)
βββ eval_report.json # Model metrics (generated by train_engine.py)
ββββββββββββββββββββββββββββββββββββββββββββββββ-βββ
β train_engine.py β
β β
get_f1_data.py β Input A: laps_train.csv (10 GPs) β
β β Input B: laps_test.csv (Italy β held out) β
FastF1 API β Input C: circuit table (TrackLength + Corners)β
β β β
βΌ β Stage 1: Ridge + PolynomialFeatures(deg=2) β
server/data/*.csv βββββ β predicts base lap time per circuit β
web/lib/*.json β β
(timing, weather, β Stage 2: XGBoost β
track, events) β β predicts residual β
β (lap time β GP median pace) β
β β
β Outputs: β
β engine_v2.joblib (Stage 2 XGBoost) β
β pace_model_v2.joblib (Stage 1 Ridge) β
β preprocessor_v2.joblib (sklearn pipeline) β
β feature_columns_v2.json (feature metadata) β
β circuit_medians.json (inference anchors) β
β eval_report.json (model metrics) β
βββββββββββββββββ¬ββββββββββββββββββββββββββββββββ-ββ
β
βΌ
main.py (runtime)
loads artifacts at startup
serves /ws/chaos (WebSocket only)
| File | Location | Rows / size | Description | Used By |
|---|---|---|---|---|
laps_train.csv |
server/data/ |
~9,366 laps | 10 GPs for model training | train_engine.py |
laps_test.csv |
server/data/ |
~878 laps | Italy (Monza) held-out test set | train_engine.py |
timing_data.json |
web/lib/ |
20 drivers | Starting grid + Monza lap anchors | Frontend timing board |
weather_data.json |
web/lib/ |
156 samples | Race weather time-series | WeatherPanel |
monza.json |
web/lib/ |
637 points | Track GPS coordinates | TrackCanvas |
race_events_data.json |
web/lib/ |
43 events | FIA race control messages | RaceHistory |
track_status_data.json |
web/lib/ |
β | Flag transitions | Yellow flag UI |
All web/lib/*.json files are exported by get_f1_data.py from FastF1. The frontend reads them directly β there are no REST data endpoints on the backend.
| Data | Reason |
|---|---|
| Full telemetry (all drivers Γ all laps) | ~10M+ rows β canvas interpolates from lap timing + track shape |
| Position data at 4Hz | Same issue β too large for a hackathon demo |
| Car data (RPM, throttle, brake for all cars) | Only needed for driver analysis, not strategy prediction |
| Q1/Q2/Q3 qualifying times | Qualifying pace β race pace (different fuel, tire management, traffic) |
train_engine.py uses a two-stage lap time modeling pipeline designed to generalize across circuits while still capturing lap-by-lap race behavior.
A Ridge regression model is fit on degree-2 polynomial features of TrackLength and Corners using 10 Grands Prix (one row per circuit). This stage predicts base lap time for a given circuit and can extrapolate to unseen tracks (e.g. Monza/Italy), though with error (~4.7s off on Italy).
Ridge is used because, with only 10 data points, a regularized linear model is safer for extrapolation than a tree-based model. Polynomial features are only used in this stage.
XGBoost is trained to predict the residual: actual lap time minus the GP median pace for that circuit. Stage 2 learns lap-by-lap deviations from the typical pace caused by tyre degradation, stint progression, position/traffic, fuel load, and weather.
EstBasePace (the Ridge output) is excluded from Stage 2 features β including it caused the model to memorise circuit identity and fail on held-out GPs. At inference on Monza, the anchor comes from circuit_medians.json (87.0s); Ridge is the fallback for unknown circuits.
| Feature | Type | Rationale |
|---|---|---|
| TyreLife | Numeric | Laps on current tyre set β degradation |
| Stint | Numeric | Which tyre stint β strategy phase |
| Position | Numeric | Dirty air / track position effects |
| FreshTyre | Numeric | New vs. used tyre set |
| Compound | Categorical (OHE) | SOFT / MEDIUM / HARD profiles |
| LapNumber | Numeric | Race progression |
| FuelLoad | Numeric | Normalized remaining fuel (derived from LapNumber) |
| Driver | Categorical (OHE) | Per-driver pace offset (2023 grid) |
| TrackLength, Corners | Numeric | Circuit geometry for extrapolation |
| AirTemp, TrackTemp, Humidity, Rainfall | Numeric | Weather conditions |
Sector times and speed-trap columns are excluded from training (mostly null or redundant).
- Remove invalid and pit laps
- Apply a 70β150% median lap time filter to remove outliers
- Estimate fuel load from lap number
- Fill missing stint and position values
- Median imputation + one-hot encoding for categorical features
- Save Stage 1 bundle, Stage 2 XGBoost, preprocessor, feature list, and circuit medians
Metrics from data/eval_report.json (run python train_engine.py to regenerate):
| Metric | Value | Notes |
|---|---|---|
| 5-Fold CV lap MAE | ~0.35s | Lap-level KFold on 10 training GPs |
| 5-Fold CV RΒ² | 0.996 | Explains 99.6% of lap-time variance in CV |
| Training MAE | 0.30s | Full fit on 10 GPs |
| Held-out Italy MAE | 0.64s | Monza test GP, using known circuit median anchor |
| Stage 1 Italy extrapolation | 82.3s est vs 87.0s actual | Ridge base pace; Stage 2 uses circuit median at inference |
| Aspect | Status | Notes |
|---|---|---|
| Two-stage modeling | Correct | Stage 1 Ridge extrapolates circuit pace; Stage 2 XGBoost captures lap dynamics |
| Monte Carlo 10K sims | Correct | Runs on backend chaos events; frontend timing uses separate heuristics |
| LLM as text formatter only | Correct | Gemini does zero math β just formats the numbers into radio calls |
| Circuit-level Stage 1 dataset | Limitation | Only 10 GPs for Ridge β more data would improve base extrapolation |
| Chaos penalties are hardcoded | Limitation | Rain, SC, crash deltas are manual estimates in simulator.py, not learned |
| Win probability is circular | Limitation | Compares model output (with noise) against model output (without noise) |
| No REST API | Limitation | Frontend reads static JSON; backend is WebSocket-only |
| Model versioning | v2 | Latest trained artifacts stored in server/models/ with v2 suffix |
- More circuit data for Stage 1 β Expanding from 10 to 20+ GPs would make the Ridge base prediction more robust.
- Learn chaos penalties from data β Use historical wet-weather and safety car races to learn actual penalties instead of hardcoding them.
- Predict degradation rate, not raw residuals β Modeling "0.08s slower per lap on softs" is more transferable than raw second-level residuals.
| Endpoint | Direction | Payload |
|---|---|---|
ws://localhost:8000/ws/chaos |
Client β Server | {"event": "rain", "compound": "MEDIUM", "current_tire_age": 15, "laps_left": 30, "position": 10, "stint": 2, "air_temp": 28.5, "track_temp": 43.0, "humidity": 45, "rainfall": 0} |
ws://localhost:8000/ws/chaos |
Server β Client | {"event": "rain", "math_results": {"predicted_total_time": ..., "win_probability": ..., "recommendation": "...", "math_baseline_lap": ...}, "radio_call": "Box box box!..."} |
The backend does not expose REST endpoints. All race visualization data is served from web/lib/*.json at build/runtime on the Next.js side.
- Node.js 18+
- Python 3.11+ (conda environment recommended)
libompfor XGBoost on macOS:brew install libomp
cd server
# Install dependencies
pip install -r requirements.txt
# Step 1: Download F1 data (first run needs internet)
python get_f1_data.py
# Step 2: Train the two-stage model
python train_engine.py
# β Generates server/models/ artifacts + data/eval_report.json
# Step 3: Start the server
python main.py
# β Server runs at http://localhost:8000On macOS, if XGBoost fails to load:
export DYLD_LIBRARY_PATH=/opt/homebrew/opt/libomp/lib:$DYLD_LIBRARY_PATH
python train_engine.pycd web
# Install dependencies
npm install
# Start dev server
npm run dev
# β Pit Wall at http://localhost:3000
# β iPad steward console at http://localhost:3000/ipadCreate server/.env:
OPENROUTER_API_KEY=your_key_here
# or
GEMINI_API_KEY=your_key_here- Dark backgrounds (
bg-gray-950) - Monospace fonts for all numbers (
font-mono, JetBrains Mono) - Sharp edges (no
rounded-fullexcept for car dots) - High-contrast neon accents: Cyan (
#22d3ee), Warning Orange (#f97316), Alert Red (#ef4444)
- REST API layer for live data serving (currently static JSON only)
- Learned chaos penalties from historical wet/SC races
- Full safety car / virtual safety car logic tied to backend MC
- Full race replays and custom scenario builder
- Player vs. player strategy battles
- Historical tracks and driver/car performance profiles
Long-term, we want Apex to become a game-like simulation and analytics tool for motorsport fans, students, and anyone who wants to experience what it feels like to run strategy from the pit wall.
| Name | GitHub / Devpost |
|---|---|
| Sanket Deshmukh | @sanket1305 |
| Akhilesh Reddy Mallu | @Akhileshreddym |
| Arnov Kandlikar | @arnovkandlikar |
| Devam Dholakia | @devamdholakia |
Built at Hacklytics 2026: Golden Byte β Georgia Tech's annual data science hackathon.