mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 13:24:13 +08:00
Refine native RoadScore hierarchy and show only played cues
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user