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

@@ -573,6 +573,47 @@ Asked by the user 2026-09-30: what happens if a driver in a running heat registe
Verified 2026-09-30: unit tests (`tests/warmup_logic.test.js`) cover seen-since-start, a passing before the warm-up not counting, drivers without a transponder, unknown transponders newest-first with owner names, repeat counting and the size bound, and the countdown. `tests/browser_warmup.js` drives it with real decoder frames: Anna and Bo go green, Cia (registered wrong) stays missing until the unknown transponder is linked from the panel - then green and saved - preflight shows 3/3, and after a restarted warm-up Start warns naming the two not seen and starts the heat on confirm. While writing that test it first failed because a passing sent "before" the warm-up reached the page after the click and was (correctly) counted - a test timing issue, fixed by waiting for the frame to arrive. After that fix the same assertion still failed rarely (twice in about ten full-suite runs). Investigated with temporary logging: every run had exactly one decoder connection and the passing was processed before the start (`no_running_session` in the diagnostics), so it is not a duplicate-connection bug - but the flake could not be reproduced while logging (4/4 passed), so the root cause is unknown. The browser test now waits 750 ms between that passing and the start; the before/after rule itself is covered deterministically by the unit tests. 230 unit/integration tests pass, and the 46 browser scenarios passed three runs in a row.
### Follow-up: the warm-up becomes the start count-down (EFRA / IFMAR)
The user's feedback on the same day: the warm-up should not be a separate part. It belongs in the heat start itself, as "heat starts in 2 min, 1 min". It must be possible to hold the start so the cars can be put on the grid. Qualifying uses a flying start, where each driver's 5 minutes start at their own crossing. Lengths should be 30 s, 1, 1:30 and 2 min. The goal is to follow EFRA and IFMAR. Checked first:
- EVERLAPS (formerly RCM), which does exactly this: a "prologue" after Start, with calls every minute and at 30/20/10 s, and green ticks for drivers who have crossed. It also offers 30 s / 10 s shortcuts and Go.
- EFRA General Rules 2024 §9.2-9.3.
- IFMAR 1:10 IC Track 2024 §4.2.
- [x] `src/warmup_logic.js` became `src/prologue_logic.js`. It adds the prologue state (hold/resume/set remaining) and the call schedule:
- EFRA 9.2b for a flying start: 1 min, 30 s, 10 s; with a longer count-down, every whole minute ("N minutes to the start").
- IFMAR 4.2.4-4.2.6 for grid and staggered starts: "30 seconds, cars to the grid", then 9..3 second by second.
- The start procedure takes over at 3 s (IFMAR 4.2.8: the signal comes 0-5 s later, which is our random delay). A flying start takes over at 0.
- [x] Race Day's Start on a race-format heat now starts the count-down. Choices are Go, 30 s, 1, 1:30, 2, 3 and 5 min. The default is set per event (`qualPrologueSec` / `finalPrologueSec`, 2 min) and comes from new fields in Race Setup.
- While counting down: Hold / Resume, +1 min, To 30 s (IFMAR 4.2.8 grid restart from 30 s), To 10 s, Go now (warns about drivers not seen) and Cancel.
- The hero clock shows the time to the start.
- When the count-down runs out the heat starts by itself, with no dialog.
- The global 10/30/60 s start count-down in Settings is skipped on Race Day. It still applies to Start on the Timing page.
- [x] The count-down lives only in the browser tab that started it (module state plus `sessionStorage`). A second browser on the same event therefore never runs its own count-down and doubles the calls. A reload restores it on hold, so a start is never fired by a reload.
- [x] `runStartSequence` takes `skipCountdown` and `skipIntro`:
- "Go" still says "drivers, stand by" and runs the lights, random delay and horn.
- After a full count-down only the lights, random delay and horn are left.
- [x] New start mode `flying` (EFRA 9.2b): no start procedure, just the horn and "Clock is running" (new voice event `clock_running`). Each car gets its own clock from its first crossing, like staggered.
- It is the default for qualifying in new events and in the three built-in presets. Existing events keep the mode they already have saved.
- The shared session clock gets one extra minute, so the last car to start still has its full window.
- [x] Latest personal clock start (EFRA 9.3, IFMAR 4.2.3) in `decoder_runtime.js`. A car first seen after the latest start gets that moment as its start, and its first crossing counts as a lap.
- Flying start: 150 % of a standard lap after the horn. We take the first lap anyone completes in the heat as the standard lap, since there is no configured standard lap time.
- Staggered start: when the first car completes a lap, or 1 minute after the start, whichever comes first.
- [x] Voice events `warmup_*` were replaced by `prologue_start`, `prologue_minutes`, `prologue_grid`, `prologue_count`, `prologue_hold`, `prologue_resumed`, `prologue_cancelled` and `clock_running`, in sv and en. The existing `race_1_minute`, `race_30_seconds` and `race_10_seconds` are reused.
Verified 2026-09-30:
- 239 unit/integration tests pass. They include new tests for the call schedule per start mode, hold/resume, jumping to 10 s (skipped calls marked done, only the latest one spoken, calls come back after +1 min), length choices, the latest clock start rules, a flying start in the decoder (own clock, and a late car getting the latest start with a counted lap), and the audio sequence for flying and post-count-down grid starts.
- `tests/browser_prologue.js` drives real decoder frames through the whole flow:
- the default length comes from the event, and a passing before the count-down doesn't count;
- Anna goes green and Cia is fixed by linking her unknown transponder;
- hold keeps the clock still for 1.5 s, and it moves again after resume; +1 min works;
- "Go now" warns about Bo only and starts on confirm;
- a second heat started with 30 s and jumped to 10 s starts by itself with no dialog.
- `tests/browser_race_day_start_sequences.js` was updated to the new flow: the heat counts down and never gets a second count-down, and the other types use Go.
- The 48 browser scenarios passed three runs in a row.
Not done: a configurable standard lap time per event (EVERLAPS has one). Until then, the first completed lap stands in for it.
## Documentation maintenance
- [x] Update the architecture's module/dependency inventory as modules change.