- Track Type: 200m Banked Indoor
- Race Distance: 800m
- Laps: 4 (200m Γ 4 = 800m)
- Checkpoints: 0m (start), 200m, 400m, 600m, 800m (finish)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMMENTARY CHECKPOINT SYSTEM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β CHECKPOINT QUEUE (One per runner Γ 5 checkpoints = max 55 events) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β RUNNER: Melodi (id=1) - Splits: [0, 32.94, 68.67, 105.05, 141.58]β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Distance β Time β Status β Audio Clips β β
β ββββββββββββΌββββββββββββΌβββββββββΌββββββββββββββββββββββββββββββββββββββ β
β β 0m β 0.00s β β β [00] Intro, [01] "Runners set..." β β
β β 200m β 32.94s β β β [04] "32.9 for Melodi at 200m" β β
β β 400m β 68.67s β β β [07] "1:08 at the bell" β β
β β 600m β 105.05s β β β [11] "1:45 through 600m" β β
β β 800m β 141.58s β β β [15] "2:21.58 for Melodi" β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β RUNNER: Skye (id=8) - Splits: [0, 41.00, 88.55, 136.83, 184.27]β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Distance β Time β Status β Audio Clips β β
β ββββββββββββΌββββββββββββΌβββββββββΌββββββββββββββββββββββββββββββββββββββ β
β β 0m β 0.00s β β β (no start clip - shared) β β
β β 200m β 41.00s β β β [05] "41 flat for Skye" β β
β β 400m β 88.55s β β β [09] "1:28 for Skye" β β
β β 600m β 136.83s β β β [13] "2:16 through 600m" β β
β β 800m β 184.27s β β β [18] "3:04.27 PR for Skye" β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β RUNNER: Margeaux (id=9) - Splits: [0, 33.18, 70.18, 109.24, 190.55]β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Distance β Time β Status β Audio Clips β β
β ββββββββββββΌββββββββββββΌβββββββββΌββββββββββββββββββββββββββββββββββββββ β
β β 0m β 0.00s β β β (no start clip) β β
β β 200m β 33.18s β β β (no clip) β β
β β 400m β 70.18s β β β (no clip) β β
β β 600m β 109.24s β β β (no clip) β β
β β 800m β 190.55s β β β [20] "Margeaux crosses in 3:10" β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
### Global Commentary Queue (Non-Runner-Specific)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β GLOBAL EVENTS β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β PRE-RACE (Time-based, triggers at raceTime = -46s) β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β [00] "Welcome to the 800m championship heat..." β β β β Trigger: raceTime >= -46 β β β β Duration: ~18s β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β FIELD SEPARATION (Distance-based, triggers at ~130m when gap forms) β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β [03] "Field stringing out..." β β β β Trigger: leader distance >= 130m β β β β Subject: None (general observation) β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β MIDDLE PACK ANALYSIS (Distance-based, triggers at ~250m) β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β [06] "Middle pack feeling the gap..." β β β β Trigger: leader distance >= 250m β β β β Subject: Parvati (id=2) - mentioned in audio β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β RACE SUMMARY (Time-based, triggers at raceTime = 198s) β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β [21] "What a race... two different strategies" β β β β Trigger: raceTime >= 198 β β β β Subject: None β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
### Audio-Visual Synchronization Logic
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRIGGER EVALUATION PER FRAME β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β EACH ANIMATION FRAME (60fps): β
β β
β 1. UPDATE raceTime β
β raceTime += (1/60) * speed; β
β β
β 2. UPDATE all runner positions β
β For each runner: β
β dist = interpolateDistance(runner.splits, raceTime) β
β (x, y) = getTrackCoordinates(dist) β
β β
β 3. EVALUATE COMMENTARY QUEUE β
β β
β A. Check PRE-RACE queue (time-based) β
β if raceTime >= -46 and intro not played: β
β playAudio([00]) β
β β
β B. Check RUNNER-SPECIFIC queues (distance-based) β
β For each runner with pending audio: β
β checkpoint = nextPendingCheckpoint(runner) β
β actualDist = getDistanceAtTime(runner.splits, raceTime) β
β β
β if actualDist >= checkpoint.distance: β
β // RUNNER HAS CROSSED THE CHECKPOINT! β
β if not isAudioPlaying: β
β playAudio(checkpoint.audioClip) β
β checkpoint.status = "PLAYED" β
β focusIndicator.target = runner.id β
β β
β C. Check GLOBAL events (mixed triggers) β
β For each global event: β
β if event.triggerType == "time" and raceTime >= event.triggerValue β
β OR β
β if event.triggerType == "distance" and leaderDist >= event.trigger β
β if not isAudioPlaying: β
β playAudio(event.audioClip) β
β β
β 4. UPDATE VISUAL INDICATOR β
β if currentFocusRunnerId and isAudioPlaying: β
β target = document.getElementById(runner-${currentFocusRunnerId}) β
β focusIndicator.style.top = target.style.top β
β focusIndicator.style.left = target.style.left β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
### Checkpoint Data Structure (JavaScript)
```javascript
// Runner-specific checkpoint queue
const runnerCheckpoints = {
1: { // Melodi
checkpoints: [
{ distance: 0, time: 0, audio: [1], played: false }, // Start
{ distance: 200, time: 32.94, audio: [4], played: false }, // 200m
{ distance: 400, time: 68.67, audio: [7, 10], played: false }, // 400m (bell + 600m approaching)
{ distance: 600, time: 105.05, audio: [11], played: false }, // 600m
{ distance: 800, time: 141.58, audio: [14, 15], played: false } // Finish
]
},
8: { // Skye
checkpoints: [
{ distance: 0, time: 0, audio: [], played: false }, // Start (no specific audio)
{ distance: 200, time: 41.00, audio: [5], played: false }, // 200m
{ distance: 400, time: 88.55, audio: [9], played: false }, // 400m
{ distance: 600, time: 136.83, audio: [12, 16], played: false }, // 600m
{ distance: 800, time: 184.27, audio: [18], played: false } // Finish
]
},
9: { // Margeaux
checkpoints: [
{ distance: 800, time: 190.55, audio: [19], played: false } // Finish only
]
}
};
// Global events queue
const globalEvents = [
{ type: "time", trigger: -46, audio: [0], played: false }, // Pre-race intro
{ type: "distance", trigger: 130, audio: [3], played: false }, // Field separation
{ type: "distance", trigger: 250, audio: [6], played: false }, // Middle pack
{ type: "distance", trigger: 450, audio: [8], played: false }, // Field strung out
{ type: "time", trigger: 198, audio: [20], played: false } // Race summary
];
ββββββββ¬ββββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ
β Clip β Checkpoint β Runner β Transcript β
ββββββββΌββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€
β [00] β Pre-race β None β "Welcome to the 800m championship..." β
β [01] β 0m β All (start) β "Runners set. Clean start..." β
β [02] β 100m β Melodi β "Melodi out fast through the first 100" β
β [03] β 130m β Leader (general) β "Field stringing out..." β
β [04] β 200m β Melodi β "32.9 for Melodi at the 200" β
β [05] β 200m β Skye β "41 flat for Skye at the 200" β
β [06] β 250m β Leader (general) β "Middle pack feeling the gap..." β
β [07] β 380m β Melodi β "400m. Melodi commanding at 1:08" β
β [08] β 450m β Leader (general) β "Look at the separation..." β
β [09] β 400m β Skye β "1:28 for Skye... negative split pacing" β
β [10] β 580m β Leader (general) β "600m mark. This is where it's decided" β
β [11] β 600m β Melodi β "1:45 for Melodi through 600m" β
β [12] β 600m β Skye β "2:16 for Skye... running people down" β
β [13] β 700m β Leader (general) β "100m to go for Melodi" β
β [14] β 720m β Skye β "Skye flying now... passing people" β
β [15] β 800m β Melodi β "2:21.58 for Melodi... outstanding" β
β [16] β 750m β Skye β "Watch Skye coming home... 47s final lap" β
β [17] β 750m β Skye β "50m to go for Skye" β
β [18] β 800m β Skye β "3:04.27 PR for Skye" β
β [19] β 800m β Margeaux β "Margeaux crosses in 3:10" β
β [20] β 198s β None (summary) β "What a race... two different strategies" β
ββββββββ΄ββββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ
-
Audio Overlap Prevention: Only ONE audio clip plays at a time. If a checkpoint is crossed while another clip is playing, it queues (or cancels if too late).
-
Visual Indicator: The yellow ring follows the runner mentioned in the CURRENTLY PLAYING audio clip, not just any runner crossing a checkpoint.
-
Reset Behavior: All
played: falseflags reset to allow replay. -
Multiple Clips at Same Checkpoint: Some checkpoints have multiple clips (e.g., Melodi at 400m has clips [7] and [10]). They play sequentially.
-
Distance Calculation: Uses linear interpolation between known split times:
distance = checkpoint * 200 + (timeInSegment / segmentDuration * 200)