feat(series): run each round from the series page

Step 3 becomes a rounds table: pick drivers inline (copy from the
previous round, add a late entry), set qualifying rounds/finals/max
cars per heat per round, see the status, and create qualifying and
finals from there. Qualifying heats are now split evenly so nobody
drives alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-10-11 18:55:36 +02:00
parent 581ec2c781
commit b5707f92d1
17 changed files with 447 additions and 39 deletions

View File

@@ -145,7 +145,8 @@ test("practice uses its layout's min/max lap time where set; races always keep t
assert.deepEqual(getSessionLapWindow(practice("layout_3"), { state }), { minLapMs: 15000, maxLapMs: 60000 }, "only min set -> the event's max stays");
assert.deepEqual(getSessionLapWindow({ eventId: "event_1", type: "heat", trackLayoutId: "layout_2" }, { state }), { minLapMs: 12000, maxLapMs: 60000 }, "a heat ignores the layout");
assert.deepEqual(getLayoutLapWindowOverride(state.trackLayouts, { type: "free_practice", trackLayoutId: "layout_2" }), { minLapMs: 8000, maxLapMs: 40000 });
assert.deepEqual(normalizeTrackLayout({ id: "x", minLapMs: -5, maxLapMs: "40000" }), { ...normalizeTrackLayout({ id: "x" }), minLapMs: 0, maxLapMs: 40000, startedAt: normalizeTrackLayout({ id: "x", minLapMs: -5, maxLapMs: "40000" }).startedAt });
// Fixed startedAt: without one each call takes Date.now(), which made this flaky.
assert.deepEqual(normalizeTrackLayout({ id: "x", startedAt: 1000, minLapMs: -5, maxLapMs: "40000" }), { id: "x", label: "", startedAt: 1000, minLapMs: 0, maxLapMs: 40000 });
});
test("Race Day preflight shows the layout's lap window for practice", () => {