Skip to content

Commit b29bf1a

Browse files
adn8naiagentclaude
andcommitted
Add sector indicators and track overlay to practice sessions
- Backend: extended sector event computation to FP1/FP2/FP3 - Frontend: sector column and track overlay now show for all non-race sessions - Settings: live sectors toggle available in practice (was qualifying only) - Requires recompute for practice sessions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dec8d47 commit b29bf1a

5 files changed

Lines changed: 13 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to F1 Replay Timing will be documented in this file.
55
## 1.3.2
66

77
### Improvements
8+
- **Practice sector indicators** — live sector colours and track map sector overlay now available in practice sessions. Requires recompute
89
- **Last lap time for all sessions** — now available in practice and qualifying. Requires recompute
910
- **Last lap colour coding** — purple for fastest lap, green for personal best
1011
- **Processing feedback** — real-time status messages during on-demand processing

backend/services/f1_data.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -879,12 +879,12 @@ def _get_driver_flag(abbr: str, frame_time: float) -> str | None:
879879
driver_best_lap_events[drv] = events
880880
driver_lap_completions[drv] = completions
881881

882-
# For qualifying sessions: build sector completion events per driver
882+
# For qualifying and practice sessions: build sector completion events per driver
883883
# Each entry: (session_time, sector_num, sector_time_seconds, lap_number, is_out_lap)
884884
# Also pre-compute which laps are out laps (lap 1 or first lap after pit exit)
885885
driver_sector_events: dict[str, list[tuple[float, int, float, int, bool]]] = {}
886886
driver_out_laps: dict[str, set[int]] = {}
887-
if session_type in ("Q", "SQ"):
887+
if session_type in ("Q", "SQ", "FP1", "FP2", "FP3"):
888888
for drv in drivers_list:
889889
drv_laps_df = laps.pick_drivers(drv).sort_values("LapNumber")
890890
sector_events = []
@@ -1474,8 +1474,8 @@ def _safe_float(v) -> float:
14741474
d["gap"] = "No time"
14751475
d["no_timing"] = False
14761476

1477-
# Add live sector indicators for qualifying
1478-
if session_type in ("Q", "SQ"):
1477+
# Add live sector indicators for qualifying and practice
1478+
if session_type in ("Q", "SQ", "FP1", "FP2", "FP3"):
14791479
# Track overall best and personal best sector times up to now
14801480
overall_best_sectors: dict[int, float] = {} # sector_num -> best time
14811481
personal_best_sectors: dict[str, dict[int, float]] = {} # driver -> sector_num -> best time

frontend/src/app/replay/[year]/[round]/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ export default function ReplayPage() {
249249
const isRace = sessionType === "R" || sessionType === "S";
250250
const isQualifying = sessionType === "Q" || sessionType === "SQ";
251251
const isPractice = sessionType === "FP1" || sessionType === "FP2" || sessionType === "FP3";
252+
const hasSectors = isQualifying || isPractice;
252253

253254
// For practice sessions, cap the total time at the official session duration (60 min)
254255
// so the "remaining" timer is accurate rather than including post-session telemetry
@@ -259,7 +260,7 @@ export default function ReplayPage() {
259260
const SECTOR_HEX: Record<string, string> = { purple: "#A855F7", green: "#22C55E", yellow: "#EAB308" };
260261
const DEFAULT_SECTOR = "#3A3A4A";
261262
const sectorOverlay: SectorOverlay | null = (() => {
262-
if (!isQualifying || !showSectorOverlay || !trackData?.sector_boundaries) return null;
263+
if (!hasSectors || !showSectorOverlay || !trackData?.sector_boundaries) return null;
263264
const target = sectorFocusDriver && selectedDrivers.includes(sectorFocusDriver)
264265
? sectorFocusDriver
265266
: null;
@@ -285,7 +286,7 @@ export default function ReplayPage() {
285286
if (!isRace && settings.showBestLapTime) w += 60; // best lap time column
286287
if (settings.showLastLapTime) w += 60; // last lap time column
287288
if (settings.showGapToLeader) w += 56 + (!isRace ? 8 : 0); // extra margin between lap time and gap in practice/qualifying
288-
if (isQualifying && settings.showSectors) w += 36; // sector indicators (28 + 8 margin)
289+
if (hasSectors && settings.showSectors) w += 36; // sector indicators (28 + 8 margin)
289290
if (isRace && settings.showPitStops) w += 24;
290291
if (isRace && settings.showTyreHistory) w += 36;
291292
if (settings.showTyreType) w += 24;
@@ -535,7 +536,7 @@ export default function ReplayPage() {
535536
{/* Telemetry now in bottom drawer */}
536537

537538
{/* Sector overlay toggle - desktop qualifying only */}
538-
{!isMobile && isQualifying && trackData?.sector_boundaries && (
539+
{!isMobile && hasSectors && trackData?.sector_boundaries && (
539540
<div className="absolute bottom-2 right-36 z-20 flex items-center gap-1">
540541
{showSectorOverlay && selectedDrivers.length === 0 && (
541542
<span className="text-[10px] text-f1-muted mr-1">Select a driver to view sectors</span>
@@ -574,7 +575,7 @@ export default function ReplayPage() {
574575
)}
575576

576577
{/* Sector overlay controls - mobile qualifying only */}
577-
{isMobile && isQualifying && trackData?.sector_boundaries && (
578+
{isMobile && hasSectors && trackData?.sector_boundaries && (
578579
<div className="absolute bottom-2 left-2 right-2 z-20 flex items-center gap-1">
579580
{showSectorOverlay && selectedDrivers.length > 0 && (
580581
<div className="flex items-center gap-1 overflow-x-auto">

frontend/src/components/Leaderboard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,8 @@ export default function Leaderboard({ drivers, highlightedDrivers, onDriverClick
364364
);
365365
})()}
366366

367-
{/* Live sector indicators - fixed width (qualifying only) */}
368-
{isQualifying && settings.showSectors && (
367+
{/* Live sector indicators - fixed width (qualifying and practice) */}
368+
{!isRace && settings.showSectors && (
369369
<span className="w-7 flex-shrink-0 flex items-center justify-center gap-[2px] mx-1">
370370
{[1, 2, 3].map((sn) => {
371371
const sec = drv.sectors?.find((s) => s.num === sn);

frontend/src/components/SessionBanner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const LEADERBOARD_SETTINGS: { key: keyof ReplaySettings; label: string; raceOnly
3838
{ key: "showTyreType", label: "Tyre type" },
3939
{ key: "showTyreAge", label: "Tyre age" },
4040
{ key: "showTyreHistory", label: "Tyre history", raceOnly: true },
41-
{ key: "showSectors", label: "Live sectors", qualiOnly: true },
41+
{ key: "showSectors", label: "Live sectors", nonRaceOnly: true },
4242
{ key: "showPitPrediction", label: "Pit prediction", raceOnly: true },
4343
{ key: "showPitConfidence", label: "Confidence", raceOnly: true, parent: "showPitPrediction" },
4444
{ key: "showPitFreeAir", label: "Pit gaps", raceOnly: true, parent: "showPitPrediction" },

0 commit comments

Comments
 (0)