Skip to content

Commit 2db51a5

Browse files
Rio517claude
andcommitted
fix: bestLane timeCount bug, denPlace null narrowing
- bestLane averaged time over all runs including untimed; now uses separate timeCount for timed-only average - Add denPlace guard before showDen for TypeScript strict null narrowing - Add test for mixed timed/untimed runs on the same lane Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4ead934 commit 2db51a5

3 files changed

Lines changed: 16 additions & 4 deletions

File tree

src/frontend/lib/racer-stats.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ export function bestLane(history: RacerHistoryEntry[]): number | null {
1010
const valid = history.filter(h => !h.dnf && h.place != null);
1111
if (valid.length === 0) return null;
1212

13-
const byLane = new Map<number, { totalTime: number; totalPlace: number; count: number; hasTimes: boolean }>();
13+
const byLane = new Map<number, { totalTime: number; timeCount: number; totalPlace: number; count: number; hasTimes: boolean }>();
1414
for (const h of valid) {
15-
const entry = byLane.get(h.lane_number) ?? { totalTime: 0, totalPlace: 0, count: 0, hasTimes: false };
15+
const entry = byLane.get(h.lane_number) ?? { totalTime: 0, timeCount: 0, totalPlace: 0, count: 0, hasTimes: false };
1616
entry.totalPlace += h.place!;
1717
entry.count++;
1818
if (h.time_ms != null && h.time_ms > 0) {
1919
entry.totalTime += h.time_ms;
20+
entry.timeCount++;
2021
entry.hasTimes = true;
2122
}
2223
byLane.set(h.lane_number, entry);
@@ -30,7 +31,7 @@ export function bestLane(history: RacerHistoryEntry[]): number | null {
3031
let bestScore = Infinity;
3132
for (const [lane, stats] of byLane) {
3233
if (anyTimed && !stats.hasTimes) continue;
33-
const score = anyTimed ? stats.totalTime / stats.count : stats.totalPlace / stats.count;
34+
const score = anyTimed ? stats.totalTime / stats.timeCount : stats.totalPlace / stats.count;
3435
if (score < bestScore) {
3536
bestScore = score;
3637
best = lane;

src/frontend/views/RacerProfileView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function RacerProfileView() {
266266
totalRacers={racers.length}
267267
/>
268268

269-
{showDen && (
269+
{denPlace && showDen && (
270270
<DenPlacementStrip rank={denPlace.rank} den={denPlace.den} isPodium={rank !== null && rank <= 3} />
271271
)}
272272

tests/best-lane.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,15 @@ describe('bestLane', () => {
7878
// Lane 1 is the only timed lane, so it wins
7979
expect(bestLane(history)).toBe(1);
8080
});
81+
82+
it('averages time only over timed runs on the same lane', () => {
83+
const history = [
84+
entry(1, 1, 3000),
85+
entry(1, 2), // untimed run — should not dilute avg time
86+
entry(2, 1, 3100),
87+
];
88+
// Lane 1 avg time: 3000/1 = 3000 (not 3000/2 = 1500)
89+
// Lane 2 avg time: 3100/1 = 3100
90+
expect(bestLane(history)).toBe(1);
91+
});
8192
});

0 commit comments

Comments
 (0)