From 70db10df004c7e4f0981ff1837fa79d57824753d Mon Sep 17 00:00:00 2001 From: Aidan Foster Date: Tue, 1 Sep 2026 11:20:33 -0700 Subject: [PATCH 1/3] [crazy-robotaxi] Remove legacy map variants --- .../crazy_robotaxi/application.py | 22 +- .../crazy_robotaxi/game_selection.py | 3 - .../maps/boulevard_district.robotaxi.yaml | 46 ++-- .../maps/flashdreams_raceway.robotaxi.yaml | 56 +--- apps/crazy_robotaxi/crazy_robotaxi/session.py | 1 - .../tests/maps/race_course.robotaxi.yaml | 6 +- apps/crazy_robotaxi/tests/test_maps.py | 102 ++------ apps/crazy_robotaxi/tests/test_ui.py | 5 - .../NODE_GRAPH_MAP_FORMAT.md | 51 +--- .../omnidreams_game_engine/engine_settings.py | 6 - .../game_map/_schema.py | 63 ++--- .../game_map/compiler.py | 53 ++-- .../omnidreams_game_engine/game_map/loader.py | 13 +- .../omnidreams_game_engine/game_map/types.py | 77 +----- .../omnidreams_game_engine/scene.py | 12 - .../omnidreams_game_engine/scene_loader.py | 240 +----------------- apps/omnidreams_game_engine/pyproject.toml | 2 - uv.lock | 2 - 18 files changed, 121 insertions(+), 639 deletions(-) diff --git a/apps/crazy_robotaxi/crazy_robotaxi/application.py b/apps/crazy_robotaxi/crazy_robotaxi/application.py index 5b4d7279c..0ebe56c5e 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/application.py +++ b/apps/crazy_robotaxi/crazy_robotaxi/application.py @@ -264,7 +264,6 @@ def init(self, commandline_args: Sequence[str]) -> None: scene_request=SceneRequest( map_path=map_path.expanduser(), camera_name=engine_settings.map.camera, - variant=engine_settings.map.variant, prompt=engine_settings.map.prompt, use_prompt_context=game_settings.live_edit.map_context.enabled, force_recompile=engine_settings.map.force_recompile, @@ -306,10 +305,7 @@ def init(self, commandline_args: Sequence[str]) -> None: live_edit=game_settings.live_edit, visual_flare_enabled=game_settings.effects.visual_flare_enabled, ) - self._map_options = _discover_game_maps( - map_path, - requested_variant=engine_settings.map.variant, - ) + self._map_options = _discover_game_maps(map_path) def _resolve_engine_settings(self, args: argparse.Namespace) -> EngineSettings: settings = EngineSettings( @@ -326,7 +322,6 @@ def _resolve_engine_settings(self, args: argparse.Namespace) -> EngineSettings: settings.map, path=args.map, camera=args.camera, - variant=args.variant, prompt=args.prompt, force_recompile=args.force_map_recompile, ), @@ -466,11 +461,7 @@ def _build_pipeline(config: Any, device: str) -> Any: return config.setup().to(device).eval() -def _discover_game_maps( - selected_path: Path, - *, - requested_variant: str, -) -> tuple[GameMapOption, ...]: +def _discover_game_maps(selected_path: Path) -> tuple[GameMapOption, ...]: """Read menu metadata for bundled maps and maps beside the CLI selection.""" selected = selected_path.expanduser().resolve() paths = {selected} @@ -483,19 +474,11 @@ def _discover_game_maps( options: list[GameMapOption] = [] for path in paths: header = load_game_map_header(path) - variants = tuple(item.name for item in header.variants) - preferred = requested_variant if path == selected else "default" - variant = ( - preferred - if preferred in variants - else ("default" if "default" in variants else variants[0]) - ) options.append( GameMapOption( map_id=header.map_id, name=header.name, path=header.source_path, - variant=variant, race_course_ids=header.race_course_ids, ) ) @@ -541,7 +524,6 @@ def _parser( parser.add_argument("--width", type=int, default=defaults.width) parser.add_argument("--height", type=int, default=defaults.height) parser.add_argument("--camera", default="camera_front_wide_120fov") - parser.add_argument("--variant", default="default") parser.add_argument("--prompt") parser.add_argument("--force-map-recompile", action="store_true") parser.add_argument("--device", default="cuda") diff --git a/apps/crazy_robotaxi/crazy_robotaxi/game_selection.py b/apps/crazy_robotaxi/crazy_robotaxi/game_selection.py index a02aa7ad2..6ed823b57 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/game_selection.py +++ b/apps/crazy_robotaxi/crazy_robotaxi/game_selection.py @@ -37,9 +37,6 @@ class GameMapOption: path: Path """Resolved authored-map path loaded after selection.""" - variant: str - """Visual variant used when loading this map.""" - race_course_ids: tuple[str, ...] = () """Ordered race courses available on this map.""" diff --git a/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml b/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml index 088a44890..9ef02f161 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml +++ b/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml @@ -122,7 +122,6 @@ nodes: # access surfaces rather than authored road edges. - id: southwest_parking_lot type: parking_lot - prompt_context: A small paved parking court sits among low-rise neighborhood buildings. connected_to: southwest_lot_driveway opening_vertex: 2 vertices: @@ -134,7 +133,6 @@ nodes: - {x_m: -24, y_m: -189} - id: south_parking_lot type: parking_lot - prompt_context: A paved commercial parking lot sits beside low-rise shops. connected_to: south_lot_driveway opening_vertex: 2 vertices: @@ -213,16 +211,16 @@ nodes: roads: # Spawn-area arterial and neighborhood grid. - - {id: west_arterial_approach, from: west_arterial_end, to: west_arterial_crossing, profile: arterial, prompt_context: &western_boulevard_context "A broad tree-lined suburban boulevard passes detached homes and low-rise neighborhood buildings."} - - {id: spawn_arterial, from: west_arterial_crossing, to: central_arterial_crossing, profile: arterial, prompt_context: *western_boulevard_context} - - {id: central_arterial, from: central_arterial_crossing, to: diagonal_arterial_crossing, profile: arterial, prompt_context: *western_boulevard_context} - - {id: west_north_lower, from: west_arterial_crossing, to: west_north_crossing, profile: street, prompt_context: &residential_context "A quiet residential street has a mix of single-story and two-story homes, lawns, and mature trees."} + - {id: west_arterial_approach, from: west_arterial_end, to: west_arterial_crossing, profile: arterial} + - {id: spawn_arterial, from: west_arterial_crossing, to: central_arterial_crossing, profile: arterial} + - {id: central_arterial, from: central_arterial_crossing, to: diagonal_arterial_crossing, profile: arterial} + - {id: west_north_lower, from: west_arterial_crossing, to: west_north_crossing, profile: street} - {id: west_north_upper, from: west_north_crossing, to: west_upper_crossing, profile: street} - {id: west_north_tail, from: west_upper_crossing, to: west_north_end, profile: street} - {id: central_north_lower, from: central_arterial_crossing, to: central_north_crossing, profile: street} - {id: central_north_upper, from: central_north_crossing, to: central_upper_crossing, profile: street} - {id: central_north_tail, from: central_upper_crossing, to: central_north_end, profile: local} - - {id: north_cross_street, from: west_north_crossing, to: central_north_crossing, profile: street, prompt_context: *residential_context} + - {id: north_cross_street, from: west_north_crossing, to: central_north_crossing, profile: street} - {id: upper_cross_street, from: west_upper_crossing, to: central_upper_crossing, profile: street} - {id: west_south_upper, from: west_arterial_crossing, to: west_south_crossing, profile: street} - {id: west_south_lower, from: west_south_crossing, to: west_lower_crossing, profile: street} @@ -238,7 +236,7 @@ roads: - {id: south_local_east, from: south_lot_driveway, to: south_local_end, profile: local} # Diagonal street and the two broad arterial branches. - - {id: diagonal_north_lower, from: diagonal_arterial_crossing, to: diagonal_bend_lower, profile: street, prompt_context: &diagonal_context "A diagonal neighborhood connector passes trees, detached homes, and small roadside buildings."} + - {id: diagonal_north_lower, from: diagonal_arterial_crossing, to: diagonal_bend_lower, profile: street} - {id: diagonal_north_middle, from: diagonal_bend_lower, to: diagonal_north_crossing, profile: street} - {id: diagonal_north_upper, from: diagonal_north_crossing, to: diagonal_bend_upper, profile: street} - {id: diagonal_north_tail, from: diagonal_bend_upper, to: diagonal_north_end, profile: street} @@ -247,7 +245,6 @@ roads: from: southwest_boulevard_end to: southwest_merge profile: arterial - prompt_context: *western_boulevard_context path: - {x_m: 184, y_m: -255} - {x_m: 205, y_m: -190} @@ -256,7 +253,6 @@ roads: from: diagonal_arterial_crossing to: arterial_merge_crossing profile: arterial - prompt_context: *western_boulevard_context path: - {x_m: 332, y_m: -8} - {x_m: 370, y_m: -39} @@ -295,15 +291,15 @@ roads: - {x_m: 710, y_m: 45} # Eastern arterial and large northern loop. - - {id: arterial_470_550, from: arterial_crossing_470, to: arterial_crossing_550, profile: arterial, prompt_context: &eastern_arterial_context "A long multi-lane eastern boulevard passes mixed low-rise commercial and residential blocks."} + - {id: arterial_470_550, from: arterial_crossing_470, to: arterial_crossing_550, profile: arterial} - {id: arterial_550_630, from: arterial_crossing_550, to: arterial_crossing_630, profile: arterial} - {id: arterial_630_710, from: arterial_crossing_630, to: arterial_crossing_710, profile: arterial} - - {id: arterial_710_800, from: arterial_crossing_710, to: arterial_crossing_800, profile: arterial, prompt_context: *eastern_arterial_context} + - {id: arterial_710_800, from: arterial_crossing_710, to: arterial_crossing_800, profile: arterial} - {id: arterial_800_860, from: arterial_crossing_800, to: arterial_crossing_860, profile: arterial} - {id: arterial_860_895, from: arterial_crossing_860, to: arterial_crossing_895, profile: arterial} - {id: arterial_895_955, from: arterial_crossing_895, to: east_corner_lot_driveway, profile: arterial} - {id: arterial_955_990, from: east_corner_lot_driveway, to: arterial_crossing_990, profile: arterial} - - {id: arterial_990_1082, from: arterial_crossing_990, to: arterial_crossing_1082, profile: arterial, prompt_context: *eastern_arterial_context} + - {id: arterial_990_1082, from: arterial_crossing_990, to: arterial_crossing_1082, profile: arterial} - {id: arterial_1082_1170, from: arterial_crossing_1082, to: arterial_crossing_1170, profile: arterial} - {id: arterial_1170_end, from: arterial_crossing_1170, to: east_arterial_end, profile: arterial} - id: east_north_loop @@ -317,7 +313,7 @@ roads: - {x_m: 895, y_m: 30} # Southern commercial grid and parking courts. - - {id: south_spine_630, from: arterial_crossing_630, to: south_crossing_630, profile: street, prompt_context: &commercial_context "A low-rise commercial district has storefronts, paved parking courts, and sparse street trees."} + - {id: south_spine_630, from: arterial_crossing_630, to: south_crossing_630, profile: street} - {id: south_710_upper, from: arterial_crossing_710, to: south_crossing_710, profile: street} - {id: south_710_lower, from: south_crossing_710, to: lower_crossing_710, profile: street} - {id: south_710_tail, from: lower_crossing_710, to: south_end_710, profile: local} @@ -325,14 +321,14 @@ roads: - {id: south_860_lower, from: south_crossing_860, to: lower_crossing_860, profile: local} - {id: south_990_upper, from: arterial_crossing_990, to: south_crossing_990, profile: street} - {id: south_990_lower, from: south_crossing_990, to: lower_crossing_990, profile: local} - - {id: upper_commercial_west, from: south_crossing_710, to: south_crossing_860, profile: local, prompt_context: *commercial_context} + - {id: upper_commercial_west, from: south_crossing_710, to: south_crossing_860, profile: local} - {id: upper_commercial_center_west, from: south_crossing_860, to: south_upper_lot_driveway, profile: local} - {id: upper_commercial_center_east, from: south_upper_lot_driveway, to: south_crossing_930, profile: local} - {id: upper_commercial_east, from: south_crossing_930, to: south_crossing_990, profile: local} - {id: commercial_930_spine, from: south_crossing_930, to: lower_crossing_930, profile: local} - {id: lower_commercial_west, from: lower_crossing_710, to: lower_lot_driveway, profile: local} - {id: lower_commercial_midwest, from: lower_lot_driveway, to: lower_crossing_860, profile: local} - - {id: lower_commercial_center, from: lower_crossing_860, to: lower_crossing_930, profile: local, prompt_context: *commercial_context} + - {id: lower_commercial_center, from: lower_crossing_860, to: lower_crossing_930, profile: local} - {id: lower_commercial_east, from: lower_crossing_930, to: lower_crossing_990, profile: local} - {id: lower_commercial_tail, from: lower_crossing_990, to: lower_crossing_1080, profile: local} - {id: east_south_cul_de_sac, from: lower_crossing_1080, to: south_cul_de_sac_1080, profile: local} @@ -345,11 +341,11 @@ roads: - {id: bottom_east_road, from: bottom_lot_driveway, to: south_crossing_630, profile: local} # Far-east surface loop and local northern spur. - - {id: far_east_north_spur, from: arterial_crossing_1082, to: far_east_north_crossing, profile: local, prompt_context: &far_east_context "A quieter far-eastern local loop passes detached homes, small lots, and mature trees."} + - {id: far_east_north_spur, from: arterial_crossing_1082, to: far_east_north_crossing, profile: local} - {id: far_east_north_tail, from: far_east_north_crossing, to: far_east_north_end, profile: local} - {id: far_east_loop_entry, from: arterial_crossing_1170, to: far_east_bend_southeast, profile: local} - {id: far_east_loop_east, from: far_east_bend_southeast, to: far_east_bend_northeast, profile: local} - - {id: far_east_loop_north, from: far_east_bend_northeast, to: far_east_bend_northwest, profile: local, prompt_context: *far_east_context} + - {id: far_east_loop_north, from: far_east_bend_northeast, to: far_east_bend_northwest, profile: local} - {id: far_east_loop_west, from: far_east_bend_northwest, to: far_east_bend_southwest, profile: local} - {id: far_east_loop_exit, from: far_east_bend_southwest, to: far_east_north_crossing, profile: local} @@ -418,12 +414,8 @@ spawns: road: spawn_arterial lane: 2 distance_m: 128 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: >- - A forward-facing view from a taxi moving through a quiet suburban - district in daylight, with low commercial buildings, houses, - landscaping, and parked cars. - prompt_context: >- - A forward-facing view from a taxi on a road through a city in daylight. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: >- + A forward-facing view from a taxi moving through a quiet suburban + district in daylight, with low commercial buildings, houses, + landscaping, and parked cars. diff --git a/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml b/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml index 38b381425..1ce7a332b 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml +++ b/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml @@ -39,25 +39,16 @@ roads: from: west_hairpin_exit to: start_finish profile: raceway - prompt_context: >- - The start-finish straight runs past packed grandstands, pit garages, - timing towers, sponsor gantries, and a black-and-white checkered line. - id: main_straight from: start_finish to: turn_one_entry profile: raceway - prompt_context: >- - A broad city-center racing straight is enclosed by concrete barriers, - tall safety fencing, colorful sponsor banners, and glass office towers. - id: turn_one from: turn_one_entry to: east_short_entry profile: raceway - prompt_context: >- - The circuit sweeps around a monumental civic plaza with stone steps, - fountains, flagpoles, dense crowds, and a skyline of modern towers. bezier: - control_points: - {x_m: 255, y_m: -160} @@ -68,17 +59,11 @@ roads: from: east_short_entry to: infield_turn_entry profile: raceway - prompt_context: >- - The race course passes a colorful entertainment district of neon signs, - theater marquees, rooftop billboards, and spectator balconies. - id: infield_turn from: infield_turn_entry to: infield_straight_entry profile: raceway - prompt_context: >- - A technical bend threads through a landscaped city park with mature - trees, flower beds, sculptures, and spectators behind safety fencing. bezier: - control_points: - {x_m: 290, y_m: -15} @@ -89,17 +74,11 @@ roads: from: infield_straight_entry to: hairpin_entry profile: raceway - prompt_context: >- - The infield straight follows a canal lined with brick warehouses, - iron footbridges, waterside cafes, race barriers, and fluttering flags. - id: infield_hairpin from: hairpin_entry to: hairpin_exit profile: raceway - prompt_context: >- - The tight hairpin circles a lively market square filled with striped - awnings, old masonry facades, cafe terraces, and cheering race fans. bezier: - control_points: - {x_m: 40, y_m: 20} @@ -114,17 +93,11 @@ roads: from: hairpin_exit to: north_sweeper_entry profile: raceway - prompt_context: >- - A fast tree-lined boulevard runs beside red-brick row houses, pocket - parks, pedestrian bridges, and long walls of racing advertisements. - id: north_sweeper from: north_sweeper_entry to: top_straight_entry profile: raceway - prompt_context: >- - The broad sweeper curves along a sunny waterfront with a marina, - sailboats, palms, promenade crowds, and the city skyline across the bay. bezier: - control_points: - {x_m: 290, y_m: 140} @@ -135,17 +108,11 @@ roads: from: top_straight_entry to: esses_entry profile: raceway - prompt_context: >- - The circuit charges through a museum district of bold contemporary - architecture, public art, reflecting pools, and elevated walkways. - id: technical_esses from: esses_entry to: esses_exit profile: raceway - prompt_context: >- - Rapid esses weave through a lush botanical garden with dense greenery, - glass conservatories, rocky landscaping, and close safety barriers. bezier: - control_points: - {x_m: -105, y_m: 250} @@ -164,17 +131,11 @@ roads: from: esses_exit to: west_hairpin_entry profile: raceway - prompt_context: >- - The west straight crosses a gritty arts district of converted factories, - vivid murals, steel fire escapes, food trucks, and overhead rail lines. - id: west_hairpin from: west_hairpin_entry to: west_hairpin_exit profile: raceway - prompt_context: >- - The final hairpin wraps around a busy harbor quarter with cargo cranes, - stacked containers, dockside warehouses, and grandstands above the quay. bezier: - control_points: - {x_m: -330, y_m: 50} @@ -209,14 +170,9 @@ spawns: road: start_finish_straight lane: 2 distance_m: 220 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: >- - A forward-facing view from a taxi on a wide closed Formula-style - road circuit in daylight, with varied-radius corners, technical - esses, hairpins, safety barriers, grandstands, sponsor banners, - and a clear start-finish straight. - prompt_context: >- - A forward-facing view from a taxi on a wide road racing circuit - through a city in daylight. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: >- + A forward-facing view from a taxi on a wide closed Formula-style + road circuit in daylight, with varied-radius corners, technical + esses, hairpins, safety barriers, grandstands, sponsor banners, + and a clear start-finish straight. diff --git a/apps/crazy_robotaxi/crazy_robotaxi/session.py b/apps/crazy_robotaxi/crazy_robotaxi/session.py index 6a80a2caa..1fa5e7cb6 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/session.py +++ b/apps/crazy_robotaxi/crazy_robotaxi/session.py @@ -153,7 +153,6 @@ def select_game(self, selection: GameSelection) -> None: request = replace( self.config.scene_request, map_path=option.path, - variant=option.variant, prompt=( self.config.scene_request.prompt if option.path diff --git a/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml b/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml index 9ea1d5de1..3b298e559 100644 --- a/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml +++ b/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml @@ -49,7 +49,5 @@ spawns: road: south_west lane: 1 distance_m: 30 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: A car racing on a rectangular city loop. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: A car racing on a rectangular city loop. diff --git a/apps/crazy_robotaxi/tests/test_maps.py b/apps/crazy_robotaxi/tests/test_maps.py index 23938eae5..6f32e7bdd 100644 --- a/apps/crazy_robotaxi/tests/test_maps.py +++ b/apps/crazy_robotaxi/tests/test_maps.py @@ -4,25 +4,17 @@ """CPU validation for shipped semantic maps.""" import math +import zipfile from pathlib import Path import numpy as np import pytest -import yaml -from omnidreams_game_engine.game_map import ( - GameMapError, - compile_game_map, - load_game_map, -) -from omnidreams_game_engine.game_map.types import ( - game_map_from_dict, - game_map_to_dict, -) +from omnidreams_game_engine.game_map import load_game_map +from omnidreams_game_engine.config import RasterConfig +from omnidreams_game_engine.scene import SceneRequest, load_scene pytestmark = pytest.mark.ci_cpu -_MAP_FIXTURES = Path(__file__).parent / "maps" - @pytest.mark.parametrize( "filename", @@ -35,82 +27,26 @@ def test_shipped_map_is_valid(filename: str) -> None: assert game_map.map_id.startswith("crazy-robotaxi-") assert game_map.spawns assert game_map.lanes - variant = game_map.default_spawn.variants[0] - assert variant.prompt_context - assert variant.prompt_context != variant.prompt - - -def test_prompt_context_is_trimmed_and_round_trips(tmp_path: Path) -> None: - source = yaml.safe_load( - (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() - ) - source["nodes"][0]["prompt_context"] = " A neighborhood landmark. " - source["roads"][0]["prompt_context"] = " Detached homes line the road. " - source["spawns"][0]["variants"]["default"]["prompt_context"] = ( - " A forward-facing road view. " - ) - path = tmp_path / "prompt-context.robotaxi.yaml" - path.write_text(yaml.safe_dump(source, sort_keys=False)) - - original = load_game_map(path) - restored = game_map_from_dict(game_map_to_dict(original)) - - assert original.topology.nodes[0].prompt_context == "A neighborhood landmark." - assert original.topology.roads[0].prompt_context == ( - "Detached homes line the road." - ) - assert original.default_spawn.variants[0].prompt_context == ( - "A forward-facing road view." - ) - assert restored.default_spawn.variants == original.default_spawn.variants - assert restored.topology == original.topology - - -@pytest.mark.parametrize("value", ["", " ", 42, ["not", "text"]]) -def test_prompt_context_requires_nonempty_text(tmp_path: Path, value: object) -> None: - source = yaml.safe_load( - (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() - ) - source["roads"][0]["prompt_context"] = value - path = tmp_path / "invalid-prompt-context.robotaxi.yaml" - path.write_text(yaml.safe_dump(source, sort_keys=False)) - - with pytest.raises(GameMapError, match="prompt_context must be a nonempty string"): - load_game_map(path) -@pytest.mark.parametrize("value", ["", " ", 42, ["not", "text"]]) -def test_spawn_prompt_context_requires_nonempty_text( - tmp_path: Path, value: object +def test_compiled_map_uses_canonical_spawn_conditioning( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - source = yaml.safe_load( - (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() - ) - source["spawns"][0]["variants"]["default"]["prompt_context"] = value - path = tmp_path / "invalid-spawn-prompt-context.robotaxi.yaml" - path.write_text(yaml.safe_dump(source, sort_keys=False)) - - with pytest.raises(GameMapError, match="prompt_context must be a nonempty string"): - load_game_map(path) - + path = Path(__file__).parent / "maps" / "race_course.robotaxi.yaml" + monkeypatch.setenv("FLASHDREAMS_CACHE_DIR", str(tmp_path)) + game_map = load_game_map(path) -def test_prompt_context_change_invalidates_compiler_cache(tmp_path: Path) -> None: - source = yaml.safe_load( - (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() + scene = load_scene( + SceneRequest(map_path=path), + RasterConfig(width=64, height=32, compute_device="automatic"), ) - path = tmp_path / "cached-prompt-context.robotaxi.yaml" - path.write_text(yaml.safe_dump(source, sort_keys=False)) - cache_root = tmp_path / "cache" - - first = compile_game_map(path, cache_root=cache_root) - assert compile_game_map(path, cache_root=cache_root).cache_hit - source["roads"][0]["prompt_context"] = "A changed roadside setting." - path.write_text(yaml.safe_dump(source, sort_keys=False)) - changed = compile_game_map(path, cache_root=cache_root) - - assert first.cache_hit is False - assert changed.cache_hit is False - assert changed.archive_path != first.archive_path + + assert scene.prompt == game_map.default_spawn.prompt + assert scene.initial_rgb.shape == (32, 64, 3) + with zipfile.ZipFile(scene.scene_path) as archive: + names = set(archive.namelist()) + assert {"prompt.txt", "first_image.png"} <= names + assert not any(name.startswith(("prompt_", "first_image_")) for name in names) def test_boulevard_traffic_turns_are_continuous_and_physically_limited() -> None: diff --git a/apps/crazy_robotaxi/tests/test_ui.py b/apps/crazy_robotaxi/tests/test_ui.py index 3525a18bf..a0ad455ce 100644 --- a/apps/crazy_robotaxi/tests/test_ui.py +++ b/apps/crazy_robotaxi/tests/test_ui.py @@ -903,7 +903,6 @@ def test_selection_menus_use_arcade_card_layout() -> None: map_id="test-city", name="Test City", path=Path("test-city.robotaxi.yaml"), - variant="default", race_course_ids=("downtown-sprint",), ) state = TaxiHudState(640, 540, _calibration(), map_options=(option,)) @@ -944,7 +943,6 @@ def test_startup_menu_selects_taxi_mode_then_map_through_v2_message() -> None: map_id="test-city", name="Test City", path=Path("test-city.robotaxi.yaml"), - variant="default", race_course_ids=("downtown-sprint",), ) state = TaxiHudState(640, 360, _calibration(), map_options=(option,)) @@ -978,7 +976,6 @@ def test_race_menu_selects_map_then_course() -> None: map_id="test-city", name="Test City", path=Path("test-city.robotaxi.yaml"), - variant="default", race_course_ids=("downtown-sprint",), ) state = TaxiHudState(640, 360, _calibration(), map_options=(option,)) @@ -1016,7 +1013,6 @@ def test_complete_cli_selection_skips_all_selection_screens() -> None: map_id="test-city", name="Test City", path=Path("test-city.robotaxi.yaml").resolve(), - variant="default", race_course_ids=("downtown-sprint",), ) state = TaxiHudState( @@ -1055,7 +1051,6 @@ def test_explicit_race_mode_and_map_skip_to_course_screen() -> None: map_id="test-city", name="Test City", path=Path("test-city.robotaxi.yaml").resolve(), - variant="default", race_course_ids=("downtown-sprint",), ) state = TaxiHudState( diff --git a/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md b/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md index 95b535e1b..34f8ae61a 100644 --- a/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md +++ b/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md @@ -93,28 +93,6 @@ positive-area overlap or share a boundary edge; isolated point tangency is allowed. Roads, parking lots, and other surfaces therefore cannot be layered over one another to repair topology. -Nodes and roads may carry an optional nonempty `prompt_context` string. A game -can append this authored environmental description to a model prompt when the -vehicle is on or approaching that topology element: - -```yaml -nodes: - - id: market_junction - type: intersection - pose: {x_m: 0, y_m: 0} - prompt_context: A busy market plaza fills the intersection. - -roads: - - id: market_street - from: west_end - to: market_junction - profile: neighborhood - prompt_context: Small storefronts and awnings line both sides of the road. -``` - -Leading and trailing whitespace is removed. Empty or non-string values are -rejected, and prompt context participates in the compiled-map cache key. - ## Nodes All non-parking nodes require `id`, `type`, and `pose`. Their remaining required @@ -425,7 +403,7 @@ compiled cyclic route and defaults to zero. Vehicles are physical, collidable, and maintain simple same-lane headway; traffic signals and right-of-way are not currently modeled. -## Spawns and visual variants +## Spawns and visual conditioning A spawn names an authored road lane and a distance along its directed centerline. Lane indices follow the effective `lanes` order. @@ -436,29 +414,20 @@ spawns: road: oak_street lane: 1 distance_m: 5 - variants: - default: - image: seed.png - prompt: A forward-facing taxi view in a quiet neighborhood at daylight. - prompt_context: A forward-facing taxi view on a road at daylight. + image: seed.png + prompt: A forward-facing taxi view in a quiet neighborhood at daylight. ``` -Every spawn requires a `default` variant. `image` is optional; when omitted (or -set to `null`), the compiler generates a deterministic synthetic first-person -view by projecting the semantic map from that spawn through the runtime front -camera. This fallback shows aligned road surfaces, boundaries, curbs, and -markings, but does not synthesize scenery. Use it as a robust placeholder, not -as a photorealistic authoring result. - -`prompt` is the complete standalone scene description. `prompt_context` is an -optional shorter base prompt for applications that append live road, topology, -and motion context. When that mode is selected, `prompt_context` is used if -present and otherwise falls back to `prompt`. An explicit runtime prompt -override remains authoritative in either mode. +Every spawn requires a non-empty `prompt`. `image` is optional; when omitted +(or set to `null`), the compiler generates a deterministic synthetic +first-person view by projecting the semantic map from that spawn through the +runtime front camera. This fallback shows aligned road surfaces, boundaries, +curbs, and markings, but does not synthesize scenery. Use it as a robust +placeholder, not as a photorealistic authoring result. Authored images may be map-relative paths or `package://package/resource` references. Resolved geometry, compiler and fallback-renderer code, seed -images, and both prompt forms participate in the compiled-map cache key. +images, and prompts participate in the compiled-map cache key. ## Validation summary diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/engine_settings.py b/apps/omnidreams_game_engine/omnidreams_game_engine/engine_settings.py index 039af8bd5..5ee055ecf 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/engine_settings.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/engine_settings.py @@ -28,12 +28,6 @@ class MapLaunchSettings: camera: str = "camera_front_wide_120fov" """Camera identifier selected from the map's available views.""" - variant: str = "default" - """Visual variant selected from the map.""" - - prompt: str | None = None - """Base prompt override; ``None`` uses the map variant's prompt.""" - force_recompile: bool = False """Whether to rebuild the selected map's compiled cache once.""" diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py index a301fe7a1..1dd797bec 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py @@ -14,10 +14,7 @@ import numpy as np import yaml -from omnidreams_game_engine.game_map.types import ( - GameMapLinearAttributes, - GameMapVisualVariant, -) +from omnidreams_game_engine.game_map.types import GameMapLinearAttributes GAME_MAP_SUFFIX = ".robotaxi.yaml" """Filename suffix for authored node-graph game maps.""" @@ -62,7 +59,6 @@ class GameMapHeader: map_id: str name: str - variants: tuple[GameMapVisualVariant, ...] source_path: Path race_course_ids: tuple[str, ...] = () @@ -203,52 +199,23 @@ def _parse_map_identity(doc: dict[str, Any]) -> tuple[str, str]: return map_id, name -def _parse_variants( +def _parse_spawn_conditioning( raw_spawn: dict[str, Any], source_path: Path -) -> tuple[GameMapVisualVariant, ...]: - variants_raw = _mapping(raw_spawn.get("variants"), "spawn.variants") - if "default" not in variants_raw: - raise GameMapError("Every spawn must define a default visual variant") - variants: list[GameMapVisualVariant] = [] - for name, raw_variant in variants_raw.items(): - variant = _mapping(raw_variant, f"variant {name!r}") - unknown = set(variant) - {"image", "prompt", "prompt_context"} - if unknown: - raise GameMapError(f"Variant {name!r} has unknown fields {sorted(unknown)}") - image_value = variant.get("image") - image = None if image_value is None else str(image_value).strip() - prompt = str(variant.get("prompt", "")).strip() - if not prompt: - raise GameMapError(f"Variant {name!r} requires a non-empty prompt") - prompt_context_value = variant.get("prompt_context") - if prompt_context_value is not None and ( - not isinstance(prompt_context_value, str) - or not prompt_context_value.strip() - ): - raise GameMapError( - f"Variant {name!r} prompt_context must be a nonempty string" - ) - prompt_context = ( - None if prompt_context_value is None else prompt_context_value.strip() - ) - if image_value is not None and not image: - raise GameMapError(f"Variant {name!r} image must not be empty") - if image is not None: - resolve_seed_asset(source_path, image) - variants.append( - GameMapVisualVariant( - name=name, - image=image, - prompt=prompt, - prompt_context=prompt_context, - ) - ) - variants.sort(key=lambda item: (item.name != "default", item.name)) - return tuple(variants) +) -> tuple[str | None, str]: + image_value = raw_spawn.get("image") + image = None if image_value is None else str(image_value).strip() + prompt = str(raw_spawn.get("prompt", "")).strip() + if not prompt: + raise GameMapError("Every spawn requires a non-empty prompt") + if image_value is not None and not image: + raise GameMapError("spawn.image must not be empty") + if image is not None: + resolve_seed_asset(source_path, image) + return image, prompt def load_game_map_header(path: Path) -> GameMapHeader: - """Load map name and default-spawn variants without resolving geometry.""" + """Load menu metadata and validate default-spawn conditioning.""" source_path = Path(path).expanduser().resolve() doc = _read_document(source_path) map_id, name = _parse_map_identity(doc) @@ -256,10 +223,10 @@ def load_game_map_header(path: Path) -> GameMapHeader: if not spawns: raise GameMapError("Map must define at least one spawn") first_spawn = _mapping(spawns[0], "spawns[0]") + _parse_spawn_conditioning(first_spawn, source_path) return GameMapHeader( map_id=map_id, name=name, - variants=_parse_variants(first_spawn, source_path), source_path=source_path, race_course_ids=_parse_race_course_ids(doc), ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py index a3dffb8f9..6d31d5773 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py @@ -87,17 +87,14 @@ def _digest(game_map: ResolvedGameMap) -> str: resolved.pop("source_path", None) hasher.update(json.dumps(resolved, sort_keys=True, separators=(",", ":")).encode()) for spawn in game_map.spawns: - for variant in spawn.variants: - hasher.update(variant.name.encode()) - hasher.update(variant.prompt.encode()) - if variant.image is None: - hasher.update(b"generated-spawn-first-frame") - hasher.update(spawn_render.SPAWN_RENDERER_VERSION.encode()) - hasher.update(Path(spawn_render.__file__).read_bytes()) - hasher.update(Path(camera_defaults.__file__).read_bytes()) - else: - asset = resolve_seed_asset(game_map.source_path, variant.image) - hasher.update(asset.read_bytes()) + if spawn.image is None: + hasher.update(b"generated-spawn-first-frame") + hasher.update(spawn_render.SPAWN_RENDERER_VERSION.encode()) + hasher.update(Path(spawn_render.__file__).read_bytes()) + hasher.update(Path(camera_defaults.__file__).read_bytes()) + else: + asset = resolve_seed_asset(game_map.source_path, spawn.image) + hasher.update(asset.read_bytes()) return hasher.hexdigest() @@ -414,7 +411,6 @@ def _trajectory(game_map: ResolvedGameMap) -> dict[str, object]: def _write_archive(path: Path, game_map: ResolvedGameMap) -> None: spawn = game_map.default_spawn - generated_image: np.ndarray | None = None with zipfile.ZipFile(path, mode="w", compression=zipfile.ZIP_STORED) as archive: archive.writestr( "metadata.yaml", yaml.safe_dump(_metadata(game_map), sort_keys=True) @@ -428,26 +424,19 @@ def _write_archive(path: Path, game_map: ResolvedGameMap) -> None: "mesh_ground.ply", save_mesh_vf(game_map.ground_vertices, game_map.ground_faces), ) - for variant in spawn.variants: - suffix = "" if variant.name == "default" else f"_{variant.name}" - archive.writestr(f"prompt{suffix}.txt", variant.prompt) - image_name = f"first_image{suffix}.png" - if variant.image is None: - if generated_image is None: - generated_image = spawn_render.render_spawn_first_frame( - game_map, spawn - ) - _write_image_array( - archive, - image_name, - generated_image, - ) - else: - _write_image( - archive, - image_name, - resolve_seed_asset(game_map.source_path, variant.image), - ) + archive.writestr("prompt.txt", spawn.prompt) + if spawn.image is None: + _write_image_array( + archive, + "first_image.png", + spawn_render.render_spawn_first_frame(game_map, spawn), + ) + else: + _write_image( + archive, + "first_image.png", + resolve_seed_asset(game_map.source_path, spawn.image), + ) _write_parquet( archive, "clipgt/calibration_estimate.parquet", _calibration_row() ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py index 96ff23486..29e0a4b7d 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py @@ -30,7 +30,7 @@ _parse_compiler_settings, _parse_map_identity, _parse_profiles, - _parse_variants, + _parse_spawn_conditioning, _positive_float, _Profile, _read_document, @@ -2722,9 +2722,12 @@ def _spawn( source_path: Path, lane_by_id: dict[str, _LaneBuild], ) -> GameMapSpawn: - if set(raw) != {"id", "road", "lane", "distance_m", "variants"}: + required = {"id", "road", "lane", "distance_m", "prompt"} + unknown = set(raw) - (required | {"image"}) + missing = required - set(raw) + if missing or unknown: raise GameMapError( - "Spawns require exactly id, road, lane, distance_m, and variants" + f"Spawns are missing {sorted(missing)} or have unknown fields {sorted(unknown)}" ) spawn_id = str(raw["id"]).strip() if not spawn_id: @@ -2751,13 +2754,15 @@ def _spawn( alpha = (distance - cumulative[segment]) / max(float(lengths[segment]), 1.0e-9) position = points[segment] + alpha * (points[segment + 1] - points[segment]) direction = points[segment + 1] - points[segment] + image, prompt = _parse_spawn_conditioning(raw, source_path) return GameMapSpawn( spawn_id=spawn_id, lane_id=lane_id, distance_m=distance, position_world=position.astype(np.float32), yaw_rad=math.atan2(float(direction[1]), float(direction[0])), - variants=_parse_variants(raw, source_path), + image=image, + prompt=prompt, ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py index 633daa3fd..d35cac14d 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py @@ -87,9 +87,6 @@ class GameMapNode: polygon_vertices_xy: tuple[tuple[float, float], ...] = () """Authored map-space polygon vertices for a parking-lot node.""" - prompt_context: str | None = None - """Optional authored environmental context appended to model prompts.""" - @dataclass(frozen=True, eq=False) class GameMapRoad: @@ -113,9 +110,6 @@ class GameMapRoad: bezier_spans_world: tuple[FloatArray, ...] """Compiler-generated map-space cubic spans shaped ``[4, 3]``; empty is straight.""" - prompt_context: str | None = None - """Optional authored environmental context appended to model prompts.""" - def __eq__(self, other: object) -> bool: """Compare road metadata and cubic span values.""" if not isinstance(other, GameMapRoad): @@ -126,7 +120,6 @@ def __eq__(self, other: object) -> bool: and self.to_node_id == other.to_node_id and self.profile_id == other.profile_id and self.attributes == other.attributes - and self.prompt_context == other.prompt_context and len(self.bezier_spans_world) == len(other.bezier_spans_world) and all( np.array_equal(first, second) @@ -173,23 +166,6 @@ class GameMapTopology: """Node identifiers paired with stable incident edge/link references.""" -@dataclass(frozen=True) -class GameMapVisualVariant: - """Optional seed image and prompt for one visual variant.""" - - name: str - """Variant slug used to select this visual conditioning.""" - - image: str | None - """Optional map-relative or ``package://`` seed-image reference.""" - - prompt: str - """World-model text prompt paired with the seed image.""" - - prompt_context: str | None = None - """Optional shorter base prompt used with dynamic map context.""" - - @dataclass(frozen=True) class GameMapSpawn: """Vehicle spawn resolved onto a directed lane.""" @@ -209,8 +185,11 @@ class GameMapSpawn: yaw_rad: float """World heading following the directed lane.""" - variants: tuple[GameMapVisualVariant, ...] - """Available visual seed variants; ``default`` is always present.""" + image: str | None + """Optional map-relative or ``package://`` seed-image reference.""" + + prompt: str + """World-model text prompt paired with the spawn's seed image.""" @dataclass(frozen=True) @@ -469,12 +448,6 @@ def default_spawn(self) -> GameMapSpawn: """Return the first declared spawn.""" return self.spawns[0] - @property - def variants(self) -> tuple[str, ...]: - """Return variants available at the default spawn.""" - names = [variant.name for variant in self.default_spawn.variants] - return tuple(names) - def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: """Serialize a resolved map into JSON-compatible values.""" @@ -497,7 +470,6 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "polygon_vertices_xy": [ list(point) for point in node.polygon_vertices_xy ], - "prompt_context": node.prompt_context, } for node in game_map.topology.nodes ], @@ -511,7 +483,6 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "bezier_spans_world": [ span.tolist() for span in road.bezier_spans_world ], - "prompt_context": road.prompt_context, } for road in game_map.topology.roads ], @@ -605,15 +576,8 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "distance_m": spawn.distance_m, "position_world": spawn.position_world.tolist(), "yaw_rad": spawn.yaw_rad, - "variants": [ - { - "name": variant.name, - "image": variant.image, - "prompt": variant.prompt, - "prompt_context": variant.prompt_context, - } - for variant in spawn.variants - ], + "image": spawn.image, + "prompt": spawn.prompt, } for spawn in game_map.spawns ], @@ -727,11 +691,6 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: (float(point[0]), float(point[1])) for point in raw.get("polygon_vertices_xy", ()) ), - prompt_context=( - None - if raw.get("prompt_context") is None - else str(raw["prompt_context"]) - ), ) for raw in raw_topology["nodes"] ), @@ -748,11 +707,6 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: np.asarray(span, dtype=np.float32) for span in raw["bezier_spans_world"] ), - prompt_context=( - None - if raw.get("prompt_context") is None - else str(raw["prompt_context"]) - ), ) for raw in raw_topology["roads"] ), @@ -844,21 +798,8 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: distance_m=float(raw["distance_m"]), position_world=np.asarray(raw["position_world"], dtype=np.float32), yaw_rad=float(raw["yaw_rad"]), - variants=tuple( - GameMapVisualVariant( - name=str(variant["name"]), - image=( - None if variant.get("image") is None else str(variant["image"]) - ), - prompt=str(variant["prompt"]), - prompt_context=( - None - if variant.get("prompt_context") is None - else str(variant["prompt_context"]) - ), - ) - for variant in raw["variants"] - ), + image=None if raw.get("image") is None else str(raw["image"]), + prompt=str(raw["prompt"]), ) for raw in value["spawns"] ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py b/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py index e6182b57c..a6a83efce 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py @@ -20,26 +20,14 @@ class SceneRequest: map_path: Path camera_name: str = "camera_front_wide_120fov" - variant: str = "default" - prompt: str | None = None - use_prompt_context: bool = False force_recompile: bool = False def load_scene(request: SceneRequest, raster: RasterConfig) -> SceneDefinition: """Compile an authored map if necessary and load its runtime scene.""" compiled = compile_game_map(request.map_path, force=request.force_recompile) - prompt_override = request.prompt - if prompt_override is None and request.use_prompt_context: - variants = compiled.game_map.default_spawn.variants - selected = next( - (item for item in variants if item.name == request.variant), variants[0] - ) - prompt_override = selected.prompt_context or selected.prompt return load_scene_bundle( scene_path=compiled.archive_path, camera_name=request.camera_name, - variant=request.variant, - prompt_override=prompt_override, raster=raster, ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/scene_loader.py b/apps/omnidreams_game_engine/omnidreams_game_engine/scene_loader.py index 97ce274e7..fb1b7cfcc 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/scene_loader.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/scene_loader.py @@ -7,20 +7,12 @@ import json import zipfile from collections import defaultdict -from dataclasses import dataclass, replace from pathlib import Path from typing import Any import numpy as np import pyarrow.parquet as pq import yaml -from interactive_drive.scene_loader import ( - SCENE_FRAME_SUFFIXES, - SCENE_FRAMES_DIRNAME, - prompt_variant_for_scene_variant, - resolve_variant_archive, - variant_from_stem, -) from loguru import logger from PIL import Image @@ -58,12 +50,6 @@ _GROUND_MESH_NAME = "mesh_ground.ply" -@dataclass(frozen=True) -class _PromptEntry: - archive_name: str - text: str - - def _read_yaml(zf: zipfile.ZipFile, name: str) -> dict[str, Any]: return yaml.safe_load(zf.read(name)) @@ -83,198 +69,17 @@ def _points_from_records(points: list[dict[str, float]]) -> np.ndarray: ) -def _load_initial_image( - zf: zipfile.ZipFile, camera_name: str, variant: str, raster: RasterConfig -) -> np.ndarray: - """Seed frame: the GT first camera frame, else the ``first_image`` render. - - Prefers ``frames//.jpeg`` so generation starts from the real - capture; falls back to ``first_image[_].png`` for older / - synthetic scenes with no per-camera frames. - """ - name = _discover_initial_frame(zf, camera_name) - if name is None: - images = _discover_first_images(zf) - name = images.get(variant) or images.get("default") - if name is None: - raise FileNotFoundError( - "No frames//*.jpeg or first_image*.png found in the USDZ archive" - ) - _log_initial_frame_selection( - zf, - variant=variant, - camera_name=camera_name, - source=name, - ) - with Image.open(io.BytesIO(zf.read(name))) as image: +def _load_initial_image(zf: zipfile.ZipFile, raster: RasterConfig) -> np.ndarray: + """Load the canonical seed frame emitted by the game-map compiler.""" + with Image.open(io.BytesIO(zf.read("first_image.png"))) as image: rgb = image.convert("RGB") resized = rgb.resize(raster.resolution_wh, resample=Image.Resampling.BILINEAR) return np.asarray(resized, dtype=np.uint8) -def _discover_initial_frame(zf: zipfile.ZipFile, camera_name: str) -> str | None: - """Earliest GT frame for ``camera_name`` (``None`` if the archive has none). - - Frames are ``frames//.jpeg``; the smallest timestamp is - the first frame. Accepts colon / underscore camera-name spellings. - """ - clipgt_name, logical_name = normalize_camera_name(camera_name) - wanted_prefixes = tuple( - { - f"{SCENE_FRAMES_DIRNAME}/{name}/" - for name in (camera_name, logical_name, clipgt_name) - } - ) - candidates = [ - name - for name in zf.namelist() - if name.startswith(wanted_prefixes) - and Path(name).suffix.lower() in SCENE_FRAME_SUFFIXES - ] - if not candidates: - return None - - def _frame_sort_key(name: str) -> tuple[int, str]: - stem = Path(name).stem - return (int(stem), name) if stem.isdigit() else (2**63 - 1, name) - - return sorted(candidates, key=_frame_sort_key)[0] - - -def _load_prompt(zf: zipfile.ZipFile, variant: str, prompt_override: str | None) -> str: - if prompt_override is not None: - _log_prompt_selection( - zf, - variant=variant, - selected_variant="override", - source="--prompt", - prompt=prompt_override, - available_variants=(), - ignored_files=(), - ) - return prompt_override - prompt_entries, ignored_files = _discover_prompt_entries(zf) - selected_variant = _select_prompt_variant(prompt_entries, variant) - prompt_entry = ( - prompt_entries[selected_variant] if selected_variant is not None else None - ) - prompt = "" if prompt_entry is None else prompt_entry.text - _log_prompt_selection( - zf, - variant=variant, - selected_variant=selected_variant, - source="" if prompt_entry is None else prompt_entry.archive_name, - prompt=prompt, - available_variants=tuple(sorted(prompt_entries.keys())), - ignored_files=ignored_files, - ) - return prompt - - -def _select_prompt_variant( - prompt_entries: dict[str, _PromptEntry], variant: str -) -> str | None: - """Pick the in-archive prompt key for the requested scene variant. - - Exact match wins first (legacy in-zip ``default`` / ``1`` / ``2``), else - the weather->prompt mapping, else ``"default"``, else ``None``. - """ - if variant in prompt_entries: - return variant - mapped = prompt_variant_for_scene_variant(variant) - if mapped in prompt_entries: - return mapped - if "default" in prompt_entries: - return "default" - return None - - -def _discover_prompts(zf: zipfile.ZipFile) -> dict[str, str]: - prompt_entries, _ = _discover_prompt_entries(zf) - return {variant: entry.text for variant, entry in prompt_entries.items()} - - -def _discover_prompt_entries( - zf: zipfile.ZipFile, -) -> tuple[dict[str, _PromptEntry], tuple[str, ...]]: - prompts: dict[str, _PromptEntry] = {} - ignored_files: list[str] = [] - for name in zf.namelist(): - if "/" in name or not name.startswith("prompt") or not name.endswith(".txt"): - continue - variant = variant_from_stem(Path(name).stem, "prompt") - if variant is None: - ignored_files.append(name) - continue - prompts[variant] = _PromptEntry( - archive_name=name, - text=zf.read(name).decode("utf-8").strip(), - ) - if "default" not in prompts and prompts: - first_key = sorted(prompts.keys())[0] - prompts["default"] = prompts[first_key] - return prompts, tuple(sorted(ignored_files)) - - -def _log_prompt_selection( - zf: zipfile.ZipFile, - *, - variant: str, - selected_variant: str | None, - source: str, - prompt: str, - available_variants: tuple[str, ...], - ignored_files: tuple[str, ...], -) -> None: - scene_name = Path(str(zf.filename)).name if zf.filename is not None else "" - prompt_text = " ".join(prompt.split()) - logger.info( - "[scene_loader] prompt " - f"scene={scene_name!r} " - f"requested_variant={variant!r} " - f"selected_variant={selected_variant or ''!r} " - f"source={source!r} " - f"available_variants={available_variants or ''!r} " - f"ignored_files={ignored_files or ''!r} " - f"length={len(prompt)} " - f"text={prompt_text!r}", - ) - - -def _log_initial_frame_selection( - zf: zipfile.ZipFile, - *, - variant: str, - camera_name: str, - source: str, -) -> None: - scene_name = Path(str(zf.filename)).name if zf.filename is not None else "" - logger.info( - "[scene_loader] initial_frame " - f"scene={scene_name!r} " - f"requested_variant={variant!r} " - f"camera={camera_name!r} " - f"source={source!r}", - ) - - -def _discover_first_images(zf: zipfile.ZipFile) -> dict[str, str]: - images: dict[str, str] = {} - for name in zf.namelist(): - if ( - "/" in name - or not name.startswith("first_image") - or not name.endswith(".png") - ): - continue - variant = variant_from_stem(Path(name).stem, "first_image") - if variant is None: - continue - images[variant] = name - if "default" not in images and images: - first_key = sorted(images.keys())[0] - images["default"] = images[first_key] - return images +def _load_prompt(zf: zipfile.ZipFile) -> str: + """Load the canonical prompt emitted by the game-map compiler.""" + return zf.read("prompt.txt").decode("utf-8").strip() def _select_camera( @@ -790,21 +595,17 @@ def _load_ground_mesh( def load_scene_bundle( scene_path: Path, camera_name: str, - variant: str, - prompt_override: str | None, raster: RasterConfig, ) -> SceneDefinition: - # Swap to the requested variant's sibling archive when present; legacy - # single-archive scenes resolve to the same path (variant picked in-zip). - scene_path = resolve_variant_archive(Path(scene_path), variant) + scene_path = Path(scene_path) with zipfile.ZipFile(scene_path, "r") as zf: metadata = _read_yaml(zf, "metadata.yaml") camera = _load_camera_calibration(zf, camera_name) initial_pose, initial_timestamp, initial_yaw, initial_speed = ( _load_initial_state(zf) ) - initial_rgb = _load_initial_image(zf, camera_name, variant, raster) - prompt = _load_prompt(zf, variant, prompt_override) + initial_rgb = _load_initial_image(zf, raster) + prompt = _load_prompt(zf) line_layers, triangle_layers, polygon_layers = _load_map_layers(zf, raster) ground_mesh_vertices, ground_mesh_faces = _load_ground_mesh(zf) game_map = ( @@ -831,26 +632,3 @@ def load_scene_bundle( ground_mesh_faces=ground_mesh_faces, game_map=game_map, ) - - -def reseed_scene_bundle( - bundle: SceneDefinition, - scene_path: Path, - camera_name: str, - variant: str, - prompt_override: str | None, - raster: RasterConfig, -) -> SceneDefinition: - """Re-seed an already-parsed ``bundle`` for a different weather variant. - - Variants share all geometry; only the initial frame and prompt differ, so - this reads just those from the variant's archive and reuses the rest, - skipping the full re-parse and bounds/snapper rebuild. - """ - scene_path = resolve_variant_archive(Path(scene_path), variant) - with zipfile.ZipFile(scene_path, "r") as zf: - initial_rgb = _load_initial_image(zf, camera_name, variant, raster) - prompt = _load_prompt(zf, variant, prompt_override) - return replace( - bundle, scene_path=scene_path, initial_rgb=initial_rgb, prompt=prompt - ) diff --git a/apps/omnidreams_game_engine/pyproject.toml b/apps/omnidreams_game_engine/pyproject.toml index 25010a1c1..555a04d4b 100644 --- a/apps/omnidreams_game_engine/pyproject.toml +++ b/apps/omnidreams_game_engine/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10,<3.13" dependencies = [ "filelock>=3", "flashdreams", - "flashdreams-interactive-drive-v2", "ludus-renderer", "numpy", "pillow", @@ -23,7 +22,6 @@ dependencies = [ [tool.uv.sources] flashdreams = { workspace = true } -flashdreams-interactive-drive-v2 = { workspace = true } ludus-renderer = { workspace = true } [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index b57f5808b..969534eec 100644 --- a/uv.lock +++ b/uv.lock @@ -3306,7 +3306,6 @@ source = { editable = "apps/omnidreams_game_engine" } dependencies = [ { name = "filelock" }, { name = "flashdreams" }, - { name = "flashdreams-interactive-drive-v2" }, { name = "ludus-renderer" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, @@ -3329,7 +3328,6 @@ dev = [ requires-dist = [ { name = "filelock", specifier = ">=3" }, { name = "flashdreams", editable = "flashdreams" }, - { name = "flashdreams-interactive-drive-v2", editable = "apps/interactive_drive" }, { name = "ludus-renderer", editable = "integrations_v2/omnidreams/impl/ludus-renderer" }, { name = "numpy" }, { name = "pillow" }, From 136ecbb737b45c0fa260016eb8ca02e8cd1bd1a3 Mon Sep 17 00:00:00 2001 From: Aidan Foster Date: Tue, 8 Sep 2026 17:06:05 -0700 Subject: [PATCH 2/3] [crazy-robotaxi] Preserve map context without variants --- apps/crazy_robotaxi/README.md | 2 +- .../maps/boulevard_district.robotaxi.yaml | 34 ++++--- .../maps/flashdreams_raceway.robotaxi.yaml | 42 +++++++++ .../maps/intersection_geometry.robotaxi.yaml | 6 +- .../tests/maps/parking_driveway.robotaxi.yaml | 6 +- .../tests/maps/race_course.robotaxi.yaml | 1 + .../tests/maps/traffic_loop.robotaxi.yaml | 6 +- apps/crazy_robotaxi/tests/test_maps.py | 90 ++++++++++++++++++- .../NODE_GRAPH_MAP_FORMAT.md | 30 ++++++- .../game_map/_schema.py | 12 ++- .../game_map/compiler.py | 24 ++++- .../omnidreams_game_engine/game_map/loader.py | 5 +- .../omnidreams_game_engine/game_map/types.py | 28 ++++++ .../omnidreams_game_engine/scene.py | 7 +- .../tests/test_scene.py | 55 +++++------- 15 files changed, 277 insertions(+), 71 deletions(-) diff --git a/apps/crazy_robotaxi/README.md b/apps/crazy_robotaxi/README.md index 0617255dd..bbf5383eb 100644 --- a/apps/crazy_robotaxi/README.md +++ b/apps/crazy_robotaxi/README.md @@ -156,6 +156,6 @@ uv run --package crazy-robotaxi crazy-robotaxi-map preview-spawn \ path/to/city.robotaxi.yaml --spawn taxi_start --output taxi_start.png ``` -Spawn variants can define both a full `prompt` for normal play and a shorter +Each spawn can define both a full `prompt` for normal play and a shorter `prompt_context` base for `--live-edit-map-context`; dynamic road and motion clauses are appended only to the latter. diff --git a/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml b/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml index 9ef02f161..173476ed4 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml +++ b/apps/crazy_robotaxi/crazy_robotaxi/maps/boulevard_district.robotaxi.yaml @@ -122,6 +122,7 @@ nodes: # access surfaces rather than authored road edges. - id: southwest_parking_lot type: parking_lot + prompt_context: A small paved parking court sits among low-rise neighborhood buildings. connected_to: southwest_lot_driveway opening_vertex: 2 vertices: @@ -133,6 +134,7 @@ nodes: - {x_m: -24, y_m: -189} - id: south_parking_lot type: parking_lot + prompt_context: A paved commercial parking lot sits beside low-rise shops. connected_to: south_lot_driveway opening_vertex: 2 vertices: @@ -211,16 +213,16 @@ nodes: roads: # Spawn-area arterial and neighborhood grid. - - {id: west_arterial_approach, from: west_arterial_end, to: west_arterial_crossing, profile: arterial} - - {id: spawn_arterial, from: west_arterial_crossing, to: central_arterial_crossing, profile: arterial} - - {id: central_arterial, from: central_arterial_crossing, to: diagonal_arterial_crossing, profile: arterial} - - {id: west_north_lower, from: west_arterial_crossing, to: west_north_crossing, profile: street} + - {id: west_arterial_approach, from: west_arterial_end, to: west_arterial_crossing, profile: arterial, prompt_context: &western_boulevard_context "A broad tree-lined suburban boulevard passes detached homes and low-rise neighborhood buildings."} + - {id: spawn_arterial, from: west_arterial_crossing, to: central_arterial_crossing, profile: arterial, prompt_context: *western_boulevard_context} + - {id: central_arterial, from: central_arterial_crossing, to: diagonal_arterial_crossing, profile: arterial, prompt_context: *western_boulevard_context} + - {id: west_north_lower, from: west_arterial_crossing, to: west_north_crossing, profile: street, prompt_context: &residential_context "A quiet residential street has a mix of single-story and two-story homes, lawns, and mature trees."} - {id: west_north_upper, from: west_north_crossing, to: west_upper_crossing, profile: street} - {id: west_north_tail, from: west_upper_crossing, to: west_north_end, profile: street} - {id: central_north_lower, from: central_arterial_crossing, to: central_north_crossing, profile: street} - {id: central_north_upper, from: central_north_crossing, to: central_upper_crossing, profile: street} - {id: central_north_tail, from: central_upper_crossing, to: central_north_end, profile: local} - - {id: north_cross_street, from: west_north_crossing, to: central_north_crossing, profile: street} + - {id: north_cross_street, from: west_north_crossing, to: central_north_crossing, profile: street, prompt_context: *residential_context} - {id: upper_cross_street, from: west_upper_crossing, to: central_upper_crossing, profile: street} - {id: west_south_upper, from: west_arterial_crossing, to: west_south_crossing, profile: street} - {id: west_south_lower, from: west_south_crossing, to: west_lower_crossing, profile: street} @@ -236,7 +238,7 @@ roads: - {id: south_local_east, from: south_lot_driveway, to: south_local_end, profile: local} # Diagonal street and the two broad arterial branches. - - {id: diagonal_north_lower, from: diagonal_arterial_crossing, to: diagonal_bend_lower, profile: street} + - {id: diagonal_north_lower, from: diagonal_arterial_crossing, to: diagonal_bend_lower, profile: street, prompt_context: &diagonal_context "A diagonal neighborhood connector passes trees, detached homes, and small roadside buildings."} - {id: diagonal_north_middle, from: diagonal_bend_lower, to: diagonal_north_crossing, profile: street} - {id: diagonal_north_upper, from: diagonal_north_crossing, to: diagonal_bend_upper, profile: street} - {id: diagonal_north_tail, from: diagonal_bend_upper, to: diagonal_north_end, profile: street} @@ -245,6 +247,7 @@ roads: from: southwest_boulevard_end to: southwest_merge profile: arterial + prompt_context: *western_boulevard_context path: - {x_m: 184, y_m: -255} - {x_m: 205, y_m: -190} @@ -253,6 +256,7 @@ roads: from: diagonal_arterial_crossing to: arterial_merge_crossing profile: arterial + prompt_context: *western_boulevard_context path: - {x_m: 332, y_m: -8} - {x_m: 370, y_m: -39} @@ -291,15 +295,15 @@ roads: - {x_m: 710, y_m: 45} # Eastern arterial and large northern loop. - - {id: arterial_470_550, from: arterial_crossing_470, to: arterial_crossing_550, profile: arterial} + - {id: arterial_470_550, from: arterial_crossing_470, to: arterial_crossing_550, profile: arterial, prompt_context: &eastern_arterial_context "A long multi-lane eastern boulevard passes mixed low-rise commercial and residential blocks."} - {id: arterial_550_630, from: arterial_crossing_550, to: arterial_crossing_630, profile: arterial} - {id: arterial_630_710, from: arterial_crossing_630, to: arterial_crossing_710, profile: arterial} - - {id: arterial_710_800, from: arterial_crossing_710, to: arterial_crossing_800, profile: arterial} + - {id: arterial_710_800, from: arterial_crossing_710, to: arterial_crossing_800, profile: arterial, prompt_context: *eastern_arterial_context} - {id: arterial_800_860, from: arterial_crossing_800, to: arterial_crossing_860, profile: arterial} - {id: arterial_860_895, from: arterial_crossing_860, to: arterial_crossing_895, profile: arterial} - {id: arterial_895_955, from: arterial_crossing_895, to: east_corner_lot_driveway, profile: arterial} - {id: arterial_955_990, from: east_corner_lot_driveway, to: arterial_crossing_990, profile: arterial} - - {id: arterial_990_1082, from: arterial_crossing_990, to: arterial_crossing_1082, profile: arterial} + - {id: arterial_990_1082, from: arterial_crossing_990, to: arterial_crossing_1082, profile: arterial, prompt_context: *eastern_arterial_context} - {id: arterial_1082_1170, from: arterial_crossing_1082, to: arterial_crossing_1170, profile: arterial} - {id: arterial_1170_end, from: arterial_crossing_1170, to: east_arterial_end, profile: arterial} - id: east_north_loop @@ -313,7 +317,7 @@ roads: - {x_m: 895, y_m: 30} # Southern commercial grid and parking courts. - - {id: south_spine_630, from: arterial_crossing_630, to: south_crossing_630, profile: street} + - {id: south_spine_630, from: arterial_crossing_630, to: south_crossing_630, profile: street, prompt_context: &commercial_context "A low-rise commercial district has storefronts, paved parking courts, and sparse street trees."} - {id: south_710_upper, from: arterial_crossing_710, to: south_crossing_710, profile: street} - {id: south_710_lower, from: south_crossing_710, to: lower_crossing_710, profile: street} - {id: south_710_tail, from: lower_crossing_710, to: south_end_710, profile: local} @@ -321,14 +325,14 @@ roads: - {id: south_860_lower, from: south_crossing_860, to: lower_crossing_860, profile: local} - {id: south_990_upper, from: arterial_crossing_990, to: south_crossing_990, profile: street} - {id: south_990_lower, from: south_crossing_990, to: lower_crossing_990, profile: local} - - {id: upper_commercial_west, from: south_crossing_710, to: south_crossing_860, profile: local} + - {id: upper_commercial_west, from: south_crossing_710, to: south_crossing_860, profile: local, prompt_context: *commercial_context} - {id: upper_commercial_center_west, from: south_crossing_860, to: south_upper_lot_driveway, profile: local} - {id: upper_commercial_center_east, from: south_upper_lot_driveway, to: south_crossing_930, profile: local} - {id: upper_commercial_east, from: south_crossing_930, to: south_crossing_990, profile: local} - {id: commercial_930_spine, from: south_crossing_930, to: lower_crossing_930, profile: local} - {id: lower_commercial_west, from: lower_crossing_710, to: lower_lot_driveway, profile: local} - {id: lower_commercial_midwest, from: lower_lot_driveway, to: lower_crossing_860, profile: local} - - {id: lower_commercial_center, from: lower_crossing_860, to: lower_crossing_930, profile: local} + - {id: lower_commercial_center, from: lower_crossing_860, to: lower_crossing_930, profile: local, prompt_context: *commercial_context} - {id: lower_commercial_east, from: lower_crossing_930, to: lower_crossing_990, profile: local} - {id: lower_commercial_tail, from: lower_crossing_990, to: lower_crossing_1080, profile: local} - {id: east_south_cul_de_sac, from: lower_crossing_1080, to: south_cul_de_sac_1080, profile: local} @@ -341,11 +345,11 @@ roads: - {id: bottom_east_road, from: bottom_lot_driveway, to: south_crossing_630, profile: local} # Far-east surface loop and local northern spur. - - {id: far_east_north_spur, from: arterial_crossing_1082, to: far_east_north_crossing, profile: local} + - {id: far_east_north_spur, from: arterial_crossing_1082, to: far_east_north_crossing, profile: local, prompt_context: &far_east_context "A quieter far-eastern local loop passes detached homes, small lots, and mature trees."} - {id: far_east_north_tail, from: far_east_north_crossing, to: far_east_north_end, profile: local} - {id: far_east_loop_entry, from: arterial_crossing_1170, to: far_east_bend_southeast, profile: local} - {id: far_east_loop_east, from: far_east_bend_southeast, to: far_east_bend_northeast, profile: local} - - {id: far_east_loop_north, from: far_east_bend_northeast, to: far_east_bend_northwest, profile: local} + - {id: far_east_loop_north, from: far_east_bend_northeast, to: far_east_bend_northwest, profile: local, prompt_context: *far_east_context} - {id: far_east_loop_west, from: far_east_bend_northwest, to: far_east_bend_southwest, profile: local} - {id: far_east_loop_exit, from: far_east_bend_southwest, to: far_east_north_crossing, profile: local} @@ -419,3 +423,5 @@ spawns: A forward-facing view from a taxi moving through a quiet suburban district in daylight, with low commercial buildings, houses, landscaping, and parked cars. + prompt_context: >- + A forward-facing view from a taxi on a road through a city in daylight. diff --git a/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml b/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml index 1ce7a332b..241d7b699 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml +++ b/apps/crazy_robotaxi/crazy_robotaxi/maps/flashdreams_raceway.robotaxi.yaml @@ -39,16 +39,25 @@ roads: from: west_hairpin_exit to: start_finish profile: raceway + prompt_context: >- + The start-finish straight runs past packed grandstands, pit garages, + timing towers, sponsor gantries, and a black-and-white checkered line. - id: main_straight from: start_finish to: turn_one_entry profile: raceway + prompt_context: >- + A broad city-center racing straight is enclosed by concrete barriers, + tall safety fencing, colorful sponsor banners, and glass office towers. - id: turn_one from: turn_one_entry to: east_short_entry profile: raceway + prompt_context: >- + The circuit sweeps around a monumental civic plaza with stone steps, + fountains, flagpoles, dense crowds, and a skyline of modern towers. bezier: - control_points: - {x_m: 255, y_m: -160} @@ -59,11 +68,17 @@ roads: from: east_short_entry to: infield_turn_entry profile: raceway + prompt_context: >- + The race course passes a colorful entertainment district of neon signs, + theater marquees, rooftop billboards, and spectator balconies. - id: infield_turn from: infield_turn_entry to: infield_straight_entry profile: raceway + prompt_context: >- + A technical bend threads through a landscaped city park with mature + trees, flower beds, sculptures, and spectators behind safety fencing. bezier: - control_points: - {x_m: 290, y_m: -15} @@ -74,11 +89,17 @@ roads: from: infield_straight_entry to: hairpin_entry profile: raceway + prompt_context: >- + The infield straight follows a canal lined with brick warehouses, + iron footbridges, waterside cafes, race barriers, and fluttering flags. - id: infield_hairpin from: hairpin_entry to: hairpin_exit profile: raceway + prompt_context: >- + The tight hairpin circles a lively market square filled with striped + awnings, old masonry facades, cafe terraces, and cheering race fans. bezier: - control_points: - {x_m: 40, y_m: 20} @@ -93,11 +114,17 @@ roads: from: hairpin_exit to: north_sweeper_entry profile: raceway + prompt_context: >- + A fast tree-lined boulevard runs beside red-brick row houses, pocket + parks, pedestrian bridges, and long walls of racing advertisements. - id: north_sweeper from: north_sweeper_entry to: top_straight_entry profile: raceway + prompt_context: >- + The broad sweeper curves along a sunny waterfront with a marina, + sailboats, palms, promenade crowds, and the city skyline across the bay. bezier: - control_points: - {x_m: 290, y_m: 140} @@ -108,11 +135,17 @@ roads: from: top_straight_entry to: esses_entry profile: raceway + prompt_context: >- + The circuit charges through a museum district of bold contemporary + architecture, public art, reflecting pools, and elevated walkways. - id: technical_esses from: esses_entry to: esses_exit profile: raceway + prompt_context: >- + Rapid esses weave through a lush botanical garden with dense greenery, + glass conservatories, rocky landscaping, and close safety barriers. bezier: - control_points: - {x_m: -105, y_m: 250} @@ -131,11 +164,17 @@ roads: from: esses_exit to: west_hairpin_entry profile: raceway + prompt_context: >- + The west straight crosses a gritty arts district of converted factories, + vivid murals, steel fire escapes, food trucks, and overhead rail lines. - id: west_hairpin from: west_hairpin_entry to: west_hairpin_exit profile: raceway + prompt_context: >- + The final hairpin wraps around a busy harbor quarter with cargo cranes, + stacked containers, dockside warehouses, and grandstands above the quay. bezier: - control_points: - {x_m: -330, y_m: 50} @@ -176,3 +215,6 @@ spawns: road circuit in daylight, with varied-radius corners, technical esses, hairpins, safety barriers, grandstands, sponsor banners, and a clear start-finish straight. + prompt_context: >- + A forward-facing view from a taxi on a wide road racing circuit + through a city in daylight. diff --git a/apps/crazy_robotaxi/tests/maps/intersection_geometry.robotaxi.yaml b/apps/crazy_robotaxi/tests/maps/intersection_geometry.robotaxi.yaml index c241664ca..0e4280561 100644 --- a/apps/crazy_robotaxi/tests/maps/intersection_geometry.robotaxi.yaml +++ b/apps/crazy_robotaxi/tests/maps/intersection_geometry.robotaxi.yaml @@ -52,7 +52,5 @@ spawns: road: west_road lane: 2 distance_m: 30 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: A skewed three-way city intersection. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: A skewed three-way city intersection. diff --git a/apps/crazy_robotaxi/tests/maps/parking_driveway.robotaxi.yaml b/apps/crazy_robotaxi/tests/maps/parking_driveway.robotaxi.yaml index 45d3c64d6..a7066b3a5 100644 --- a/apps/crazy_robotaxi/tests/maps/parking_driveway.robotaxi.yaml +++ b/apps/crazy_robotaxi/tests/maps/parking_driveway.robotaxi.yaml @@ -50,7 +50,5 @@ spawns: road: west_road lane: 1 distance_m: 30 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: A street beside a small parking lot. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: A street beside a small parking lot. diff --git a/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml b/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml index 3b298e559..56f25d5ef 100644 --- a/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml +++ b/apps/crazy_robotaxi/tests/maps/race_course.robotaxi.yaml @@ -51,3 +51,4 @@ spawns: distance_m: 30 image: package://omnidreams_game_engine/screenshot.jpg prompt: A car racing on a rectangular city loop. + prompt_context: A forward-facing view from a car on a city road. diff --git a/apps/crazy_robotaxi/tests/maps/traffic_loop.robotaxi.yaml b/apps/crazy_robotaxi/tests/maps/traffic_loop.robotaxi.yaml index 458b802e2..73190bbbc 100644 --- a/apps/crazy_robotaxi/tests/maps/traffic_loop.robotaxi.yaml +++ b/apps/crazy_robotaxi/tests/maps/traffic_loop.robotaxi.yaml @@ -42,7 +42,5 @@ spawns: road: south_west lane: 1 distance_m: 30 - variants: - default: - image: package://omnidreams_game_engine/screenshot.jpg - prompt: A car on a rectangular city loop. + image: package://omnidreams_game_engine/screenshot.jpg + prompt: A car on a rectangular city loop. diff --git a/apps/crazy_robotaxi/tests/test_maps.py b/apps/crazy_robotaxi/tests/test_maps.py index 6f32e7bdd..73670ebde 100644 --- a/apps/crazy_robotaxi/tests/test_maps.py +++ b/apps/crazy_robotaxi/tests/test_maps.py @@ -9,12 +9,24 @@ import numpy as np import pytest -from omnidreams_game_engine.game_map import load_game_map +import yaml from omnidreams_game_engine.config import RasterConfig +from omnidreams_game_engine.game_map import ( + GameMapError, + compile_game_map, + load_game_map, + load_game_map_header, +) +from omnidreams_game_engine.game_map.types import ( + game_map_from_dict, + game_map_to_dict, +) from omnidreams_game_engine.scene import SceneRequest, load_scene pytestmark = pytest.mark.ci_cpu +_MAP_FIXTURES = Path(__file__).parent / "maps" + @pytest.mark.parametrize( "filename", @@ -27,6 +39,76 @@ def test_shipped_map_is_valid(filename: str) -> None: assert game_map.map_id.startswith("crazy-robotaxi-") assert game_map.spawns assert game_map.lanes + assert game_map.default_spawn.prompt_context + assert game_map.default_spawn.prompt_context != game_map.default_spawn.prompt + + +def test_prompt_context_is_trimmed_and_round_trips(tmp_path: Path) -> None: + source = yaml.safe_load( + (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() + ) + source["nodes"][0]["prompt_context"] = " A neighborhood landmark. " + source["roads"][0]["prompt_context"] = " Detached homes line the road. " + source["spawns"][0]["prompt_context"] = " A forward-facing road view. " + path = tmp_path / "prompt-context.robotaxi.yaml" + path.write_text(yaml.safe_dump(source, sort_keys=False)) + + original = load_game_map(path) + restored = game_map_from_dict(game_map_to_dict(original)) + + assert original.topology.nodes[0].prompt_context == "A neighborhood landmark." + assert original.topology.roads[0].prompt_context == ( + "Detached homes line the road." + ) + assert original.default_spawn.prompt_context == "A forward-facing road view." + assert game_map_to_dict(restored) == game_map_to_dict(original) + + +@pytest.mark.parametrize("value", ["", " ", 42, ["not", "text"]]) +def test_prompt_context_requires_nonempty_text(tmp_path: Path, value: object) -> None: + source = yaml.safe_load( + (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() + ) + source["roads"][0]["prompt_context"] = value + path = tmp_path / "invalid-prompt-context.robotaxi.yaml" + path.write_text(yaml.safe_dump(source, sort_keys=False)) + + with pytest.raises(GameMapError, match="prompt_context must be a nonempty string"): + load_game_map(path) + + +@pytest.mark.parametrize("value", ["", " ", 42, ["not", "text"]]) +def test_spawn_prompt_context_requires_nonempty_text( + tmp_path: Path, value: object +) -> None: + source = yaml.safe_load( + (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() + ) + source["spawns"][0]["prompt_context"] = value + path = tmp_path / "invalid-spawn-prompt-context.robotaxi.yaml" + path.write_text(yaml.safe_dump(source, sort_keys=False)) + + with pytest.raises(GameMapError, match="prompt_context must be a nonempty string"): + load_game_map(path) + + +def test_prompt_context_change_invalidates_compiler_cache(tmp_path: Path) -> None: + source = yaml.safe_load( + (_MAP_FIXTURES / "intersection_geometry.robotaxi.yaml").read_text() + ) + path = tmp_path / "cached-prompt-context.robotaxi.yaml" + path.write_text(yaml.safe_dump(source, sort_keys=False)) + cache_root = tmp_path / "cache" + + first = compile_game_map(path, cache_root=cache_root) + assert compile_game_map(path, cache_root=cache_root).cache_hit + source["roads"][0]["prompt_context"] = "A changed roadside setting." + path.write_text(yaml.safe_dump(source, sort_keys=False)) + changed = compile_game_map(path, cache_root=cache_root) + + assert first.cache_hit is False + assert changed.cache_hit is False + assert changed.archive_path != first.archive_path def test_compiled_map_uses_canonical_spawn_conditioning( @@ -40,8 +122,14 @@ def test_compiled_map_uses_canonical_spawn_conditioning( SceneRequest(map_path=path), RasterConfig(width=64, height=32, compute_device="automatic"), ) + context_scene = load_scene( + SceneRequest(map_path=path, use_prompt_context=True), + RasterConfig(width=64, height=32, compute_device="automatic"), + ) assert scene.prompt == game_map.default_spawn.prompt + assert context_scene.prompt == game_map.default_spawn.prompt_context + assert context_scene.scene_path != scene.scene_path assert scene.initial_rgb.shape == (32, 64, 3) with zipfile.ZipFile(scene.scene_path) as archive: names = set(archive.namelist()) diff --git a/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md b/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md index 34f8ae61a..2ac9e80c3 100644 --- a/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md +++ b/apps/omnidreams_game_engine/NODE_GRAPH_MAP_FORMAT.md @@ -93,6 +93,28 @@ positive-area overlap or share a boundary edge; isolated point tangency is allowed. Roads, parking lots, and other surfaces therefore cannot be layered over one another to repair topology. +Nodes and roads may carry an optional nonempty `prompt_context` string. A game +can append this authored environmental description to a model prompt when the +vehicle is on or approaching that topology element: + +```yaml +nodes: + - id: market_junction + type: intersection + pose: {x_m: 0, y_m: 0} + prompt_context: A busy market plaza fills the intersection. + +roads: + - id: market_street + from: west_end + to: market_junction + profile: neighborhood + prompt_context: Small storefronts and awnings line both sides of the road. +``` + +Leading and trailing whitespace is removed. Empty or non-string values are +rejected, and prompt context participates in the compiled-map cache key. + ## Nodes All non-parking nodes require `id`, `type`, and `pose`. Their remaining required @@ -416,6 +438,7 @@ spawns: distance_m: 5 image: seed.png prompt: A forward-facing taxi view in a quiet neighborhood at daylight. + prompt_context: A forward-facing taxi view on a road at daylight. ``` Every spawn requires a non-empty `prompt`. `image` is optional; when omitted @@ -425,9 +448,14 @@ runtime front camera. This fallback shows aligned road surfaces, boundaries, curbs, and markings, but does not synthesize scenery. Use it as a robust placeholder, not as a photorealistic authoring result. +`prompt` is the complete standalone scene description. `prompt_context` is an +optional shorter base prompt for applications that append live road, topology, +and motion context. When that mode is selected, `prompt_context` is used if +present and otherwise falls back to `prompt`. + Authored images may be map-relative paths or `package://package/resource` references. Resolved geometry, compiler and fallback-renderer code, seed -images, and prompts participate in the compiled-map cache key. +images, and both prompt forms participate in the compiled-map cache key. ## Validation summary diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py index 1dd797bec..66c1b1c7c 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/_schema.py @@ -201,7 +201,7 @@ def _parse_map_identity(doc: dict[str, Any]) -> tuple[str, str]: def _parse_spawn_conditioning( raw_spawn: dict[str, Any], source_path: Path -) -> tuple[str | None, str]: +) -> tuple[str | None, str, str | None]: image_value = raw_spawn.get("image") image = None if image_value is None else str(image_value).strip() prompt = str(raw_spawn.get("prompt", "")).strip() @@ -209,9 +209,17 @@ def _parse_spawn_conditioning( raise GameMapError("Every spawn requires a non-empty prompt") if image_value is not None and not image: raise GameMapError("spawn.image must not be empty") + prompt_context_value = raw_spawn.get("prompt_context") + if prompt_context_value is not None and ( + not isinstance(prompt_context_value, str) or not prompt_context_value.strip() + ): + raise GameMapError("spawn.prompt_context must be a nonempty string") + prompt_context = ( + None if prompt_context_value is None else prompt_context_value.strip() + ) if image is not None: resolve_seed_asset(source_path, image) - return image, prompt + return image, prompt, prompt_context def load_game_map_header(path: Path) -> GameMapHeader: diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py index 6d31d5773..8bdea2452 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/compiler.py @@ -409,7 +409,12 @@ def _trajectory(game_map: ResolvedGameMap) -> dict[str, object]: } -def _write_archive(path: Path, game_map: ResolvedGameMap) -> None: +def _write_archive( + path: Path, + game_map: ResolvedGameMap, + *, + use_prompt_context: bool, +) -> None: spawn = game_map.default_spawn with zipfile.ZipFile(path, mode="w", compression=zipfile.ZIP_STORED) as archive: archive.writestr( @@ -424,7 +429,12 @@ def _write_archive(path: Path, game_map: ResolvedGameMap) -> None: "mesh_ground.ply", save_mesh_vf(game_map.ground_vertices, game_map.ground_faces), ) - archive.writestr("prompt.txt", spawn.prompt) + archive.writestr( + "prompt.txt", + spawn.prompt_context or spawn.prompt + if use_prompt_context + else spawn.prompt, + ) if spawn.image is None: _write_image_array( archive, @@ -456,6 +466,7 @@ def _write_archive(path: Path, game_map: ResolvedGameMap) -> None: def compile_game_map( path: Path, *, + use_prompt_context: bool = False, cache_root: Path | None = None, force: bool = False, ) -> CompiledGameMap: @@ -464,7 +475,8 @@ def compile_game_map( digest = _digest(game_map) root = _cache_root() if cache_root is None else Path(cache_root) output_dir = root / digest - archive_path = output_dir / f"{game_map.map_id}.usdz" + context_suffix = "-context" if use_prompt_context else "" + archive_path = output_dir / f"{game_map.map_id}{context_suffix}.usdz" lock = FileLock(str(root / f"{digest}.lock")) root.mkdir(parents=True, exist_ok=True) with lock: @@ -484,7 +496,11 @@ def compile_game_map( os.close(file_descriptor) temporary = Path(temporary_name) try: - _write_archive(temporary, game_map) + _write_archive( + temporary, + game_map, + use_prompt_context=use_prompt_context, + ) temporary.replace(archive_path) finally: temporary.unlink(missing_ok=True) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py index 29e0a4b7d..e35fb7404 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/loader.py @@ -2723,7 +2723,7 @@ def _spawn( lane_by_id: dict[str, _LaneBuild], ) -> GameMapSpawn: required = {"id", "road", "lane", "distance_m", "prompt"} - unknown = set(raw) - (required | {"image"}) + unknown = set(raw) - (required | {"image", "prompt_context"}) missing = required - set(raw) if missing or unknown: raise GameMapError( @@ -2754,7 +2754,7 @@ def _spawn( alpha = (distance - cumulative[segment]) / max(float(lengths[segment]), 1.0e-9) position = points[segment] + alpha * (points[segment + 1] - points[segment]) direction = points[segment + 1] - points[segment] - image, prompt = _parse_spawn_conditioning(raw, source_path) + image, prompt, prompt_context = _parse_spawn_conditioning(raw, source_path) return GameMapSpawn( spawn_id=spawn_id, lane_id=lane_id, @@ -2763,6 +2763,7 @@ def _spawn( yaw_rad=math.atan2(float(direction[1]), float(direction[0])), image=image, prompt=prompt, + prompt_context=prompt_context, ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py index d35cac14d..a88393911 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/game_map/types.py @@ -87,6 +87,9 @@ class GameMapNode: polygon_vertices_xy: tuple[tuple[float, float], ...] = () """Authored map-space polygon vertices for a parking-lot node.""" + prompt_context: str | None = None + """Optional authored environmental context appended to model prompts.""" + @dataclass(frozen=True, eq=False) class GameMapRoad: @@ -110,6 +113,9 @@ class GameMapRoad: bezier_spans_world: tuple[FloatArray, ...] """Compiler-generated map-space cubic spans shaped ``[4, 3]``; empty is straight.""" + prompt_context: str | None = None + """Optional authored environmental context appended to model prompts.""" + def __eq__(self, other: object) -> bool: """Compare road metadata and cubic span values.""" if not isinstance(other, GameMapRoad): @@ -120,6 +126,7 @@ def __eq__(self, other: object) -> bool: and self.to_node_id == other.to_node_id and self.profile_id == other.profile_id and self.attributes == other.attributes + and self.prompt_context == other.prompt_context and len(self.bezier_spans_world) == len(other.bezier_spans_world) and all( np.array_equal(first, second) @@ -191,6 +198,9 @@ class GameMapSpawn: prompt: str """World-model text prompt paired with the spawn's seed image.""" + prompt_context: str | None = None + """Optional shorter base prompt used with dynamic map context.""" + @dataclass(frozen=True) class GameMapTrafficVehicle: @@ -470,6 +480,7 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "polygon_vertices_xy": [ list(point) for point in node.polygon_vertices_xy ], + "prompt_context": node.prompt_context, } for node in game_map.topology.nodes ], @@ -483,6 +494,7 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "bezier_spans_world": [ span.tolist() for span in road.bezier_spans_world ], + "prompt_context": road.prompt_context, } for road in game_map.topology.roads ], @@ -578,6 +590,7 @@ def game_map_to_dict(game_map: ResolvedGameMap) -> dict[str, Any]: "yaw_rad": spawn.yaw_rad, "image": spawn.image, "prompt": spawn.prompt, + "prompt_context": spawn.prompt_context, } for spawn in game_map.spawns ], @@ -691,6 +704,11 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: (float(point[0]), float(point[1])) for point in raw.get("polygon_vertices_xy", ()) ), + prompt_context=( + None + if raw.get("prompt_context") is None + else str(raw["prompt_context"]) + ), ) for raw in raw_topology["nodes"] ), @@ -707,6 +725,11 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: np.asarray(span, dtype=np.float32) for span in raw["bezier_spans_world"] ), + prompt_context=( + None + if raw.get("prompt_context") is None + else str(raw["prompt_context"]) + ), ) for raw in raw_topology["roads"] ), @@ -800,6 +823,11 @@ def game_map_from_dict(value: dict[str, Any]) -> ResolvedGameMap: yaw_rad=float(raw["yaw_rad"]), image=None if raw.get("image") is None else str(raw["image"]), prompt=str(raw["prompt"]), + prompt_context=( + None + if raw.get("prompt_context") is None + else str(raw["prompt_context"]) + ), ) for raw in value["spawns"] ) diff --git a/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py b/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py index a6a83efce..6a86ca233 100644 --- a/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py +++ b/apps/omnidreams_game_engine/omnidreams_game_engine/scene.py @@ -20,12 +20,17 @@ class SceneRequest: map_path: Path camera_name: str = "camera_front_wide_120fov" + use_prompt_context: bool = False force_recompile: bool = False def load_scene(request: SceneRequest, raster: RasterConfig) -> SceneDefinition: """Compile an authored map if necessary and load its runtime scene.""" - compiled = compile_game_map(request.map_path, force=request.force_recompile) + compiled = compile_game_map( + request.map_path, + use_prompt_context=request.use_prompt_context, + force=request.force_recompile, + ) return load_scene_bundle( scene_path=compiled.archive_path, camera_name=request.camera_name, diff --git a/apps/omnidreams_game_engine/tests/test_scene.py b/apps/omnidreams_game_engine/tests/test_scene.py index 0f18f2603..73b65016b 100644 --- a/apps/omnidreams_game_engine/tests/test_scene.py +++ b/apps/omnidreams_game_engine/tests/test_scene.py @@ -9,59 +9,48 @@ import pytest from omnidreams_game_engine import scene as scene_module from omnidreams_game_engine.config import RasterConfig -from omnidreams_game_engine.game_map.types import GameMapVisualVariant from omnidreams_game_engine.scene import SceneRequest, load_scene pytestmark = pytest.mark.ci_cpu -@pytest.mark.parametrize( - ("use_prompt_context", "explicit_prompt", "context_prompt", "expected"), - [ - (False, None, "context prompt", None), - (True, None, "context prompt", "context prompt"), - (True, None, None, "full prompt"), - (True, "explicit prompt", "context prompt", "explicit prompt"), - ], -) -def test_scene_selects_context_prompt_only_when_requested( +@pytest.mark.parametrize("use_prompt_context", [False, True]) +def test_scene_forwards_context_selection_to_map_compiler( monkeypatch: pytest.MonkeyPatch, use_prompt_context: bool, - explicit_prompt: str | None, - context_prompt: str | None, - expected: str | None, ) -> None: - variants = ( - GameMapVisualVariant( - name="default", - image=None, - prompt="full prompt", - prompt_context=context_prompt, - ), - ) - compiled = SimpleNamespace( - archive_path=Path("compiled.usdz"), - game_map=SimpleNamespace( - default_spawn=SimpleNamespace(variants=variants), - ), - ) - captured: list[str | None] = [] + compiled = SimpleNamespace(archive_path=Path("compiled.usdz")) + compiler_options: list[dict[str, object]] = [] + loader_options: list[dict[str, object]] = [] + + def compile_map(*args: object, **kwargs: object) -> SimpleNamespace: + del args + compiler_options.append(kwargs) + return compiled + monkeypatch.setattr( - scene_module, "compile_game_map", lambda *args, **kwargs: compiled + scene_module, + "compile_game_map", + compile_map, ) monkeypatch.setattr( scene_module, "load_scene_bundle", - lambda **kwargs: captured.append(kwargs["prompt_override"]), + lambda **kwargs: loader_options.append(kwargs), ) load_scene( SceneRequest( map_path=Path("map.robotaxi.yaml"), - prompt=explicit_prompt, use_prompt_context=use_prompt_context, ), RasterConfig(), ) - assert captured == [expected] + assert compiler_options == [ + { + "use_prompt_context": use_prompt_context, + "force": False, + } + ] + assert "prompt_override" not in loader_options[0] From 9cfb2fe77bf491e26f76d3c9b9b4edcfec51c5c3 Mon Sep 17 00:00:00 2001 From: Aidan Foster Date: Tue, 1 Sep 2026 10:38:43 -0700 Subject: [PATCH 3/3] [crazy-robotaxi] Remove legacy prompt override --- apps/crazy_robotaxi/crazy_robotaxi/application.py | 3 --- apps/crazy_robotaxi/crazy_robotaxi/session.py | 6 ------ 2 files changed, 9 deletions(-) diff --git a/apps/crazy_robotaxi/crazy_robotaxi/application.py b/apps/crazy_robotaxi/crazy_robotaxi/application.py index 0ebe56c5e..ae972b517 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/application.py +++ b/apps/crazy_robotaxi/crazy_robotaxi/application.py @@ -264,7 +264,6 @@ def init(self, commandline_args: Sequence[str]) -> None: scene_request=SceneRequest( map_path=map_path.expanduser(), camera_name=engine_settings.map.camera, - prompt=engine_settings.map.prompt, use_prompt_context=game_settings.live_edit.map_context.enabled, force_recompile=engine_settings.map.force_recompile, ), @@ -322,7 +321,6 @@ def _resolve_engine_settings(self, args: argparse.Namespace) -> EngineSettings: settings.map, path=args.map, camera=args.camera, - prompt=args.prompt, force_recompile=args.force_map_recompile, ), rendering=replace( @@ -524,7 +522,6 @@ def _parser( parser.add_argument("--width", type=int, default=defaults.width) parser.add_argument("--height", type=int, default=defaults.height) parser.add_argument("--camera", default="camera_front_wide_120fov") - parser.add_argument("--prompt") parser.add_argument("--force-map-recompile", action="store_true") parser.add_argument("--device", default="cuda") parser.add_argument("--total-blocks", type=int) diff --git a/apps/crazy_robotaxi/crazy_robotaxi/session.py b/apps/crazy_robotaxi/crazy_robotaxi/session.py index 1fa5e7cb6..c6100abd6 100644 --- a/apps/crazy_robotaxi/crazy_robotaxi/session.py +++ b/apps/crazy_robotaxi/crazy_robotaxi/session.py @@ -153,12 +153,6 @@ def select_game(self, selection: GameSelection) -> None: request = replace( self.config.scene_request, map_path=option.path, - prompt=( - self.config.scene_request.prompt - if option.path - == self.config.scene_request.map_path.expanduser().resolve() - else None - ), ) scene = self.scene_factory(request, self.config.renderer.raster) self.close()