Skip to content

plan 0002: sprint mode core — /TRACKS/SPRINT, mode-by-folder, SprintTimer backend, DOVEX race_mode - #116

Merged
TheAngryRaven merged 5 commits into
BETAfrom
claude/sprint-mode-race-types-wt9s7o
Aug 2, 2026
Merged

plan 0002: sprint mode core — /TRACKS/SPRINT, mode-by-folder, SprintTimer backend, DOVEX race_mode#116
TheAngryRaven merged 5 commits into
BETAfrom
claude/sprint-mode-race-types-wt9s7o

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Summary

Phase 2, PR A of plan 0002 — the firmware's sprint mode core, consuming the library work merged in DovesLapTimer#47. (PR B = BLE sprint-track sync opcodes, PR C = the on-device course creator — kept separate per one-concern-per-PR.)

  • /TRACKS/SPRINT/: sprint tracks live in their own auto-provisioned folder; circuit tracks and all existing behavior untouched. Manifest entries carry their folder kind; buildTrackList() walks both via a new scanTrackDir(folder, kind); makeFullTrackPath() is kind-aware.
  • Mode follows the detected track's folder. trackDetectionLoop() finds the nearest entry per kind; when both kinds are in range the new race_mode setting (default circuit) breaks the tie via the host-tested sprint_select unit — circuit yields only to a sprint track whose newest course was created today (event-day heuristic), sprint always prefers the sprint track (fixed rally layouts).
  • Sprint sessions skip CourseManager/CourseDetector entirely: createSprintSession() selects the newest course by date_created (sortable ISO stamp; autocross venues re-lay the course every event) and stands up the library's SprintTimer (start + separate finish_* line + optional splits). sprintTimer != nullptr is sprint mode; every activeTimer*() helper duck-types runs as laps, so displays/logging/replay work unchanged — "laps" verbiage kept everywhere by design.
  • Session lifecycle (session = heat): run completions are captured on the run-count edge (identical consecutive run times are normal at autocross; the value-change dedupe would silently drop them) and each completed run re-arms the auto-idle grace. Sprint auto-idle is engine-aware — a running engine at the start line can never end the session. Current Lap / Pace pages show *waiting* between runs; every other page stays live.
  • DOVEX race_mode trailing header column (CIRCUIT/SPRINT, empty = circuit; same append mechanism as device_name) so the webapp knows to read the laps line as runs. Includes a real parser fix the new column exposed: the strtok line-2 splitter collapsed empty middle fields, shifting every later column left (regression-tested).
  • New pure units: sprint_select.{h,cpp} (+21 test cases), dovex_header extensions (+4 cases incl. the empty-field regression).

Type of change

  • Bug fix (no user-visible behavior change beyond the fix)
  • New feature / behavior
  • Refactor (no behavior change)
  • Tests only
  • CI / tooling / docs
  • Breaking change (track files, log format, BLE protocol, or a removed mode)

Not breaking: legacy logs/tracks parse identically; the DOVEX column and JSON fields are additive; sprint behavior only activates for files in the new folder.

