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:
@@ -12,7 +12,7 @@ function createHarness(options = {}) {
|
||||
mode: "race",
|
||||
type: options.sessionType || "team_race",
|
||||
status: "running",
|
||||
startMode: "mass",
|
||||
startMode: options.startMode || "mass",
|
||||
startedAt: 1_000,
|
||||
};
|
||||
const state = {
|
||||
@@ -36,7 +36,7 @@ function createHarness(options = {}) {
|
||||
t: (key) => key,
|
||||
getActiveSession: () => session,
|
||||
ensureSessionResult: (sessionId) => ensureSessionResult(sessionId, { state }),
|
||||
normalizeStartMode: () => "mass",
|
||||
normalizeStartMode: () => options.startMode || "mass",
|
||||
getSessionLapWindow: () => ({
|
||||
minLapMs: Number(options.minLapMs || 0),
|
||||
maxLapMs: options.maxLapMs == null ? Number.POSITIVE_INFINITY : Number(options.maxLapMs),
|
||||
@@ -112,6 +112,30 @@ test("maximum lap time starts a statistics stint but still counts the race lap",
|
||||
assert.equal(result.passings[0].stintBoundary, true);
|
||||
});
|
||||
|
||||
test("flying start: each car's clock starts at its own first crossing, a late car's at the latest 150 % of a lap (EFRA 9.3)", () => {
|
||||
const harness = createHarness({ sessionType: "qualification", startMode: "flying" });
|
||||
const byDriver = (driverId) => Object.values(harness.state.resultsBySession.session_1.competitors).find((entry) => entry.driverId === driverId);
|
||||
harness.passing(4_000, "main", "123"); // Driver One's clock starts - not a lap
|
||||
assert.equal(byDriver("driver_1").startTimestamp, 4_000);
|
||||
assert.equal(byDriver("driver_1").laps, 0);
|
||||
harness.passing(24_000, "main", "123"); // first lap anyone completes: 20 s -> latest start 1 000 + 30 000
|
||||
assert.equal(byDriver("driver_1").laps, 1);
|
||||
harness.passing(29_000, "main", "456"); // Driver Two just in time: own clock
|
||||
assert.equal(byDriver("driver_2").startTimestamp, 29_000);
|
||||
assert.equal(byDriver("driver_2").laps, 0);
|
||||
});
|
||||
|
||||
test("flying start: a car first seen after the latest clock start gets that clock and its crossing counts as a lap", () => {
|
||||
const harness = createHarness({ sessionType: "qualification", startMode: "flying" });
|
||||
const byDriver = (driverId) => Object.values(harness.state.resultsBySession.session_1.competitors).find((entry) => entry.driverId === driverId);
|
||||
harness.passing(4_000, "main", "123");
|
||||
harness.passing(24_000, "main", "123"); // latest start = 31 000
|
||||
harness.passing(45_000, "main", "456");
|
||||
assert.equal(byDriver("driver_2").startTimestamp, 31_000);
|
||||
assert.equal(byDriver("driver_2").laps, 1);
|
||||
assert.equal(byDriver("driver_2").lastLapMs, 14_000);
|
||||
});
|
||||
|
||||
test("pit entry and exit create one timed stop without adding laps", () => {
|
||||
const harness = createHarness();
|
||||
harness.passing(17_000, "main");
|
||||
|
||||
Reference in New Issue
Block a user