feat(race-day): start heats with an EFRA/IFMAR count-down and flying start

Race Day Start now runs a count-down (Go, 30 s - 5 min, default per
event) with calls every minute, 30 s and 10 s, grid call and 9..3
count for grid starts, hold/resume/+1 min/to 30 s/to 10 s/Go now, and
starts the heat by itself when it runs out. The transponder check runs
during the count-down, replacing the separate warm-up.

Adds a flying start mode (horn + clock is running, own clock per car)
as the qualifying default, and the latest personal clock start rules
from EFRA 9.3 / IFMAR 4.2.3 for flying and staggered starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-09-30 21:27:20 +02:00
parent 1bc75f8ec1
commit 5bfb67ea02
27 changed files with 884 additions and 334 deletions

View File

@@ -576,13 +576,17 @@ Type:
Owns:
- "Track layout: <label> - since <date> [New layout]" on Training stats, Race Day and the Dashboard; the button prompts for a name and calls the injected `addTrackLayout` (which refuses while a race session is running - `findRaceSessionBlockingLayoutChange` in `track_layouts_logic.js`)
### `src/warmup_logic.js`
### `src/prologue_logic.js`
Type:
- pure warm-up / transponder check logic
- pure heat-start logic per EFRA 9.2-9.3 / IFMAR 4.2: prologue (count-down), transponder check, latest personal clock start
Owns:
- `recordTransponderSighting`: in-memory, bounded map of transponder -> last seen (never persisted, never counted as laps); fed by `decoder_runtime.js`'s `onTransponderSeen` for every main-line passing, before the running-session check
- `buildWarmupCheck`: per-heat entrant rows (seen since the warm-up started or not), unknown transponders with owner names; used by the Race Day warm-up panel (`race_day_view.js`) via `getRaceDayWarmupCheck` in `app.js`, which also owns the warm-up timer and its `warmup_*` voice events
- `buildWarmupCheck`: per-heat entrant rows (seen since the count-down began or not), unknown transponders with owner names; used by the Race Day transponder check panel (`race_day_view.js`) via `getRaceDayWarmupCheck` in `app.js`
- prologue state as a plain object (`createPrologue`, `holdPrologue`, `resumePrologue`, `setPrologueRemaining`, `getPrologueRemainingMs`); `takeDuePrologueCall` decides which voice call is due (every minute, 30 s, 10 s; grid starts add `prologue_grid` at 30 s and `prologue_count` 9..3); `getPrologueHandoverMs` is 3 s for grid/staggered starts (IFMAR 4.2.8) and 0 for a flying start; `PROLOGUE_OPTIONS_SEC` / `normalizePrologueSec` / `formatPrologueOption` for the event's `qualPrologueSec` / `finalPrologueSec`
- `usesPersonalClock` (staggered, flying) and `getLatestClockStartTs`, used by `decoder_runtime.js` so a late car's clock starts at the latest allowed moment and its first crossing counts as a lap
The prologue controller lives in `app.js` (`startRacePrologue`, `tickRacePrologue`, hold/resume/+1 min/to 30 s/to 10 s/Go/Cancel). It is per browser tab (module state plus `sessionStorage`, restored on hold after a reload), so a second browser on the same event never runs its own count-down. When it runs out it calls `startOperationalSession` with `skipCountdown` and `skipIntro`; `runStartSequence` in `race_audio.js` then only runs lights, random delay and horn, or for a flying start the horn plus `clock_running`.
### `src/misc_views.js`
Type: