From cc291d08bd7ef5a05b0e00927ab1abf9bb0fce96 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sat, 19 Sep 2026 13:35:07 -0700 Subject: [PATCH] Refine native RoadScore hierarchy and show only played cues --- roadscore/prototype/overlay_view.py | 44 ++++++++-------------------- roadscore/tests/test_overlay_view.py | 22 +++++++++----- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/roadscore/prototype/overlay_view.py b/roadscore/prototype/overlay_view.py index ed295210d1..27ae304305 100644 --- a/roadscore/prototype/overlay_view.py +++ b/roadscore/prototype/overlay_view.py @@ -35,24 +35,11 @@ GESTURE_LABELS = { def gesture_view(state): active = state.get('gesture_active') active = [kind for kind in active if isinstance(kind, str)] if isinstance(active, (list, tuple)) else [] - queued = state.get('gesture_queued') - queued = [item.get('kind') for item in queued if isinstance(item, dict) and isinstance(item.get('kind'), str)] if isinstance(queued, (list, tuple)) else [] for kind, (_, label) in GESTURE_LABELS.items(): if kind in active: return label, 'active', kind if active: return 'Music cue', 'active', None - for kind in queued: - if kind in GESTURE_LABELS: - return 'Next: ' + GESTURE_LABELS[kind][0], 'queued', kind - if queued: - return 'Next: music cue', 'queued', None - # This legacy flag can include queued cues; it does not prove audible playback. - if state.get('turn_signal_music'): - return 'Turn signal cue', 'reported', 'turn_signal' - lead = seconds(state.get('lead')) - if state.get('kind') == 'curve' and state.get('phase') == 'anticipation' and lead is not None and lead > 0: - return f'Curve ahead / {lead:.1f}s', 'anticipated', 'curve_prepare' return '', '', None @@ -102,30 +89,19 @@ class EventPresentation: def __init__(self): self.last_active = None self.last_active_at = -math.inf - self.pending = None - self.pending_since = 0. def update(self, view, now): result = dict(view) if view['activity'] == 'DEGRADED': self.last_active = None - self.pending = None return result if view['event_state'] == 'active': self.last_active = view['event'].split(' / ')[0] self.last_active_at = now - self.pending = None return result if self.last_active and now - self.last_active_at < 2.5: result.update(event='Recent: ' + self.last_active, event_state='recent', event_kind=None) return result - if view['event_state'] == 'queued': - if view['event'] != self.pending: - self.pending, self.pending_since = view['event'], now - if now - self.pending_since < .4: - result.update(event='', event_state='', event_kind=None) - else: - self.pending = None return result @@ -154,13 +130,14 @@ def draw_panel(rl, font, state, screen_width, screen_height, emphasis_font=None, subtitle = identity + (' / ' + section if section else '') title = 'RoadScore' if view['activity'] == 'DEGRADED': - subtitle = 'DEGRADED / ' + ('Music on hold' if state.get('holding_accepted_music') else 'Composer unavailable') + subtitle = ('Music on hold' if state.get('holding_accepted_music') else + 'Composer unavailable' if state.get('worker_failed') else 'Reserve in use') elif view['event']: - subtitle = identity + ' / ' + view['event'].split(' / ')[0] + subtitle = view['event'].split(' / ')[0] elif view['activity'] == 'GENERATING': subtitle = identity + ' / Composing' elif view['activity'] == 'PREPARING': - subtitle = identity + ' / Preparing' + subtitle = 'Preparing music' if identity == 'Preparing' else identity + ' / Preparing' # Match the native steering wheel's 50px identity, with fixed text anchors. rl.draw_circle(int(x + 25), int(y + 29), 25, rl.Color(0, 0, 0, 150)) icon_color = accent if view['activity'] == 'DEGRADED' else rl.WHITE @@ -168,12 +145,15 @@ def draw_panel(rl, font, state, screen_width, screen_height, emphasis_font=None, rl.draw_circle(int(x + 33), int(y + 34), 5, icon_color) for left, top, w, h in ((17, 14, 3, 24), (36, 10, 3, 24), (17, 10, 22, 4)): rl.draw_rectangle_rounded(rl.Rectangle(x + left, y + top, w, h), .2, 4, icon_color) - def text(label, top, size, face, tint, available): + def text(label, top, size, face, tint, available, left=64): label = fit_text(label, available, lambda value: rl.measure_text_ex(face, value, size, 0).x) # Native HUD text uses local shadows; avoid an opaque rectangle over the road. for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1), (1, 2)): - rl.draw_text_ex(face, label, rl.Vector2(x + 64 + dx, y + top + dy), size, 0, rl.Color(0, 0, 0, 210)) - rl.draw_text_ex(face, label, rl.Vector2(x + 64, y + top), size, 0, tint) - text(title, 5, 20, title_font, accent if view['activity'] == 'DEGRADED' else rl.WHITE, width - 64) - text(subtitle, 33, 14, font, muted, width - 64) + rl.draw_text_ex(face, label, rl.Vector2(x + left + dx, y + top + dy), size, 0, rl.Color(0, 0, 0, 210)) + rl.draw_text_ex(face, label, rl.Vector2(x + left, y + top), size, 0, tint) + text(title, 5, 20, title_font, accent if view['activity'] == 'DEGRADED' else rl.WHITE, 118) + reserve = '' if view['stored'] or view['buffered'] is None else f"{int(view['buffered'])}s buffer" + if reserve: + text(reserve, 11, 12, font, accent, width - 190, left=190) + text(subtitle, 33, 16, font, muted, width - 64) return view diff --git a/roadscore/tests/test_overlay_view.py b/roadscore/tests/test_overlay_view.py index 66820a9bff..fa87b08737 100644 --- a/roadscore/tests/test_overlay_view.py +++ b/roadscore/tests/test_overlay_view.py @@ -22,10 +22,10 @@ class OverlayTests(unittest.TestCase): self.assertTrue(view['ready']) self.assertIn('12.3s', view['note']) - def test_gesture_remains_separate_from_generation_timing(self): + def test_legacy_signal_does_not_claim_audible_cue(self): view = overlay_view(dict(readiness='READY', job_inflight=True, generation_elapsed_seconds=123.4, turn_signal_music=True)) - self.assertEqual(view['event'], 'Turn signal cue') + self.assertEqual(view['event'], '') self.assertIn('123.4s', view['note']) def test_active_cue_precedes_queued_and_persistent_signal(self): @@ -34,17 +34,23 @@ class OverlayTests(unittest.TestCase): self.assertEqual(view['event'], 'Curve apex / impact') self.assertEqual(view['event_state'], 'active') - def test_queued_cue_does_not_claim_current_music(self): + def test_queued_cue_is_hidden(self): view = overlay_view(dict(turn_signal_music=True, gesture_queued=[{'kind': 'turn_signal'}])) - self.assertEqual(view['event'], 'Next: Turn signal') - self.assertEqual(view['event_state'], 'queued') + self.assertEqual(view['event'], '') + self.assertEqual(view['event_state'], '') + + def test_signal_sequence_and_grid_are_not_proof_of_played_audio(self): + for rhythm_enabled in (False, True): + view = overlay_view({'readiness': 'READY', 'signal_shaker': { + 'enabled': True, 'sequence_active': True, 'rhythm_enabled': rhythm_enabled}}) + self.assertEqual(view['event'], '') def test_unknown_and_malformed_cues_do_not_invent_a_road_reason(self): self.assertEqual(overlay_view({'gesture_active': ['unknown_future_kind']})['event'], 'Music cue') self.assertEqual(overlay_view({'gesture_active': 'curve_apex', 'gesture_queued': [None, {}, 3]})['event'], '') self.assertEqual(overlay_view({'lead': 4})['event'], '') self.assertEqual(overlay_view(dict(kind='navigation', phase='anticipation', lead=4))['event'], '') - self.assertEqual(overlay_view(dict(kind='curve', phase='anticipation', lead=4))['event'], 'Curve ahead / 4.0s') + self.assertEqual(overlay_view(dict(kind='curve', phase='anticipation', lead=4))['event'], '') def test_native_slot_clears_speed_sign_driver_and_steering(self): x, y, width, height = hud_bounds(536, 240) @@ -68,11 +74,11 @@ class OverlayTests(unittest.TestCase): self.assertEqual(apex['event'], 'Curve apex / impact') self.assertEqual(presenter.update(overlay_view({'readiness': 'READY'}), 3)['event'], '') - def test_queue_debounce_and_degraded_never_wait(self): + def test_hidden_queue_and_degraded_never_wait(self): presenter = EventPresentation() queued = overlay_view(dict(gesture_queued=[{'kind': 'curve_apex'}], readiness='READY')) self.assertEqual(presenter.update(queued, 0)['event'], '') - self.assertEqual(presenter.update(queued, .5)['event'], 'Next: Curve apex') + self.assertEqual(presenter.update(queued, .5)['event'], '') degraded = presenter.update(overlay_view(dict(readiness='DEGRADED', holding_accepted_music=True)), .6) self.assertEqual(degraded['activity'], 'DEGRADED') self.assertEqual(degraded['note'], 'Holding accepted music')