From 11530357c93c8c3824ea6a644bd01483dedd2448 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sat, 19 Sep 2026 11:35:25 -0700 Subject: [PATCH] Replace RoadScore panel with compact camera-edge ribbon --- roadscore/DEMO.md | 4 +- roadscore/prototype/overlay_view.py | 77 +++++++++++++++-------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/roadscore/DEMO.md b/roadscore/DEMO.md index 5b77648301..613cddc64e 100644 --- a/roadscore/DEMO.md +++ b/roadscore/DEMO.md @@ -6,7 +6,7 @@ The conductor receives only road messages already delivered by replay. It reques ## Reading the overlay -The lower-left panel is 94–112 logical pixels tall on the comma four's 536 × 240 canvas, with width limited to 55% of the screen. The existing camera, lane and path display stays in place. Its rows show RoadScore's activity, profile, section, compute backend and buffered music. +A 34-pixel translucent ribbon sits at the lower-left edge of the comma four's 536 × 240 canvas, leaving the road visible. Its width follows the content, capped at 300 pixels. Profile and section lead, with a compact state indicator; the second line shows RoadScore, backend or a contextual event, and a quiet buffer time. Degraded explanations take priority over other detail. | Display | Meaning | | --- | --- | @@ -40,6 +40,6 @@ python3 roadscore/tools/preview_overlay.py --output roadscore/results/ui-polish/ The preview renders six full 536 × 240 synthetic canvases with the repository's Inter bitmap font atlas: preparation, ready, generating, accepted-music hold, failed composer and stored score. It uses CPU image drawing and does not open a window, contact a device, start a worker, run replay or open an audio stream. It is a layout review, not a native-rendering or hardware-validation claim. The output stays in ignored `results/`. -Generation timing and road gestures have separate space in the footer, so an active job cannot hide the event. The startup label uses the selected Prism/Aurora profile; stored playback is labeled as such from preparation onward. +Road gestures take priority over job timing in the detail row; degraded explanations take priority over both. The startup label uses the selected Prism/Aurora profile; stored playback is labeled as such from preparation onward. The active native overlay is `prototype/overlay.py`; the pure presentation helper is `prototype/overlay_view.py`. `prototype/index.html` and `prototype/native_display.py` are older excerpt-specific bench interfaces, not the normal onroad demo. The historical style selector is not an ACE profile selector. Keep those older entrypoints distinct when presenting the current demo. diff --git a/roadscore/prototype/overlay_view.py b/roadscore/prototype/overlay_view.py index 8651596449..d317140d37 100644 --- a/roadscore/prototype/overlay_view.py +++ b/roadscore/prototype/overlay_view.py @@ -65,45 +65,48 @@ def fit_text(text, max_width, measure): def draw_panel(rl, font, state, screen_width, screen_height): + """A content-sized edge ribbon; leave the road and native right rail clear.""" view = overlay_view(state) - width = min(320, screen_width * .55, screen_width - 24) - height = 112 if view['note'] or view['event'] else 94 - x, y = 12, max(12, screen_height - height - 12) color = {'READY': (123, 229, 193), 'GENERATING': (131, 192, 255), - 'DEGRADED': (255, 197, 112), 'PREPARING': (180, 190, 204)}[view['activity']] + 'DEGRADED': (255, 197, 112), 'PREPARING': (196, 204, 214)}[view['activity']] accent = rl.Color(*color, 255) - muted = rl.Color(180, 190, 204, 255) - rl.draw_rectangle_rounded(rl.Rectangle(x, y, width, height), .12, 8, rl.Color(15, 22, 31, 235)) - - def text(label, left, top, size=13, tint=rl.WHITE, available=None): - available = width - left - 12 if available is None else available - label = fit_text(label, available, lambda s: rl.measure_text_ex(font, s, size, 0).x) - rl.draw_text_ex(font, label, rl.Vector2(x + left, y + top), size, 0, tint) - - badge_width = rl.measure_text_ex(font, view['activity'], 11, 0).x + 16 - rl.draw_rectangle_rounded(rl.Rectangle(x + width - badge_width - 10, y + 10, badge_width, 22), .4, 8, rl.Color(*color, 28)) - text(view['activity'], width - badge_width - 2, 15, 11, accent) - text('RoadScore', 12, 12, 17, available=width - badge_width - 34) - text(view['profile'].upper(), 12, 35, 15) - text(view['section'], 12, 55, 11, muted) + muted = rl.Color(210, 218, 225, 255) + measure = lambda label, size: rl.measure_text_ex(font, label, size, 0).x + section = view['section'].removeprefix('INTENT: ') + if section in ('ARCHIVED SCORE', 'WAITING FOR SCORE'): + section = '' + elif state.get('form_labels_are_intent'): + section = 'intent ' + section.title() + else: + section = section.title() + primary = view['profile'] + (' / ' + section if section else '') + if view['activity'] == 'DEGRADED': + detail = view['note'] + elif view['event']: + detail = view['event'].capitalize() + elif view['activity'] == 'GENERATING': + detail = view['note'] + else: + detail = 'Archived playback' if view['stored'] else ' / '.join(filter(None, view['backend'])) + secondary = 'RoadScore' + (' / ' + detail if detail else '') buffer = view['buffered'] - label = 'BUFFER ' + ('--' if buffer is None else f'{buffer:.0f}s') - label_width = rl.measure_text_ex(font, label, 11, 0).x - buffer_left = width - label_width - 12 - text(label, buffer_left, 75, 11, accent) - left, right = view['backend'] - text(left, 12, 75, 11, muted, available=buffer_left - 24) - if right: - offset = 12 + rl.measure_text_ex(font, left, 11, 0).x - # A geometric middle dot avoids missing-glyph boxes in the native font. - rl.draw_circle(int(x + offset + 7), int(y + 81), 1.5, muted) - text(right, offset + 15, 75, 11, muted, available=buffer_left - offset - 27) - if view['note'] and view['event']: - # Give road gestures their own space so job timing cannot push them offscreen. - event_width = min((width - 36) / 2, rl.measure_text_ex(font, view['event'], 10, 0).x) - event_left = width - event_width - 12 - text(view['note'], 12, 95, 10, accent, available=event_left - 24) - text(view['event'], event_left, 95, 10, muted, available=event_width) - elif view['note'] or view['event']: - text(view['note'] or view['event'], 12, 95, 10, accent) + reserve = '--' if buffer is None else f'{buffer:.0f}s' + activity_width = measure(view['activity'], 9) + max_width = min(300, screen_width * .64, screen_width - 24) + width = min(max_width, max(210, measure(primary, 11) + activity_width + 38, + measure(secondary, 9) + measure(reserve, 9) + 30)) + x, y, height = 12, max(12, screen_height - 42), 34 + # One quiet translucent surface, with no enclosing badge or oversized title. + rl.draw_rectangle_rounded(rl.Rectangle(x, y, width, height), .4, 8, rl.Color(8, 13, 18, 184)) + def text(label, left, top, size, tint, available): + label = fit_text(label, max(0, available), lambda value: measure(value, size)) + rl.draw_text_ex(font, label, rl.Vector2(x + left, y + top), size, 0, tint) + activity_left = width - activity_width - 9 + text(primary, 9, 5, 11, rl.WHITE, activity_left - 24) + rl.draw_circle(int(x + activity_left - 7), int(y + 10), 2, accent) + text(view['activity'], activity_left, 6, 9, accent, activity_width + 1) + reserve_width = measure(reserve, 9) + text(secondary, 9, 21, 9, accent if view['activity'] == 'DEGRADED' else muted, + width - reserve_width - 29) + text(reserve, width - reserve_width - 9, 21, 9, muted, reserve_width + 1) return view