Fix code-review findings C1–C3, H1, H2: line init, midnight rollover, detection window, input validation, buffer wraparound#41
Merged
Conversation
…n window, input validation, buffer wraparound Addresses the C1/C2/C3/H1/H2 findings from the professional code review: - C1: zero-initialize all crossing-line members, gate checkStartFinish() on a new startFinishLineConfigured flag (exposed via isStartFinishLineConfigured()), and reject degenerate/non-finite lines in all three line setters. loop() before setStartFinishLine() was reading indeterminate doubles (UB, phantom crossings). - C2: normalize every duration across the UTC midnight wrap via a shared timeSinceMidnightDelta() helper (DovesLapTimer + WaypointLapTimer). The crossing interpolator now computes its fix-pair delta in double, wrap-normalized and clamped to CROSSING_MAX_FIX_GAP_MS (10s), before converting back to unsigned long - the old path underflowed unsigned subtraction and pushed an out-of-range double through double->unsigned conversion (UB). Crossing validity is now a bool return instead of the time != 0 sentinel, so a legitimate crossing at exactly 00:00:00.000 is no longer discarded. - C3: CourseDetector re-anchors the lap-distance window after every completed proximity pass, match or no match. Previously a no-match lap left distanceSinceWaypoint accumulating, falsely matching a 2x-length layout on the next pass (same root cause as the v4.1.0 rejection- cooldown fix, CLAUDE.md issue 13). - H1: validate GPS input in DovesLapTimer::loop(), WaypointLapTimer::loop() and CourseDetector::update(): reject NaN/Inf/out-of-range/(0,0) fixes, sanitize non-finite alt/speed, drop single-fix teleports >500m, and re-accept sustained relocations after 3 consecutive far fixes without crediting the gap to the odometer. A single NaN or (0,0) fix previously poisoned odometers for the whole session and hung course detection. - H2: unwind the crossing ring buffer chronologically before scanning for the straddling pair. Once the ring wrapped (kart parked in the zone - standing start, red flag), the seam pair (newest entry adjacent to oldest) masqueraded as the crossing, fabricating or invalidating laps. Adds 23 new host unit tests in three new suites (test_midnight_rollover, test_input_validation, test_buffer_wraparound) plus two CourseDetector re-anchor regression tests; the C3 and H2 tests were verified to fail against the pre-fix code. All 89 tests green, NMEA replay goldens unchanged. https://claude.ai/code/session_01XNmVH6ySJ6Dz2QKDjB69mE
GCC Code Coverage Report📂 Overall coverage
📄 File coverage
|
This was referenced Jun 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses all five findings from the professional code review (scored 4.5/10). Each fix lands with regression tests; the C3 and H2 tests were verified to fail against the pre-fix code. All 89 host tests green, and the pinned NMEA replay goldens are unchanged (no timing drift on real-track data).
C1 — Start/finish line never initialized
startFinishLineConfiguredflag (mirroring the sector flags) gatescheckStartFinish()inloop()— callingloop()beforesetStartFinishLine()is now a safe no-op instead of UB on indeterminate doubles.0.00placeholders shipped inbasic_oled_example) or non-finite lines, with a debug warning.isStartFinishLineConfigured().C2 — Midnight rollover / backwards GPS time
timeSinceMidnightDelta()helper normalizes every duration across the 86,399,999 → 0 UTC midnight wrap. Applied at all subtraction sites inDovesLapTimer(lap, all three sectors,getCurrentLapTime,getPaceDifference) andWaypointLapTimer.double, wrap-normalizes it, and clamps it toCROSSING_MAX_FIX_GAP_MS(10 s) before any conversion back tounsigned long— the old path underflowed unsigned subtraction and pushed an out-of-range double through double→unsigned conversion ([conv.fpint] UB).interpolateCrossingPoint()/_detectLineCrossing()instead of thetime != 0sentinel, so a legitimate crossing at exactly 00:00:00.000 is no longer discarded.C3 — Course detection false-matched longer layouts
_checkWaypointProximity()now re-anchors_waypointOdometerafter a completed proximity pass with no match, sodistanceSinceWaypointcan't accumulate across laps and falsely match a 2×-length layout on the next pass. Same root-cause class as the v4.1.0 rejection-cooldown fix (CLAUDE.md issue little review with opus #13); regression test mirrors that one.H1 — GPS input validation
GeoMath.hhelpersgeoIsFinite()/geoCoordinatesValid()(noisnan/isinfdependency — portable across Arduino cores and host).DovesLapTimer::loop(),WaypointLapTimer::loop(), andCourseDetector::update()reject NaN/Inf, |lat|>90, |lng|>180, and exact (0,0) fixes; non-finite altitude/speed are sanitized rather than dropping the fix.GPS_MAX_PLAUSIBLE_JUMP_METERS(500 m) is dropped; afterGPS_JUMP_REACCEPT_COUNT(3) consecutive far fixes the new position is accepted as a genuine relocation and re-seeded without crediting the gap to the odometer.H2 — Crossing-buffer wraparound
interpolateCrossingPoint()unwinds the ring chronologically (oldest → newest) before scanning for the straddling pair. Once the ring wrapped (kart parked in the zone: standing start, red flag, stop-and-go), physical index order ≠ chronological order and the seam pair (newest adjacent to oldest) masqueraded as the crossing — fabricating a crossing from points minutes apart or invalidating the real one. Verified failing pre-fix: the park-cross-park scenario dropped the crossing entirely.Tests
test_midnight_rollover.cpp(7): lap/sector/current-lap/crossing-zone timing across the wrap,WaypointLapTimeracross the wrap, delta-helper units.test_input_validation.cpp(11): adversarial fixes, teleport/relocation, unconfigured + degenerate line safety, detector/WLT guards.test_buffer_wraparound.cpp(3): park-cross-park, standing start on the grid, normal lap after a wrapped start.test_course_detector.cpp(+2): no-match re-anchor + post-re-anchor genuine match.CHANGELOG, README, CLAUDE.md, keywords.txt, and test/README.md updated accordingly.
https://claude.ai/code/session_01XNmVH6ySJ6Dz2QKDjB69mE
Generated by Claude Code