Skip to content

Fix spurious 0.00s lap from duplicate portal speed events#36

Merged
burkben merged 1 commit into
mainfrom
burkben-fix-race-duplicate-lap
Jun 21, 2026
Merged

Fix spurious 0.00s lap from duplicate portal speed events#36
burkben merged 1 commit into
mainfrom
burkben-fix-race-duplicate-lap

Conversation

@burkben

@burkben burkben commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Problem

In race mode, every physical car pass over the portal recorded two laps: a spurious 0.00s lap immediately followed by the real lap time. A 5-lap race ended after only 3 real crossings, with laps showing 0.00, 2.07, 0.00, 1.53, 0.00. The same duplication inflated pass counts in History and Garage.

Root cause

The real portal hardware delivers two speed PortalEvents per single physical crossing (a BLE notification echo / firmware double-send), arriving within a few milliseconds of each other with identical decoded readings. portalStore.dispatch (case "speed") created a separate Pass for each event, and the race screen folds each fresh pass into raceEngine.recordGate — so two passes ~0ms apart produced a real lap followed by a ~0.00s lap.

The mock/demo portal emits exactly one speed per pass (seconds apart), so demo mode never reproduced it. Both BLE transports and the mock converge on portalStore.dispatch, so the fix lives there and is transport-agnostic. raceEngine.ts, race.tsx, and the mock are unchanged.

Fix

Add a 250ms same-speed dedupe to the "speed" case in portalStore.dispatch. A car physically cannot cross the gate twice within a few hundred ms, so a raw-equal speed event arriving within PASS_DEDUPE_MS of the previous pass is treated as a duplicate indication: the live gauge (lastSpeed) still updates, but no new pass is appended and bestMph is not bumped. Requiring both the time window and an equal raw value means distinct synchronous speeds are unaffected. Dedupe state (lastPassAt/lastPassRaw) is cleared on reset() and on disconnect, consistent with the existing passCounter pattern.

Tests

Added three cases to portalStore.test.ts (using vi.spyOn(Date, "now")):

  • collapses a duplicate same-speed event from one crossing into a single pass
  • records a same-speed pass on a later lap (outside the dedupe window)
  • does not dedupe back-to-back passes with different speeds

npm run typecheck and npm test both pass (181 mobile + 49 protocol tests).

Closes #35

Real portal hardware delivers two `speed` PortalEvents per physical
crossing (BLE notification echo / firmware double-send) within a few ms
with identical readings. portalStore.dispatch created a Pass for each,
so the race engine folded them into a real lap followed by a ~0.00s lap.

Add a 250ms same-speed dedupe in the "speed" case so a duplicate
indication of one crossing is collapsed into a single pass: the live
gauge still updates but no pass is appended and bestMph is unchanged.
Requiring both the time window and an equal `raw` value leaves distinct
synchronous speeds unaffected. Dedupe state is cleared on reset() and on
disconnect. Fix is transport-agnostic and also corrects inflated
History/Garage pass counts.

Closes #35

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@burkben burkben merged commit 6188ab5 into main Jun 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race mode records a spurious 0.00s lap on every pass (duplicate portal speed events)

1 participant