Skip to content

Beta 4.2.0 - drag timer - #160

Open
TheAngryRaven wants to merge 14 commits into
masterfrom
BETA
Open

Beta 4.2.0 - drag timer#160
TheAngryRaven wants to merge 14 commits into
masterfrom
BETA

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

tbd

TheAngryRaven and others added 5 commits August 25, 2026 00:54
… is answered

9134 Hz, 96 us mean, 37 ms worst iteration, SLP 12.0, with a GPS lock,
logging and the camera recording.

The instrument validates itself: 9134 Hz x 96 us = 87.7% of the second
inside loop(), and SLP — computed by a different route — reads 12.0%.
All fourteen slots sum to ~99.8.

SLP 12% also settles what the previous build could not distinguish: the
CPU is not sleeping through the second, so the loop genuinely runs at
~9 kHz and the old pegged rate was the display clamp rather than the
timebase bug. There was no way to know that until the window was
wall-clocked.

Headroom, against the binding constraint (one iteration per 40 ms PVT
frame): ~420x on the mean, 43x on the worst iteration against the 4 KB
GPS ring, 108x against the watchdog. Nothing here justifies an nRF5340
on throughput, and SLP 12% caps what leaving the Arduino core could
reclaim.

Also records where the 88% goes — it is polling, not work. GPS is called
9134 times a second to service a 25 Hz stream; DSP renders 3 times and
early-returns ~9000; BTN is six digitalReads per iteration with nobody
touching a button. Rate-limiting the polls would reclaim most of it
without changing silicon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqENxo91S1VL3sM9PTWMf1
Main menu -> Drag -> pick 1/8 Mile / 1000 ft / 1/4 Mile / 1/2 Mile /
1 Mile and the session starts: no track file, no detection. Stage at a
standstill, rollout-style launch (11.25 in past a re-latching standstill
anchor, ET start interpolated between 25 Hz fixes), run ends at the
target distance with interpolated ET + trap speed and a 0-60 split, then
re-arms automatically for the next pass. All passes land in one DOVEX
session with race_mode=DRAG (laps line = run ETs, same trailing-column
scheme as SPRINT).

- New host-tested pure unit drag_timer.{h,cpp}: the whole run state
  machine (ARMED/STAGED/LAUNCHED), the distance table, silent aborts
  (mid-run standstill, >=2 s fix gap — also how queue-creep phantom
  launches self-cancel), 14 doctest cases.
- Sketch glue mirrors sprint: dragTimer != nullptr IS drag mode, drag
  branch first in every activeTimer*() helper, run capture on the
  run-count edge, trackDetected latched, idle-grace re-arm on each run
  AND each fresh STAGED latch.
- UI: Drag main-menu row, PAGE_DRAG_DISTANCE picker (scrolling window),
  drag branches on the lap-time/pace/best pages (live ET, *staged*,
  trap/0-60 subtext, live 0-60 readout), LED pace pip suppressed between
  runs like sprint.
- Sim: golden walk updated for the new menu row + a picker fixture;
  goldens regenerated; soak/determinism/goldens/oracles all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKa62h4fhn61pKn1LygrMK
…ranch-8jmgzr

docs(0011): record the wall-clocked hardware run — the board question…
Six findings from the branch review, all fixed:

- Time base: the glue fed getGpsTimeInMilliseconds() (time-of-day, wraps
  to zero at UTC midnight — evening sessions) into a unit whose contract
  says epoch ms. Both call sites now feed getGpsUnixTimestampMillis(),
  and the unit's non-monotonic guard resyncs on a backwards step instead
  of rejecting every subsequent fix — one step used to wedge the drag
  timer for the rest of the session.
- Launch edge: a >=2 s fix gap while STAGED now re-stages instead of
  evaluating the launch, so a launch that happened inside a GPS dropout
  can't interpolate its ET start back to a parked-car fix.
- Prove-out gate: a launch must reach 15 mph within 5 s of the ET start
  or the run is silently abandoned — a wave-off driven to the pits at
  4 mph never holds the sub-2 mph standstill abort and used to record
  660 ft of pit road as a ~90 s "run".
- activeTimerTotalDistance() converts the drag branch to meters, matching
  every other branch of the accessor.
- distanceFeet() delegates to the haversine unit (x 5280) instead of
  carrying a second copy of the formula and Earth radius.
