Skip to content

plan 0016: Manual drag mode — the christmas tree - #161

Open
TheAngryRaven wants to merge 1 commit into
BETAfrom
claude/drag-mode-planning-mggk0b
Open

plan 0016: Manual drag mode — the christmas tree#161
TheAngryRaven wants to merge 1 commit into
BETAfrom
claude/drag-mode-planning-mggk0b

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Summary

Drag now asks Automatic or Manual after the distance (new PAGE_DRAG_MODE). Automatic is plan 0015's behavior, unchanged. Manual stages like a real strip:

  • Stop, and the 9-px LED bar lights a white staging pip (----w----), then three yellows at the sportsman 500 ms cadence (---ywy-----yywyy---yyywyyy-), then green (gyyywyyyg) — mirrored on the OLED as STOP TO STAGE, a big 3…2…1 countdown (size-7 glyphs), and a flashing GO.
  • Movement during the yellows = RED LIGHT foul; 5 s still after green = FAILED TO LAUNCH; a mid-run physics abort surfaces as RUN ABORTED — all three flash the strip red and wait for a button.
  • Runs end on a results screen: ET, trap, 0-60, and a new reaction time (green light → interpolated rollout crossing, both Unix-epoch ms, so RT inherits the ET's sub-fix precision). RT is display-only — the DOVEX format is untouched.
  • The screen is pinned to staging info for the whole manual session (gpsLockHold construction; presses are consumed by the new dragStagingLoop() before displayLoop()); hold Select 2 s in any non-running state to end the session (a held side button disarms it so the reboot combo wins; auto-idle stays the backstop).

Mechanism: the new host-tested pure unit drag_tree.{h,cpp} is the ONE state machine driving both the LED strip (renderStrip) and the OLED (countdownDigit/flashPhase) so the two can never disagree; flash phases derive from timestamps at a 500 ms half-period (the 3 Hz OLED aliases anything faster). The tree never duplicates physics — it observes drag_timer, whose new launch gate (setLaunchEnabled, default true = automatic byte-identical) stays closed except while the tree shows green, so a pre-green move re-arms instead of ever becoming a run. The LED tree renders strip-only as a new arm of the NeoPixel strip cascade (status LEDs stay live; search pip wins without a fix; the normal RPM/speed scale takes over during the pass itself).

Also fixes a plan-0015 layout bug found while building the results screen: subtext printed after size-3 text with size-1 newlines rendered on the ET's bottom glyph rows (lap-time page + both new screens now advance with a size-3 newline).

Design record: docs/plans/0016-manual-drag-tree.md.

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)

How it was verified

  • Host unit tests pass (ctest --test-dir tests/build) — 607 cases: 17 new drag_tree cases (exact stage-entry timings, fouls from every yellow, failed-launch, green-no-run and mid-run aborts, exit-hold semantics incl. the results-press pass-through and side-button disarm, golden strip frames for every stage + both flash phases) and 3 new drag_timer cases (launch gate held/re-enabled, runStartEpochMs vs the analytic crossing)
  • clang-tidy clean — left to CI
  • Compiles for the XIAO nRF52840 Sense — left to CI (native sim build of the full firmware TU compiles clean against DovesLapTimer BETA)
  • Tested on real hardware — not yet; sim-verified end-to-end: the golden walk now runs a full manual pass through the real pipeline (arm → park → tree → green → 70 mph launch → results with RT 0.68 and ET exactly matching the analytic 660 ft pass), 23 fixtures regenerated, soak/determinism/both lap oracles/two-session carryover all pass, and all six new screens were eyeballed from PNG dumps

Checklist

  • CHANGELOG.md updated under [Unreleased] (if user-visible)
  • ARCHITECTURE.md / CLAUDE.md updated (if a module or interface changed)
  • New testable logic has a matching test in tests/
  • Branch is focused — refactors / behavior / tests are not mixed together

Related issues

Builds on plan 0015 (PR #148, merged). Design record: docs/plans/0016-manual-drag-tree.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BKa62h4fhn61pKn1LygrMK


Generated by Claude Code

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
@github-actions

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 2116/2149 (98.5%)
Functions 🟢 214/215 (99.5%)
Branches 🟢 1535/1704 (90.1%)

📄 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 🟢 44/45 (97.8%) 🟢 7/7 (100.0%) 🟢 33/34 (97.1%)
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%)

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