fix(lap): keep best laps per track layout and count the current session for records
A layout switch during a running open practice kept every driver's old-layout best lap, blocking personal-best calls and skewing the practice leaderboard. Best laps now restart on a new layout and each lap records its layout. The track record baseline now includes the current session so the same record isn't called twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
10
src/app.js
10
src/app.js
@@ -5046,9 +5046,17 @@ function addTrackLayout(label, startedAtMs = Date.now()) {
|
||||
state.trackLayouts = normalizeTrackLayouts(layouts, { uid });
|
||||
// A practice session that keeps running across the switch moves onto the new layout;
|
||||
// its earlier laps stay on the old one because training laps are tagged per lap.
|
||||
// Its best laps start over too: personal bests, session fastest and the leaderboard's best
|
||||
// lap are per layout, so the old layout's times can't block or fake a new best.
|
||||
if (startedAtMs <= Date.now()) {
|
||||
state.sessions.forEach((session) => {
|
||||
if (isPracticeSessionType(session.type) && ["running", "paused"].includes(session.status)) session.trackLayoutId = layout.id;
|
||||
if (!isPracticeSessionType(session.type) || !["running", "paused"].includes(session.status)) return;
|
||||
session.trackLayoutId = layout.id;
|
||||
Object.values(state.resultsBySession?.[session.id]?.competitors || {}).forEach((entry) => {
|
||||
entry.bestLapMs = null;
|
||||
entry.bestLapLayoutId = layout.id;
|
||||
delete lastOverlayBestLapByKey[`${session.id}:${entry.key}`];
|
||||
});
|
||||
});
|
||||
}
|
||||
saveState();
|
||||
|
||||
@@ -337,6 +337,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
|
||||
laps: 0,
|
||||
lastLapMs: null,
|
||||
bestLapMs: null,
|
||||
bestLapLayoutId: String(session.trackLayoutId || ""),
|
||||
startTimestamp: null,
|
||||
lastTimestamp: null,
|
||||
};
|
||||
@@ -494,6 +495,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
|
||||
stintBoundary: validationWarning === "above_max_stint",
|
||||
strength: msg.strength,
|
||||
loopId: String(msg.loop_id || ""),
|
||||
trackLayoutId: String(session.trackLayoutId || ""),
|
||||
resend: Boolean(msg.resend),
|
||||
};
|
||||
|
||||
@@ -505,6 +507,12 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Best laps are per track layout: an open practice that runs all season moves onto a new
|
||||
// layout when one is added (addTrackLayout resets every best lap then), and this catches a
|
||||
// layout change made elsewhere. Entries from before this field existed adopt the current one.
|
||||
const layoutId = String(session.trackLayoutId || "");
|
||||
if (entry.bestLapLayoutId === undefined) entry.bestLapLayoutId = layoutId;
|
||||
else if (entry.bestLapLayoutId !== layoutId) { entry.bestLapMs = null; entry.bestLapLayoutId = layoutId; }
|
||||
const previousPersonalBest = Number(entry.bestLapMs || 0) || null;
|
||||
const leaderboardBefore = buildLeaderboard(session);
|
||||
const previousPosition = leaderboardBefore.findIndex((row) => row.key === key);
|
||||
|
||||
@@ -83,7 +83,12 @@ export function recalculateCompetitorFromPassings(session, rowKey, { ensureSessi
|
||||
entry.lastTimestamp = validPassings.length
|
||||
? Number(validPassings[validPassings.length - 1].timestamp || 0) || entry.startTimestamp || session.startedAt || null
|
||||
: entry.startTimestamp || session.startedAt || null;
|
||||
const bestLapCandidates = validPassings.map((passing) => Number(passing.lapMs || 0)).filter((lapMs) => lapMs > 500);
|
||||
// Only laps on the session's current track layout count for the best lap (passings from
|
||||
// before layouts were tagged have no trackLayoutId and still count).
|
||||
const layoutId = String(session.trackLayoutId || "");
|
||||
const bestLapCandidates = validPassings
|
||||
.filter((passing) => passing.trackLayoutId === undefined || String(passing.trackLayoutId) === layoutId)
|
||||
.map((passing) => Number(passing.lapMs || 0)).filter((lapMs) => lapMs > 500);
|
||||
entry.bestLapMs = bestLapCandidates.length ? Math.min(...bestLapCandidates) : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,9 +165,10 @@ export function ensureSessionResult(sessionId, { state }) {
|
||||
export function getTrackRecordMs(session, { state }) {
|
||||
const event = state.events.find((item) => item.id === session?.eventId);
|
||||
if (!event?.classId) return null;
|
||||
// The current session counts too: once someone beats the old record in this session, the
|
||||
// next driver must beat that new time - not the old record again - to be called a record.
|
||||
const candidates = state.sessions.filter((item) =>
|
||||
item.id !== session?.id
|
||||
&& String(item.trackLayoutId || "") === String(session?.trackLayoutId || "")
|
||||
String(item.trackLayoutId || "") === String(session?.trackLayoutId || "")
|
||||
&& state.events.find((race) => race.id === item.eventId)?.classId === event.classId);
|
||||
const values = candidates
|
||||
.flatMap((item) => Object.values(state.resultsBySession[item.id]?.competitors || {}).map((row) => Number(row.bestLapMs || 0)))
|
||||
|
||||
Reference in New Issue
Block a user