| name | scenario-writing |
| description | This skill should be used when the user asks to "write a scenario", "create a CARLA scenario", "add a test scenario", "write a concrete scenario", "create a logical scenario", "add constraints", "run multirun", "spawn NPC", "add condition", "add action", mentions scenario writing for autoware_carla_scenario, or discusses CARLA test scenario creation workflow. Provides comprehensive guidance for the full scenario lifecycle from concrete to logical scenarios. |
CARLA Scenario Writing Guide
Purpose
Guide the interactive creation of CARLA test scenarios for the autoware_carla_scenario package. Cover the full lifecycle: concrete scenario authoring, parameter tuning, logical scenario conversion, constraint definition, and multirun execution.
Scenario Writing Workflow
Follow these stages in order. Commit after completing the concrete scenario stage.
Stage 1: Concrete Scenario
Create a Python class inheriting BaseScenario with Lanelet2 spawn coordinates.
Critical rules:
- Always specify spawn positions in Lanelet2 coordinate system (
Lanelet2Pose(lanelet_id=..., s=...)). The OpenDRIVE converter auto-assigns Road/Lane IDs, so OpenDRIVE coordinates break when the converter changes.
- Use
_setup_ego_spawn() and snap_to_carla_road() for coordinate conversion with ground projection. Without ground projection, ~1 in 30 spawns fail due to clipping into terrain or roadside objects.
- Guard against infinite loops. Unlike OpenSCENARIO-DSL, scenarios run as Python — ensure all tick loops have termination conditions (timeout, pass/fail).
- Prefer composing existing Conditions/Actions over creating new ones. Proper Condition/Action objects leave viewer logs for debugging.
File structure for a new scenario:
- Python file:
autoware_carla_scenario/src/autoware_carla_scenario/examples/<scenario_name>.py
- Dataclass config: Add to
examples/configs.py
- YAML config:
examples/conf/scenario/<scenario_name>/<variant>.yaml
- Register in
examples/run.py: Add import, config class, and build_scenario() branch
Scenario class pattern:
class MyScenario(BaseScenario):
def __init__(self, ego_config, spawn_pose, config=None, ground_projection=None):
super().__init__(ego_config, spawn_pose=spawn_pose, ground_projection=ground_projection)
self._config = config or MyScenarioConfig()
def setup(self):
self._setup_ego_spawn()
def is_done(self):
return False
Stage 2: Debug Concrete Scenario
Use the debug-concrete-scenario command (or scenario-debugging skill) to execute, analyze, and iterate on the scenario until it passes.
This stage autonomously runs the execute → analyze → fix loop. See the scenario-debugging skill for log analysis patterns and common failure fixes. Once the user confirms the behavior matches intent, proceed to Stage 3.
Stage 3: Review and Define Logical Scenario Parameters
Use the scenario-review skill to analyze the parameter design before defining logical parameters. The review skill provides a structured process for:
- Identifying candidate parameters from the concrete scenario
- Analyzing orthogonality between parameter pairs
- Classifying each candidate as parameter, binding, Python-computed, or constant
- Validating NPC dependency design (NPC positions derived from ego must be computed in Python, not swept)
- Verifying sweeper compatibility (constraint/binding types exist)
- Presenting a review summary for user confirmation
After the review is confirmed, write parameters as a dataclass in configs.py and corresponding YAML in conf/scenario/. Concrete and logical parameters share the same YAML file.
Stage 4: Discover Constraints/Bindings and Add Sweep Config
Do not rely on a hardcoded list. Dynamically discover available Constraint and Binding types:
- Find all classes inheriting from
BaseConstraint:
- Search for
class.*BaseConstraint in autoware_carla_scenario/src/autoware_carla_scenario/sweeper/constraints.py
- Read the
build_constraint() factory function to see the type: string → class mapping
- Find all classes inheriting from
BaseBinding:
- Search for
class.*BaseBinding in autoware_carla_scenario/src/autoware_carla_scenario/sweeper/bindings.py
- Read the
build_binding() factory function to see the type: string → class mapping
- Read constructor signatures to understand YAML parameters for each type
When existing Constraints/Bindings are insufficient:
- Propose the new Constraint/Binding to the user with rationale
- Wait for approval before implementing
- Inherit from
BaseConstraint or BaseBinding
- Implement the required interface (
check() for constraints, resolve() for bindings)
- Register in the
build_constraint()/build_binding() factory function
- Export from the appropriate
__init__.py
Then edit the sweep: section in the scenario YAML. Avoid hardcoding specific Lanelet IDs in logical scenario constraints — this breaks cross-map portability.
Map-specific exclusions (like no_3d_model_lanelet_ids) are centralized in conf/map/<map_name>.yaml.
See references/constraints-reference.md for YAML structure examples and patterns.
Tip: Insert type: equals, value: any constraints as placeholders. After multirun, replace with concrete values from successful runs to aid debugging.
Stage 5: Run Logical Scenario
uv run scenario scenario=<name>/<variant> hydra/sweeper=lanelet_constraint --multirun
uv run scenario scenario=<name>/<variant>
uv run scenario scenario=<name>/<variant> map=nishishinjuku
Warning: Omitting hydra/sweeper=lanelet_constraint with --multirun runs only the concrete scenario, not the sweep.
Discovering Available Conditions and Actions
Do not rely on a hardcoded list. Dynamically discover available types:
- Find all classes inheriting from
BaseCondition:
- Search for
class.*BaseCondition and class.*Condition.*BaseCondition in autoware_carla_scenario/src/
- Also check
conditions/composition/ for composite conditions
- Find all classes inheriting from
BaseAction:
- Search for
class.*BaseAction and class.*Action.*BaseAction
- Read constructor signatures to understand parameters
- Check
__init__.py exports for the public API
When existing Conditions/Actions are insufficient:
- Propose the new Condition/Action to the user with rationale
- Wait for approval before implementing
- Inherit from
BaseCondition or BaseAction
- Implement
check() (conditions) or execute() (actions)
- Export from appropriate
__init__.py
Additional Resources
Reference Files
For detailed patterns and type catalogs, consult:
references/constraints-reference.md — Sweeper constraint types, binding types, and YAML examples
references/scenario-patterns.md — Complete scenario examples and common composition patterns
Key Source Locations
- Conditions:
autoware_carla_scenario/src/autoware_carla_scenario/conditions/
- Actions:
autoware_carla_scenario/src/autoware_carla_scenario/actions/
- Coordinate transforms:
autoware_carla_scenario/src/autoware_carla_scenario/coordinate/
- Sweeper:
autoware_carla_scenario/src/autoware_carla_scenario/sweeper/
- Example scenarios:
autoware_carla_scenario/src/autoware_carla_scenario/examples/
- Hydra configs:
autoware_carla_scenario/src/autoware_carla_scenario/examples/conf/