Skip to content

v4.3.0 Beta — SprintTimer, CrossingEngine extraction, DOVES_DISABLE_DEBUG - #49

Merged
TheAngryRaven merged 10 commits into
masterfrom
BETA
Aug 10, 2026
Merged

v4.3.0 Beta — SprintTimer, CrossingEngine extraction, DOVES_DISABLE_DEBUG#49
TheAngryRaven merged 10 commits into
masterfrom
BETA

Conversation

@TheAngryRaven

@TheAngryRaven TheAngryRaven commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

Promotes the current beta to master as v4.3.0. Three logical changes (merged into BETA via #47 and #48), no breaking API changes — all existing DovesLapTimer/CourseManager timing behavior is pinned by the Layer-3 NMEA replay goldens.

1. CrossingEngine — crossing detection extracted into a reusable core (refactor)

  • The in-zone GPS ring buffer, zone state machine (detect()), and crossing interpolation moved verbatim out of DovesLapTimer's private _detectLineCrossing / interpolateCrossingPoint into a new CrossingEngine class (src/CrossingEngine.h/.cpp). This resolves the long-standing checkStartFinish split-timing portability TODO.
  • Line coordinates + the per-line in-zone flag are passed per call, so one engine can be shared across lines (DovesLapTimer: one engine, 3 lines, mutual exclusion) or dedicated per line (SprintTimer: 4 independent engines).
  • Shared line geometry (geoPointOnSideOfLine, geoPointLineSegmentDistance, geoInsideLineThreshold) moved to GeoMath.h; DovesLapTimer's public methods delegate unchanged.
  • crossingPointBufferEntry, LineDetectResult, timeSinceMidnightDelta(), DOVES_MILLIS_PER_DAY, and the CROSSING_* constants now live in CrossingEngine.h but remain visible through DovesLapTimer.h for back-compat.
  • Includes the ArxTypeTraits polyfill fix so CrossingEngine.h builds on classic AVR.

2. SprintTimer — point-to-point run timing ("sprint mode") (feature)

  • For autocross / hillclimb / rally-stage events: a run starts at a START line and ends at a separate FINISH line; no laps. Up to 2 optional split lines — zero/one/two all legal (no both-or-nothing sector gate).
  • Two states, purely line-driven: WAITING → start crossing begins a run; RUNNING → finish completes it; re-crossing start mid-run cancels + restarts (botched-course re-launch, tracked via getCancelledRunCount()); a finish while WAITING is ignored; a DNF simply never completes.
  • All lines hot on every fix with independent per-line CrossingEngines — no mutual exclusion, so start/finish zones may overlap (autocross paddocks). Cost ~11.6 KB/instance on 32-bit; intended as a single instance, not for small AVRs.
  • Same fix-intake pipeline as DovesLapTimer (validation, teleport rejection, odometer). Duck-typed to the full DovesLapTimer getter surface (laps == runs) plus run-native aliases (getRuns(), getBestRunTime(), isRunActive(), ...). getCurrentLapTime() returns 0 while WAITING as the downstream "waiting" display signal.
  • CourseManager::selectCourse(int) added alongside: activate a known course directly, bypassing CourseDetector (point-to-point driving can never satisfy the detector's drive-a-lap-back-to-your-waypoint premise).
  • New example: examples/sprint_timing_example (synthetic course, Serial only), wired into the CI compile matrix for the 32-bit boards.

3. DOVES_DISABLE_DEBUG — compile-time debug kill switch (feature)

  • Debug output was gated by a runtime if (_serial) check, so production firmwares that never attach a debug Stream still carried every debug string and call site in flash (~150 call sites, several KB). Defining DOVES_DISABLE_DEBUG swaps all six classes' debug_print/debug_println templates for empty inlines so the compiler drops it all.
  • Off by default — default behavior unchanged. CI runs the host suite both with and without the flag.

Type of change

  • Bug fix
  • New feature
  • Refactor (no behavior change)
  • Docs / examples
  • CI / tooling

Testing

  • cd test && make run passes locally (all suites green)
  • Added/updated tests in the appropriate layer:
    • test/test_sprint_timer.cpp — synthetic point-to-point semantics: run times, cancel/restart, ignored finishes, 0/1/2 splits, missed-split invalidation
    • test/test_nmea_sprint.cppreal-data differential replay: the OKC 2-laps fixture through BOTH the circuit timer and a SprintTimer (start = S/F, finish = the real OKC sector-2 line); every run must equal that lap's circuit sector-1 time to ±10 ms, plus pinned run goldens
    • test/test_course_manager.cpp — extended for selectCourse()
    • unit-tests.yml — second host-suite pass with -DDOVES_DISABLE_DEBUG
  • Timing output unchanged — the Layer-3 NMEA replay goldens pin numeric behavior across the CrossingEngine move (that's the point of the extraction being verbatim)

Checklist

  • Doc comments updated for any public API changes (feeds the API docs site)
  • README.md updated (SprintTimer section + API tables, debug kill switch)
  • CHANGELOG.md updated (entries under Unreleased; v4.3.0 release prep: version bumps + all 8 pre-release code-review fixes #50 cuts them into the [4.3.0] release heading)
  • No new heap allocation in the GPS hot path; SprintTimer's per-line buffers are static members, documented as 32-bit-targets-only

Notes for reviewers

  • Merge order: v4.3.0 release prep: version bumps + all 8 pre-release code-review fixes #50 (v4.3.0 version bumps + CHANGELOG release heading) should land in BETA first so this PR ships master with correct release metadata. Tag v4.3.0 after this merges.
  • SprintTimer intentionally has no DirectionDetector — backward start crossings self-heal via the cancel/restart rule.
  • The sprint differential replay can't exercise staging stops / cancel-restart on real data (the OKC fixture is circuit driving); those paths stay covered synthetically until a true autocross fixture exists.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TQK29n1LCebSUxf1nD12FW

claude and others added 8 commits August 2, 2026 19:23
…tability TODO)

Move the in-zone ring buffer, zone state machine (_detectLineCrossing),
and crossing interpolation verbatim into a reusable CrossingEngine class;
line geometry (side-of-line, segment distance, hypotenuse zone test)
becomes GeoMath free functions. DovesLapTimer delegates through thin
wrappers - public API, memory layout, and numeric behavior unchanged
(Layer-2 suites and all four Layer-3 NMEA replay goldens pass unmodified).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
…urse

SprintTimer: sprint-mode (autocross/hillclimb) timing - a run starts at a
START line and ends at a separate FINISH line, up to two optional splits
(0/1/2 all legal). Two states, purely line-driven: start crossing begins a
run and cancels+restarts one already in progress (botched-course
re-launch); finish crossings with no active run are ignored; DNF is
nothing special. Every line has its own CrossingEngine so all lines stay
hot on every fix and start/finish zones may overlap. Duck-typed to
DovesLapTimer's getter surface (laps == runs) plus run-native aliases.

CourseManager::selectCourse(int) selects a layout directly, bypassing
CourseDetector - required for sprint (point-to-point driving can never
satisfy drive-a-lap-back-to-your-waypoint detection) and useful for
app-chosen circuit courses.

15 SprintTimer tests over a synthetic 500m open course (exact-by-
construction times) + 4 selectCourse tests. Coverage 85.2% (gate 80).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
…intTimer

Example compiles on the ESP32 + XIAO CI cells only (per-line buffers do
not fit small AVRs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
std::forward doesn't exist on classic AVR without the ArxTypeTraits
polyfill (no C++ standard library) - the extracted debug templates used
it but the new header didn't pull the polyfill in, breaking the Uno/Mega
CI cells for any TU that parses CrossingEngine.h before DovesLapTimer.h.
Same first-include rule DovesLapTimer.h already follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
… on OKC fixture

Per review idea: reuse the recorded OKC session as pseudo-sprint data.
Start = the OKC 'Normal' S/F line, finish = its real sector-2 line (from
the DovesDataLogger SDCARD OKC.json - S/F coords match the fixture to
the last digit). Every recorded lap then contains exactly one
point-to-point run whose duration must equal that lap's circuit-timer
sector-1 time on the same fixes: two independent accounting layers over
the same crossing engine agree to the millisecond (28378 / 27903 /
28304 ms, pinned +/-50 like the other layer-3 goldens). Also verifies
zero cancellations on circuit data and the fixture-end abandoned run
(DNF-as-normal-op) on real GPS. Synthetic suite unchanged - it still
owns the semantics circuit data can't produce (staging stops,
cancel/restart, ignored finishes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
SprintTimer: point-to-point run timing (sprint mode) + CrossingEngine extraction + selectCourse
The debug templates' runtime if(_serial) gate keeps every debug string
and print call-site in flash even on firmwares that never attach a
debug Stream (~150 call sites across the library - several KB on a
fully-featured build; the DovesDataLogger beta image just burst its
320 KB OTA cap at 100.9% partly on this dead weight). Defining
DOVES_DISABLE_DEBUG swaps the templates for empty inlines so the
compiler drops strings and call-sites entirely. Default behavior
unchanged; unit-tests CI now runs the host suite both ways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
DOVES_DISABLE_DEBUG: compile-time debug kill switch (unblocks the logger's OTA size gate)
@github-actions

Copy link
Copy Markdown

GCC Code Coverage Report

📂 Overall coverage

Metric Coverage
Lines 🟡 1465/1712 (85.6%)
Functions 🟡 242/296 (81.8%)
Branches 🔴 659/1050 (62.8%)

📄 File coverage

File Lines Functions Branches
src/CourseDetector.cpp 🟢 98/104 (94.2%) 🟢 18/20 (90.0%) 🟡 39/46 (84.8%)
src/CourseManager.cpp 🟡 154/173 (89.0%) 🟡 18/24 (75.0%) 🟡 79/102 (77.5%)
src/CourseManager.h 🟢 33/33 (100.0%) 🟢 11/11 (100.0%) 🔴 11/22 (50.0%)
src/CrossingEngine.cpp 🟢 185/198 (93.4%) 🟢 11/12 (91.7%) 🔴 103/172 (59.9%)
src/CrossingEngine.h 🟡 91/103 (88.3%) 🟡 30/34 (88.2%) 🔴 31/68 (45.6%)
src/DovesLapTimer.cpp 🟡 294/362 (81.2%) 🔴 38/54 (70.4%) 🔴 161/254 (63.4%)
src/DovesLapTimer.h 🔴 53/91 (58.2%) 🟡 18/24 (75.0%) 🔴 14/40 (35.0%)
src/GeoMath.h 🟢 49/52 (94.2%) 🟢 7/7 (100.0%) 🟡 32/38 (84.2%)
src/SprintTimer.cpp 🟡 271/321 (84.4%) 🟡 37/48 (77.1%) 🔴 125/188 (66.5%)
src/SprintTimer.h 🟢 57/63 (90.5%) 🟢 23/25 (92.0%) 🔴 17/38 (44.7%)
src/WaypointLapTimer.cpp 🟡 159/188 (84.6%) 🟡 24/29 (82.8%) 🔴 40/66 (60.6%)
src/WaypointLapTimer.h 🟡 21/24 (87.5%) 🟡 7/8 (87.5%) 🔴 7/16 (43.8%)

- library.properties: 4.2.0 -> 4.3.0
- Doxyfile PROJECT_NUMBER: stale 4.0.0 -> 4.3.0
- CLAUDE.md version line -> 4.3.0
- CHANGELOG: move Unreleased entries under [4.3.0] - 2026-08-10 with
  release summary; add the 4.3.0 tag link

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQK29n1LCebSUxf1nD12FW
@TheAngryRaven TheAngryRaven changed the title Beta v4.3.0 Beta — SprintTimer, CrossingEngine extraction, DOVES_DISABLE_DEBUG Aug 10, 2026
…iew-j5itt1

chore: v4.3.0 release prep — version bumps + CHANGELOG release heading
@TheAngryRaven
TheAngryRaven merged commit b578c13 into master Aug 10, 2026
8 checks 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.

2 participants