How it was verified

  • Host unit tests pass (ctest --test-dir tests/build) — 292 cases / 4102 assertions, 0 failed
  • clang-tidy clean — deferred to CI
  • Compiles for the XIAO nRF52840 Sense — deferred to CI (BETA-targeted PRs build against the library's BETA branch, which carries SprintTimer since DovesLapTimer#47)
  • Tested on real hardware — not yet; needs a track-day pass before release, ideally at the autocross user's venue
  • Simulator: the full native sim suite passes (boot soak, determinism, golden fixtures, both lap oracles, two-session carryover) — the real firmware TU compiles with sprint integrated and the oracles pin circuit behavior as unchanged.

Checklist

  • CHANGELOG.md updated under [Unreleased] (if user-visible)
  • ARCHITECTURE.md / CLAUDE.md updated (if a module or interface changed) — CLAUDE.md file map, subsystem 9, DOVEX + track JSON + settings sections; README settings table
  • New testable logic has a matching test in tests/
  • Branch is focused — refactors / behavior / tests are not mixed together

Related issues

Design: docs/plans/0002-sprint-mode.md. Library side: TheAngryRaven/DovesLapTimer#47 (merged).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb


Generated by Claude Code

claude added 3 commits August 2, 2026 20:58
sprint_select: newest-course-by-date_created ordering (sortable ISO
strings, ties resolve to the later file entry) + the circuit-vs-sprint
tiebreak decision table (race_mode pref; circuit yields to a sprint
course created today). dovex_header: race_mode trailing column
(CIRCUIT/SPRINT, empty = circuit, same append mechanism as device_name)
and the line-2 splitter now preserves empty middle fields - the strtok
version let a blank column shift every later column left (regression
test included).

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

- /TRACKS/SPRINT folder (auto-provisioned; circuit tracks untouched);
  manifest entries carry their folder kind; buildTrackList scans both
  via the new scanTrackDir(folder, kind); makeFullTrackPath is
  kind-aware; FILEPATH_MAX 50->64
- parseTrackFile: track-level "type", per-course finish_* lines and
  date_created (containsKey idiom, same as sectors)
- trackDetectionLoop: nearest entry PER KIND; race_mode setting (new,
  default circuit) breaks both-in-range ties via sprint_select, with
  the event-day heuristic parsing the sprint file's newest course date
- sprint path skips CourseManager/CourseDetector: createSprintSession()
  picks the newest course and stands up the library's SprintTimer
  (start + separate finish + optional splits); sprintTimer != nullptr
  IS sprint mode, all activeTimer*() helpers duck-type runs as laps
- lifecycle: run-count-edge history capture (identical consecutive run
  times are normal at autocross - value-change dedupe would drop them),
  each run re-arms the auto-idle grace, sprint idle is engine-aware
  (running engine at the start line never ends the session)
- DOVEX header: sprint course name + race_mode=SPRINT
- display: Current Lap / Pace show *waiting* between runs; 'laps'
  verbiage kept everywhere by design
- sim: SprintTimer/CrossingEngine/sprint_select compiled into the TU,
  prototypes mirrored; all 6 sim tests (incl. lap oracles) green

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

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 822/832 (98.8%)
Functions 🟢 88/88 (100.0%)
Branches 🟢 665/731 (91.0%)

📄 File coverage

File Lines Functions Branches
BirdsEye/camera_fsm.cpp 🟢 222/230 (96.5%) 🟢 20/20 (100.0%) 🟡 126/144 (87.5%)
BirdsEye/crc32.cpp 🟢 30/30 (100.0%) 🟢 4/4 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/dovex_header.cpp 🟢 106/107 (99.1%) 🟢 7/7 (100.0%) 🔴 62/88 (70.5%)
BirdsEye/filename_validator.cpp 🟢 14/14 (100.0%) 🟢 1/1 (100.0%) 🟢 30/30 (100.0%)
BirdsEye/gps_stats.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 8/8 (100.0%)
BirdsEye/gps_status_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/gps_time.cpp 🟢 45/45 (100.0%) 🟢 6/6 (100.0%) 🟢 30/32 (93.8%)
BirdsEye/gps_validation.cpp 🟢 24/24 (100.0%) 🟢 2/2 (100.0%) 🟢 66/66 (100.0%)
BirdsEye/haversine.cpp 🟢 8/8 (100.0%) 🟢 1/1 (100.0%) ⚫ 0/0 (0.0%)
BirdsEye/insta360_protocol.cpp 🟢 140/140 (100.0%) 🟢 16/16 (100.0%) 🟡 86/98 (87.8%)
BirdsEye/lap_format.cpp 🟢 18/18 (100.0%) 🟢 1/1 (100.0%) 🟢 9/9 (100.0%)
BirdsEye/sat_bars.cpp 🟢 33/33 (100.0%) 🟢 2/2 (100.0%) 🟢 51/54 (94.4%)
BirdsEye/sd_access_policy.cpp 🟢 9/9 (100.0%) 🟢 3/3 (100.0%) 🟢 18/18 (100.0%)
BirdsEye/sd_format_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 25/26 (96.2%)
BirdsEye/sensoregg_protocol.cpp 🟢 44/45 (97.8%) 🟢 7/7 (100.0%) 🟢 33/34 (97.1%)
BirdsEye/sprint_select.cpp 🟢 25/25 (100.0%) 🟢 4/4 (100.0%) 🟢 46/48 (95.8%)
BirdsEye/tach_filter.cpp 🟢 15/15 (100.0%) 🟢 3/3 (100.0%) 🟡 7/8 (87.5%)
BirdsEye/wake_cause.cpp 🟢 14/14 (100.0%) 🟢 2/2 (100.0%) 🟢 20/20 (100.0%)

claude added 2 commits August 2, 2026 21:09
… fix)

The beta image burst the 320 KB OTA self-flash cap (330,628 B, 100.9%)
- and was already at 98.2% before sprint mode. Root cause: the
DovesLapTimer library's debug pipeline is gated by a RUNTIME
if(_serial) check, so ~150 debug call-sites and their F() strings stay
resident in flash on hardware builds that never attach a debug Stream.
DovesLapTimer#48 (BETA) adds the DOVES_DISABLE_DEBUG compile-time kill
switch; this passes it in compile-sketch/beta/release so the dead
pipeline is dropped (~6-8 KB), bringing the image back under the cap
with real headroom instead of moving the OTA staging layout. IDE debug
builds are unaffected (flag is per-build opt-in; documented in
CONTRIBUTING).

Requires DovesLapTimer#48 merged to BETA before the compile-sketch
gate can pass (CI pulls the library's BETA branch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
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