- The seconds.hundredths split rendering is now one function
  (displayPrintSplitSeconds) shared by the results subtext and the pace
  page's live 0-60 readout.

Five new regression tests cover the wedge recovery, the staged-gap
launch, the wave-off, a slow-but-real pass, and duplicate timestamps.
Plan 0015 doc + CLAUDE.md constants updated to match.

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

plan 0015: Drag mode — distance runs without a track
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 2159/2192 (98.5%)
Functions 🟢 220/221 (99.5%)
Branches 🟢 1576/1746 (90.3%)

📄 File coverage

File Lines Functions Branches
BirdsEye/ble_stream.cpp 🟢 34/34 (100.0%) 🟢 8/8 (100.0%) 🟡 17/20 (85.0%)
BirdsEye/camera_fsm.cpp 🟢 238/246 (96.7%) 🟢 20/20 (100.0%) 🟡 142/160 (88.8%)
BirdsEye/course_creator.cpp 🟢 213/221 (96.4%) 🟢 21/21 (100.0%) 🟡 119/136 (87.5%)
BirdsEye/course_prune.cpp 🟢 37/37 (100.0%) 🟢 5/5 (100.0%) 🟢 47/50 (94.0%)
BirdsEye/crc32.cpp 🟢 30/30 (100.0%) 🟢 4/4 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/crossing_pattern.cpp 🟢 15/15 (100.0%) 🟢 1/1 (100.0%) 🟢 12/12 (100.0%)
BirdsEye/dovex_header.cpp 🟢 106/107 (99.1%) 🟢 7/7 (100.0%) 🔴 62/88 (70.5%)
BirdsEye/drag_timer.cpp 🟢 150/154 (97.4%) 🟢 10/10 (100.0%) 🟡 76/94 (80.9%)
BirdsEye/drag_tree.cpp 🟢 112/114 (98.2%) 🟡 7/8 (87.5%) 🟢 87/94 (92.6%)
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 🟢 29/29 (100.0%) 🟢 4/4 (100.0%) 🟢 28/28 (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/idle_policy.cpp 🟢 17/17 (100.0%) 🟢 2/2 (100.0%) 🟢 14/14 (100.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/led_animations.cpp 🟢 84/84 (100.0%) 🟢 7/7 (100.0%) 🟢 43/46 (93.5%)
BirdsEye/led_frame.cpp 🟢 21/21 (100.0%) 🟢 7/7 (100.0%) 🟢 6/6 (100.0%)
BirdsEye/led_modes.cpp 🟢 67/68 (98.5%) 🟢 6/6 (100.0%) 🟢 50/52 (96.2%)
BirdsEye/led_status.cpp 🟢 106/108 (98.1%) 🟢 11/11 (100.0%) 🟢 71/75 (94.7%)
BirdsEye/local_time.cpp 🟢 48/48 (100.0%) 🟢 6/6 (100.0%) 🟢 46/50 (92.0%)
BirdsEye/loop_profile.cpp 🟢 65/65 (100.0%) 🟢 7/7 (100.0%) 🟢 35/36 (97.2%)
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/sector_purple.cpp 🟢 84/85 (98.8%) 🟢 3/3 (100.0%) 🟡 57/64 (89.1%)
BirdsEye/sensoregg_protocol.cpp 🟢 87/88 (98.9%) 🟢 13/13 (100.0%) 🟢 74/76 (97.4%)
BirdsEye/setting_parse.cpp 🟢 29/30 (96.7%) 🟢 2/2 (100.0%) 🟢 38/42 (90.5%)
BirdsEye/sprint_select.cpp 🟢 25/25 (100.0%) 🟢 4/4 (100.0%) 🟢 46/48 (95.8%)
BirdsEye/tach_filter.cpp 🟢 91/91 (100.0%) 🟢 13/13 (100.0%) 🟡 72/82 (87.8%)
BirdsEye/track_json.cpp 🟢 116/120 (96.7%) 🟢 12/12 (100.0%) 🟡 67/88 (76.1%)
BirdsEye/wake_cause.cpp 🟢 14/14 (100.0%) 🟢 2/2 (100.0%) 🟢 20/20 (100.0%)

claude and others added 9 commits August 26, 2026 04:07
Drag now asks Automatic or Manual after the distance. Manual stages
like a strip: stop, and the 9-px bar lights a white staging pip, three
yellows at the sportsman 500 ms cadence, then green — mirrored on the
OLED as STOP TO STAGE, a big 3..2..1, and a flashing GO. Movement
during the yellows is a RED LIGHT foul; 5 s still after green is
FAILED TO LAUNCH; both flash red and wait for a button. Runs end on a
results screen (ET, trap, 0-60, and a new reaction time = green ->
interpolated rollout crossing, both epoch ms). The screen is pinned to
staging info for the whole manual session; hold Select 2 s to end it.
Automatic mode is behavior-identical (one extra menu hop).

- New host-tested pure unit drag_tree.{h,cpp}: the ONE state machine
  driving both the LED strip (renderStrip) and the OLED (countdownDigit
  / flashPhase) so they can never disagree; 17 doctest cases (exact
  stage timings, fouls from every yellow, exit-hold semantics, golden
  strip frames). Flash half-period 500 ms — the 3 Hz OLED aliases
  anything faster.
- drag_timer grows setLaunchEnabled (default true; while disabled a
  rollout-at-speed re-arms — a pre-green move is the tree's foul, never
  a run) and runStartEpochMs() for the RT computation; 3 new tests.
- Glue: dragStagingLoop() in the gpsStatusPageLoop slot (Inputs/step/
  Effects, resetButtons on consumption); the launch gate re-asserted
  every loop (open exactly while green); pin cleared only by
  endRaceSession so every session ender releases it.
- UI: PAGE_DRAG_MODE (-18) Auto/Manual/Back; PAGE_DRAG_STAGING (-19)
  pinned via the gpsLockHold construction. Fixed a plan-0015 layout
  bug while here: subtext after size-3 text needs a size-3 newline or
  it renders on the ET's bottom glyph rows.
- NeoPixel: tree renders strip-only as a new cascade arm (status LEDs
  live, search pip wins without a fix, normal scale during the run);
  paceValid gains !dragManualActive() (constant false for auto).
- Sim: 6 new golden fixtures including a FULL manual pass through the
  real pipeline (park -> tree -> green -> 70 mph -> results with RT);
  goldens regenerated (23 fixtures), soak/determinism/oracles pass.

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

plan 0016: Manual drag mode — the christmas tree
parseMac/formatMac (settings-string codec), macReverse (human <->
ble_gap_addr_t LSB-first, aliasing-safe), macIsWildcard, macAccepts —
the byte-order reversal the scan callback depends on finally moves
under host test, with a case pinning that a same-order compare rejects.
kPairingTimeoutMs (120 s, camera parity) and kMacStrLen live with the
other tunables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Runtime MAC filter boots from the SENSOREGG_MAC fallback and loads
sensoregg_mac at SENSOREGG_SETUP(); the scan callback stays protocol-
blind (it additionally memcpys the advertiser address into a per-slot
field of the existing double buffer, ready-flag-last preserved) and
bypasses the MAC filter while the capture window is open. Capture runs
in the main-loop drain on the parsed pairingActive bit: persist first,
then the RAM filter, then close the window — a failed SD write retries
on the next frame. 120 s window timeout; bench latch (eggTestActive)
and the pairing window now force the race-gated scanner on; packet-rate
meter for the test page. Twelve new public accessors with no-op twins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Main-menu row 'Egg' (index 6, appended after Camera so nothing shifts),
PAGE_PAIR_EGG (-20) cloning the camera page's shape — paired =
Back/Test/Unpair with Back at index 0, unpaired = window-gated capture
status with B2 cancel and B1 reserved for future manual entry — and
PAGE_EGG_TEST (-21): rf tri-state + proto version, EGT/CJ/AUX/BAT,
sequence + measured packet rate, live PAIR/FAULT flags, active MAC
filter, Back. All five display_ui coupling points wired under the
build flag; the camera test page now latches the egg bench scan on
enter/exit, restoring the desk-soak behavior its comment promised
(stale since plan 0012's race gate — comment fixed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Flag-0 sim never renders the Egg pages; stubs mirror the module's full
surface so a future flag-1 sim build links. Golden hashes verified
UNCHANGED — the gate does not leak into stock renders.

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

Also fixes the two stale subsystem-14 blocks that still described a
PW-ADV-1-only 14-byte parser and hardcoded-MAC-only pairing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
plan 0017: SensorEgg pairing menu + live-data test page
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