Skip to content

feat(ui): give Race its own screen, with the lap times it was computing and dropping - #224

Merged
PurpleSentinel merged 1 commit into
mainfrom
feat/221-race-lap-times
Oct 17, 2026
Merged

PurpleSentinel merged 1 commit into
mainfrom
feat/221-race-lap-times

Conversation

@PurpleSentinel

Copy link
Copy Markdown
Contributor

Closes #221. Found by road test: the GPS trigger armed and started the session on pit exit, laps were driven, and no lap time appeared at any point.

The times were never missing

apply_lap_state() fills current, previous and best into the UiSnapshot every service tick, and ui::present() formats all three. Nothing on the device drew that model — ui::present was called only from three host tests. Race borrowed TrackdayScreen, which has no lap labels at all, and says so itself:

Track Day deliberately shows no lap times while running... this screen simply has nowhere to put a lap time.

Same pattern as #18, #204, #214 and #222: built, host-tested, never wired.

The screen

Test Circuit                              RACE

                CURRENT LAP
                 12:34.567

FASTEST                          SESSION LEFT
1:21.002                                 19:45

Separate from TrackdayScreen, not lap fields added to it under a flag. Track Day withholding live lap times is the compliance rule #132 established, not a display preference, and a screen with no lap labels cannot leak one through a mode check that is wrong. running_screen() states the choice once, so the session lifecycle has a single place deciding where a running session goes.

It takes the presenter's DeviceViewModel verbatim rather than formatting anything itself, so --:--.--- before the first lap is the presenter's tested behaviour rather than a fourth copy of lap-time formatting.

Details worth the review

Nine cells, not eight. A lap past ten minutes formats to 12:34.567 — nine characters. An eight-cell row would drop the leading digit. test_ui_foundation now asserts that length.

Repaint discipline, because of #213: cells are rewritten only when their character changes, and the screen is only updated while it is the visible one. The lap in progress changes on every fix; the other two almost never.

kMaximumSizes 6 → 8. Two new numeral sizes could otherwise exhaust the font cache, which fails soft — countdown_font returns nullptr and the glyphs silently do not appear. A silent cap is how the carousel came to be unselectable once before.

Layout is derived, not placed. The supporting row is anchored to the bottom margin; the lap in progress is centred in the band left between it and the title. static_asserts hold that the rows cannot collide, so moving one does not unbalance the other.

Verification

  • make check → CHECK_RC=0
  • firmware builds clean
  • Flashed and reviewed on hardware over three layout iterations
  • New assertions in test_ui_foundation.cpp: both lap figures read --:--.--- before anything is measured, 1:23.456 / 1:21.002 when they are, 11:05 for the session clock, and a 12-minute lap formats to nine characters

A lap time still cannot be confirmed end to end on the bench — that needs a drive, and #222 means an interrupted session leaves no record, so the re-test has to be stopped deliberately on the device before power-off.

🤖 Generated with Claude Code

…ng and dropping

A road test started a Race session on pit exit, ran laps, and showed no lap time
at any point. The times were not missing: apply_lap_state fills current,
previous and best into the UiSnapshot on every service tick, and ui::present
formats all three. Nothing on the device drew that model - ui::present was
called only from three host tests. Race borrowed TrackdayScreen, which has no
lap labels at all, and whose own header says so: "this screen simply has nowhere
to put a lap time".

RaceScreen shows the lap in progress in mm:ss.sss, the fastest lap of the
session, and the session time remaining.

Separate from TrackdayScreen rather than lap fields added to it under a mode
flag. Track Day withholding live lap times is the compliance rule #132
established, not a display preference, and a screen with no lap labels cannot
leak one through a mode check that is wrong. running_screen() states the choice
once, so the session lifecycle has one place that decides where a running
session goes.

It takes the DeviceViewModel the presenter already produces rather than
formatting anything itself, so "--:--.---" for a lap nothing has measured yet is
the presenter's behaviour and not a fourth copy of lap-time formatting. Those
three strings are now asserted in test_ui_foundation, including that a lap past
ten minutes formats to nine characters - which is why the row is nine fixed
cells rather than eight, so such a lap reads rather than losing its leading
digit.

Cells are rewritten only when their character changes. The lap in progress
advances on every fix while the other two almost never do, and a repaint is the
most expensive thing this device does (#213); the screen is also only updated
while it is the visible one.

kMaximumSizes goes from 6 to 8. Two new numeral sizes would otherwise have been
able to exhaust the font cache, which fails soft - countdown_font returns
nullptr and the glyphs simply do not appear - and a silent cap is how the
carousel came to be unselectable once before.

Layout is derived rather than placed: the supporting row is anchored to the
bottom margin and the lap in progress is centred in the band left between it and
the title, with static_asserts that the rows cannot collide. Moving one no
longer unbalances the other.

Closes #221

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PurpleSentinel
PurpleSentinel merged commit b49568a into main Oct 17, 2026
3 checks passed
@PurpleSentinel
PurpleSentinel deleted the feat/221-race-lap-times branch October 17, 2026 16:11
PurpleSentinel added a commit that referenced this pull request Oct 21, 2026
…it never drew (#231)

Issue #219 and #221 are the same road test: a Race session started on pit exit,
timed laps, and showed none of them. #221 was fixed in #224, which gave Race its
own screen. Three of #219's acceptance criteria were still open.

The routing was the important one. Track Day withholding live lap times is a
compliance rule, and the screen it draws has no lap labels anywhere in it - so
the mode comparison inside the router's screen lookup was the only thing
enforcing that rule, and nothing asserted it. ui::running_screen_for states it
once, defaults to the screen that cannot show a lap time, and the tests pin it
for every mode plus the invariant that ties the two rules together: a mode is
drawn on the Race screen exactly when it is permitted lap times. Grant laps to a
mode routed to TrackdayScreen and they would never appear, which is the bug this
issue reported.

lap_delta is now false everywhere. Race was granted it when its running screen
had no lap fields at all; the screen it has shows three figures and no delta, and
the reference mapping a trustworthy delta needs is still #58. A permission for
something nothing draws describes a display that does not exist.

The Race screen's track name is written only when it changes. The router handed
it over every display tick while the armed track cannot change during a session,
and lv_label_set_text invalidates whether or not the text differs - a repaint of
the widest label on the screen 25 times a second, beside three lap rows already
change-gated for that reason (#213).

Closes #219

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

Race mode shows no lap times: the screen has nowhere to put them

1 participant