From 97aada4e01c640407918b53e9ed4bdd11064b2f5 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sat, 19 Sep 2026 16:19:31 -0700 Subject: [PATCH] Resolve private local route favorites in normal RoadScore launch --- roadscore/prototype/normal_onroad.py | 6 +++++- roadscore/prototype/route_favorites.py | 22 ++++++++++++++++++++ roadscore/prototype/test_route_favorites.py | 23 +++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 roadscore/prototype/route_favorites.py create mode 100644 roadscore/prototype/test_route_favorites.py diff --git a/roadscore/prototype/normal_onroad.py b/roadscore/prototype/normal_onroad.py index ee7ae7ce47..f50435482f 100644 --- a/roadscore/prototype/normal_onroad.py +++ b/roadscore/prototype/normal_onroad.py @@ -15,7 +15,7 @@ R=Path(__file__).resolve().parents[1] native=Path('/TICI').exists() def interrupt(*_):raise KeyboardInterrupt signal.signal(signal.SIGTERM,interrupt) -p=argparse.ArgumentParser();p.add_argument('--roadscore-seed',type=seed_argument,help='Reproduce an ACE session; normal launches choose a fresh seed');p.add_argument('--render-mode',choices=['current','gold-core'],default=None,help='ACE rendering mode');p.add_argument('--roadscore-presentation',choices=['conservative-v1','off','frozen']);p.add_argument('route',nargs='?');p.add_argument('--routeid');p.add_argument('--roadscore',action='store_true',required=True);p.add_argument('--replay',action='store_true',help='Play recorded final score without Chestnut');p.add_argument('--start',type=int,default=0);p.add_argument('--duration',type=float,default=float('inf'),help='Optional duration limit; normally replay to route EOF');p.add_argument('--audible',action='store_true',help='Compatibility flag; output is audible by default outside automated sessions');p.add_argument('--muted',action='store_true');p.add_argument('--no-overlay',action='store_true');p.add_argument('--capture-ui',action='store_true',help='Record the normal UI internally without speaker output');p.add_argument('--audio-device',default=None,help='Development host output device; default is the system output');p.add_argument('--transport-only',action='store_true');p.add_argument('--headless',action='store_true');p.add_argument('--runtime',type=Path,default=Path('/data/openpilot') if native else Path(os.environ.get('ROADSCORE_RUNTIME','/Users/dominickthompson/starpilot/.host_runtime/darwin/worktree')));p.add_argument('--bench',default=device_target());p.add_argument('--composer',choices=['sa3','ace'],default=choice(),help='ACE Prism is the event default; SA3 is an explicit fallback');p.add_argument('--profile',choices=['prism','aurora'],default='prism');a=p.parse_args() +p=argparse.ArgumentParser();p.add_argument('--roadscore-seed',type=seed_argument,help='Reproduce an ACE session; normal launches choose a fresh seed');p.add_argument('--render-mode',choices=['current','gold-core'],default=None,help='ACE rendering mode');p.add_argument('--roadscore-presentation',choices=['conservative-v1','off','frozen']);p.add_argument('route',nargs='?');p.add_argument('--routeid');p.add_argument('--roadscore',action='store_true',required=True);p.add_argument('--replay',action='store_true',help='Play recorded final score without Chestnut');p.add_argument('--start',type=int,default=None);p.add_argument('--duration',type=float,default=float('inf'),help='Optional duration limit; normally replay to route EOF');p.add_argument('--audible',action='store_true',help='Compatibility flag; output is audible by default outside automated sessions');p.add_argument('--muted',action='store_true');p.add_argument('--no-overlay',action='store_true');p.add_argument('--capture-ui',action='store_true',help='Record the normal UI internally without speaker output');p.add_argument('--audio-device',default=None,help='Development host output device; default is the system output');p.add_argument('--transport-only',action='store_true');p.add_argument('--headless',action='store_true');p.add_argument('--runtime',type=Path,default=Path('/data/openpilot') if native else Path(os.environ.get('ROADSCORE_RUNTIME','/Users/dominickthompson/starpilot/.host_runtime/darwin/worktree')));p.add_argument('--bench',default=device_target());p.add_argument('--composer',choices=['sa3','ace'],default=choice(),help='ACE Prism is the event default; SA3 is an explicit fallback');p.add_argument('--profile',choices=['prism','aurora'],default='prism');a=p.parse_args() if a.render_mode=='gold-core' and a.composer!='ace':raise SystemExit('Gold core requires ACE') if a.roadscore_seed is not None and (a.replay or a.composer!='ace'):raise SystemExit('--roadscore-seed applies only to fresh ACE generation') session=None @@ -27,6 +27,10 @@ a.render_mode=presentation['render_mode'] composition_policy='hook-v2' if hook_enabled(session,a.replay,a.composer) and not a.transport_only else 'prepared-v1' from settings import Settings,resolve_route a.routeid=resolve_route(p,a.route,a.routeid) +from route_favorites import resolve_favorite +try:a.routeid,favorite_start=resolve_favorite(a.routeid) +except (ValueError,KeyError) as error:p.error(str(error)) +if a.start is None:a.start=favorite_start settings=Settings(mode='stored' if a.replay else 'generate',muted=a.muted,overlay=not a.no_overlay,output_device=a.audio_device) a.audible=not settings.effective_muted(a.headless) rt=a.runtime;py=Path('/usr/local/venv/bin/python') if native else rt/'.venv/bin/python3';replay=R/'native_build/replay' if native else rt/'tools/replay/replay' diff --git a/roadscore/prototype/route_favorites.py b/roadscore/prototype/route_favorites.py new file mode 100644 index 0000000000..d40294f2aa --- /dev/null +++ b/roadscore/prototype/route_favorites.py @@ -0,0 +1,22 @@ +"""Local route shortcuts; private route identities stay out of source control.""" +import json +from pathlib import Path + + +def resolve_favorite(value, path=None): + path = Path(path) if path else Path(__file__).resolve().parents[1] / 'routes/favorites.json' + if '/' in value or '|' in value: + return value, 0 + if not path.is_file(): + raise ValueError(f'Unknown route shortcut {value!r}; configure {path}') + favorites = json.loads(path.read_text()) + entry = favorites.get(value) + if not isinstance(entry, dict): + raise ValueError(f'Unknown route shortcut {value!r}; available: {", ".join(sorted(favorites))}') + from route_library import identity + route = entry['route'] + identity(route) + start = entry.get('start', 0) + if type(start) is not int or start < 0: + raise ValueError('Favorite start must be nonnegative whole seconds') + return route, start diff --git a/roadscore/prototype/test_route_favorites.py b/roadscore/prototype/test_route_favorites.py new file mode 100644 index 0000000000..21a01345a7 --- /dev/null +++ b/roadscore/prototype/test_route_favorites.py @@ -0,0 +1,23 @@ +import json +from pathlib import Path +import tempfile +import unittest +from route_favorites import resolve_favorite + + +class FavoriteTests(unittest.TestCase): + def test_ids_do_not_require_alias_file(self): + route = '0123456789abcdef/00000001--1234567890' + self.assertEqual(resolve_favorite(route, '/missing'), (route, 0)) + + def test_shortcut_keeps_route_and_excerpt_together(self): + with tempfile.TemporaryDirectory() as directory: + p = Path(directory) / 'favorites.json' + route = '0123456789abcdef/00000001--1234567890' + p.write_text(json.dumps({'route1': {'route': route, 'start': 149}})) + self.assertEqual(resolve_favorite('route1', p), (route, 149)) + with self.assertRaisesRegex(ValueError, 'Unknown'): + resolve_favorite('route2', p) + p.write_text(json.dumps({'route1': {'route': route, 'start': -1}})) + with self.assertRaisesRegex(ValueError, 'nonnegative'): + resolve_favorite('route1', p)