Improve training statistics, achievements and stable pace audio
This commit is contained in:
@@ -60,6 +60,18 @@ function createHarness(options = {}) {
|
||||
return { state, session, passing, overlayEvents, audioEvents, getSaveCount: () => saveCount };
|
||||
}
|
||||
|
||||
test("five controlled practice laps trigger one stable-pace voice cue", () => {
|
||||
const harness = createHarness({ sessionType: "open_practice" });
|
||||
for (let lap = 1; lap <= 5; lap += 1) harness.passing(1_000 + lap * 20_000, "main");
|
||||
assert.equal(harness.audioEvents.filter((event) => event === "training_stable_pace").length, 1);
|
||||
});
|
||||
|
||||
test("stable-pace training cue is never emitted during a race", () => {
|
||||
const harness = createHarness({ sessionType: "team_race" });
|
||||
for (let lap = 1; lap <= 5; lap += 1) harness.passing(1_000 + lap * 20_000, "main");
|
||||
assert.equal(harness.audioEvents.includes("training_stable_pace"), false);
|
||||
});
|
||||
|
||||
test("split decoder records intermediate time without changing main lap state", () => {
|
||||
const harness = createHarness();
|
||||
assert.equal(harness.passing(17_000, "main"), true);
|
||||
|
||||
@@ -105,6 +105,7 @@ test("public profile contains only the selected driver's sanitized aggregates",
|
||||
splitCount: 2,
|
||||
bestSplitMs: 7000,
|
||||
averageSplitMs: 7100,
|
||||
latestSplitGainMs: 200,
|
||||
pitStopCount: 1,
|
||||
totalPitMs: 12500,
|
||||
bestPitStopMs: 12500,
|
||||
|
||||
@@ -88,7 +88,7 @@ test("event catalog covers every supported race-audio event", () => {
|
||||
const keys = new Set(RACE_AUDIO_EVENT_DEFINITIONS.map((item) => item.key));
|
||||
[
|
||||
"grid_open", "grid_closed", "race_1_minute", "race_30_seconds", "race_10_seconds", "drivers_ready", "start_sequence", "start_tone", "false_start", "restart_race",
|
||||
"transponder_pass", "lap_completed", "personal_best", "fastest_lap", "track_record", "new_leader", "position_gain", "position_loss", "final_lap", "one_minute_remaining", "thirty_seconds_remaining", "ten_seconds_remaining", "time_expired", "race_finished",
|
||||
"transponder_pass", "lap_completed", "personal_best", "fastest_lap", "track_record", "training_stable_pace", "new_leader", "position_gain", "position_loss", "final_lap", "one_minute_remaining", "thirty_seconds_remaining", "ten_seconds_remaining", "time_expired", "race_finished",
|
||||
"track_call", "track_clear", "yellow_flag", "red_flag", "race_paused", "race_resumed", "marshal_warning", "marshal_required", "penalty", "stop_and_go", "drive_through", "disqualified",
|
||||
"pit_lane_open", "pit_lane_closed", "entering_pits", "leaving_pits", "pit_speed_warning",
|
||||
"qualifying_start", "qualifying_remaining", "qualifying_finished", "provisional_pole", "pole_position",
|
||||
|
||||
@@ -134,7 +134,8 @@ test("driver statistics remain isolated and aggregate days and sessions", () =>
|
||||
assert.equal(stats.trainingDays, 2);
|
||||
assert.equal(stats.bestLapMs, 16500);
|
||||
assert.equal(stats.averageLapMs, 17167);
|
||||
assert.equal(stats.controlledLapStreak, 2);
|
||||
// A flow streak never carries over into another session or stint.
|
||||
assert.equal(stats.controlledLapStreak, 1);
|
||||
assert.equal(stats.dailySummaries[0].laps, 1);
|
||||
assert.equal(stats.dailySummaries[1].laps, 2);
|
||||
});
|
||||
@@ -193,6 +194,7 @@ test("split and pit statistics aggregate by personal profile and selected class"
|
||||
assert.equal(stats.splitCount, 2);
|
||||
assert.equal(stats.bestSplitMs, 7900);
|
||||
assert.equal(stats.averageSplitMs, 8000);
|
||||
assert.equal(stats.latestSplitGainMs, 200);
|
||||
assert.equal(stats.pitStopCount, 2);
|
||||
assert.equal(stats.totalPitMs, 22000);
|
||||
assert.equal(stats.bestPitStopMs, 9500);
|
||||
@@ -292,6 +294,9 @@ test("active practice stats include only the selected driver's current valid lap
|
||||
bestLapMs: 16_000,
|
||||
averageLapMs: 16_500,
|
||||
lastLapMs: 17_000,
|
||||
controlZoneMinMs: 16_000,
|
||||
controlZoneMaxMs: 17_280,
|
||||
controlledLapStreak: 2,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -391,6 +396,31 @@ test("a stint shorter than the target duration has no five-minute result", () =>
|
||||
assert.equal(getBestTrainingDurationResult(stints, 300_000), null);
|
||||
});
|
||||
|
||||
test("training progress recognizes a clean five-minute stint and strong finish", () => {
|
||||
const ledger = Array.from({ length: 16 }, (_, index) => ({
|
||||
driverId: "driver_1",
|
||||
sourceId: "transponder_1",
|
||||
sessionId: "practice_1",
|
||||
timestamp: (index + 1) * 20_000,
|
||||
lapMs: 20_000,
|
||||
}));
|
||||
const state = { training: { ledger } };
|
||||
const stats = buildDriverTrainingStats(state, "driver_1");
|
||||
const analysis = buildTrainingStintAnalysis(state, "driver_1");
|
||||
|
||||
assert.equal(stats.controlZoneMinMs, 20_000);
|
||||
assert.equal(stats.controlZoneMaxMs, 21_600);
|
||||
assert.equal(stats.controlledLapStreak, 16);
|
||||
assert.equal(stats.outlierLaps, 0);
|
||||
assert.equal(stats.strongFinishStints, 1);
|
||||
assert.equal(stats.cleanFiveMinuteStints, 1);
|
||||
assert.equal(stats.achievements.find((item) => item.id === "strong_finish").unlocked, true);
|
||||
assert.equal(stats.achievements.find((item) => item.id === "clean_five_minute").unlocked, true);
|
||||
assert.equal(analysis.rows[0].strongFinish, true);
|
||||
assert.equal(analysis.rows[0].fiveMinuteResult.lapCount, 15);
|
||||
assert.equal(analysis.rows[0].fiveMinuteResult.totalMs, 300_000);
|
||||
});
|
||||
|
||||
test("recommendations select baseline, consistency, and progression", () => {
|
||||
assert.equal(buildTrainingRecommendation({ totalLaps: 9 }).level, "baseline");
|
||||
assert.deepEqual(
|
||||
|
||||
Reference in New Issue
Block a user