Repository navigation
feat(ui): give Race its own screen, with the lap times it was computing and dropping - #224
Merged
Merged
Conversation
…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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 theUiSnapshotevery service tick, andui::present()formats all three. Nothing on the device drew that model —ui::presentwas called only from three host tests. Race borrowedTrackdayScreen, which has no lap labels at all, and says so itself:Same pattern as #18, #204, #214 and #222: built, host-tested, never wired.
The screen
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
DeviceViewModelverbatim 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_foundationnow 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.
kMaximumSizes6 → 8. Two new numeral sizes could otherwise exhaust the font cache, which fails soft —countdown_fontreturnsnullptrand 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=0test_ui_foundation.cpp: both lap figures read--:--.---before anything is measured,1:23.456/1:21.002when they are,11:05for the session clock, and a 12-minute lap formats to nine charactersA 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