Add configurable manifest-driven race audio system

This commit is contained in:
larssand
2026-08-28 13:29:27 +02:00
parent 3f3b410869
commit 5f79228941
22 changed files with 1133 additions and 109 deletions

View File

@@ -251,7 +251,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
state, t, getActiveSession, ensureSessionResult, normalizeStartMode, getSessionLapWindow,
findEventTeamForPassing, persistPassingToBackend, pushOverlayEvent, buildLeaderboard,
formatLap, announcePassing, saveState, lastOverlayLeaderKeyBySession,
lastOverlayTop3BySession, lastOverlayBestLapByKey,
lastOverlayTop3BySession, lastOverlayBestLapByKey, triggerRaceAudio = () => {}, getTrackRecordMs = () => null,
} = deps;
if (!msg || typeof msg !== "object") {
@@ -277,6 +277,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
const result = ensureSessionResult(session.id);
const competitor = resolveCompetitor(session, transponder, { state, t, findEventTeamForPassing });
if (competitor.ignore) {
triggerRaceAudio("unknown_transponder", { variables: { transponder }, scope: transponder, logData: { raceId: session.eventId, sessionId: session.id } });
return;
}
const key = competitor.key;
@@ -327,6 +328,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
if (!Array.isArray(result.pitEvents)) result.pitEvents = [];
result.pitEvents.push({ timestamp, type: "entry", transponder, competitorKey: key, driverId: entry.driverId, carId: entry.carId });
pushOverlayEvent("pit", `${entry.displayName || entry.driverName} • PIT IN`);
triggerRaceAudio("entering_pits", { variant: entry.carId || entry.driverId || transponder, driverId: entry.driverId, logData: { raceId: session.eventId, driverId: entry.driverId } });
saveState();
return;
}
@@ -356,6 +358,7 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
entry.pitEntryCarName = "";
entry.pitEntryTransponder = "";
pushOverlayEvent("pit", `${entry.displayName || entry.driverName} • PIT ${formatLap(pitStopMs)}`);
triggerRaceAudio("leaving_pits", { variant: entry.carId || entry.driverId || transponder, driverId: entry.driverId, logData: { raceId: session.eventId, driverId: entry.driverId } });
saveState();
return;
}
@@ -448,6 +451,13 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
return;
}
const previousPersonalBest = Number(entry.bestLapMs || 0) || null;
const leaderboardBefore = buildLeaderboard(session);
const previousPosition = leaderboardBefore.findIndex((row) => row.key === key);
const previousLeaderKey = leaderboardBefore[0]?.key || null;
const previousSessionFastest = leaderboardBefore.map((row) => Number(row.bestLapMs || 0)).filter((value) => value > 500).sort((a, b) => a - b)[0] || null;
const previousTrackRecord = Number(getTrackRecordMs(session) || 0) || null;
entry.laps += 1;
entry.lastLapMs = lapMs;
entry.lastTimestamp = timestamp;
@@ -460,10 +470,18 @@ export function processDecoderMessageHelper(msg, deps, decoderRole = "main") {
persistPassingToBackend(session.id, passing);
pushOverlayEvent("passing", `${entry.displayName || entry.driverName} • ${formatLap(entry.lastLapMs)}`);
const leaderboard = buildLeaderboard(session);
const currentPosition = leaderboard.findIndex((row) => row.key === key);
const audioVariables = { driverName: entry.speechName || entry.displayName || entry.driverName, lapTime: formatLap(lapMs) };
if (previousPersonalBest && lapMs < previousPersonalBest) triggerRaceAudio("personal_best", { variables: audioVariables, driverId: entry.driverId, competitorKey: key, logData: { raceId: session.eventId, driverId: entry.driverId, lapNumber: entry.laps, lapTime: lapMs } });
if (previousSessionFastest && lapMs < previousSessionFastest) triggerRaceAudio("fastest_lap", { variables: audioVariables, driverId: entry.driverId, competitorKey: key, logData: { raceId: session.eventId, driverId: entry.driverId, lapNumber: entry.laps, lapTime: lapMs } });
if (previousTrackRecord && lapMs < previousTrackRecord) triggerRaceAudio("track_record", { variables: audioVariables, driverId: entry.driverId, competitorKey: key, logData: { raceId: session.eventId, driverId: entry.driverId, lapNumber: entry.laps, lapTime: lapMs } });
if (previousPosition >= 0 && currentPosition >= 0 && currentPosition < previousPosition) triggerRaceAudio("position_gain", { driverId: entry.driverId, competitorKey: key });
if (previousPosition >= 0 && currentPosition > previousPosition) triggerRaceAudio("position_loss", { driverId: entry.driverId, competitorKey: key });
const leader = leaderboard[0];
if (leader?.key && lastOverlayLeaderKeyBySession[session.id] !== leader.key) {
lastOverlayLeaderKeyBySession[session.id] = leader.key;
pushOverlayEvent("leader", `${leader.displayName || leader.driverName} • P1`);
if (previousLeaderKey && previousLeaderKey !== leader.key) triggerRaceAudio("new_leader", { variables: { driverName: leader.displayName || leader.driverName }, driverId: leader.driverId, competitorKey: leader.key, logData: { raceId: session.eventId, driverId: leader.driverId } });
}
if (entry.bestLapMs && Number.isFinite(entry.bestLapMs)) {
const bestKey = `${session.id}:${entry.key}`;