Roadrunner Scenario Authoring
MathWorksOfficialCreated by MathWorks
- Skill
- Tool integration
- Software development
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for creating RoadRunner scenarios using the roadrunnerAPI in MATLAB. It covers the end-to-end process of scene awareness, actor placement, route definition, and complex phase logic construction. The skill emphasizes a physics-first design approach, requiring users to perform spatial and kinematic analysis before implementing scenario logic to ensure valid, reproducible simulation outcomes.
What it does
- Automates actor placement and anchoring to road geometry.
- Defines complex scenario logic including phases, triggers, and actor actions.
- Supports programmatic creation of routes and waypoints for path-following.
- Provides helper scripts for scene awareness and HD Map analysis.
- Validates scenario structure to identify potential simulation failures.
- Enables batch processing for multi-actor scenario configurations.
When to use it
- When you need to generate multiple or complex driving scenarios programmatically.
- When performing automated testing of autonomous driving algorithms.
- When you need to ensure precise spatial relationships between actors.
- When building custom scenario libraries for simulation-based validation.
Advantages
- Reduces manual effort in scenario setup and actor positioning.
- Ensures consistency and repeatability across large scenario sets.
- Integrates directly with MATLAB for data-driven scenario generation.
- Provides robust validation to catch configuration errors before simulation.
Usage examples
- “How do I add a lead vehicle and an ego vehicle for a cut-in scenario?”
- The agent will guide you to use helperSceneAwareness to identify lane positions, define actorSpecs for both vehicles, and use helperPlaceActors to place them before setting up the cut-in phase logic.
- “How can I make a pedestrian cross the road in my scenario?”
- The agent will instruct you to load a character asset, create a route with waypoints that cross the road, and ensure the route segments are set to freeform to allow pathing across the road surface.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/matlab/roadrunner-scenario-authoring/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip roadrunner-scenario-authoring.zip -d .agents/skills
Documentation by MathWorks
RoadRunner Scenario Authoring
Programmatically create RoadRunner scenarios from MATLAB: actors, routes, phase logic, and validation — all via roadrunnerAPI.
When to Use
Adding actors (vehicles, pedestrians, objects) to a RoadRunner scenario
Creating routes and waypoints for actors
Building scenario logic: phases, conditions, actions
Authoring common patterns: cut-in, pedestrian crossing, lead-follow, emergency brake
Placing actors on roads using anchor-based or HD Map positioning
Validating scenario structure before simulation
When NOT to Use
Connecting to or launching RoadRunner → use
roadrunner-coreBuilding scenarios from recorded sensor data → use
matlab-use-scenario-builderAuthoring road geometry (lanes, junctions) → use
roadrunner-rrhd-authoringImporting maps or scenes → use
roadrunner-import-sceneSimulating or exporting scenarios → use
roadrunner-scenario-simulating
Workflow
1. Ensure Session
Verify rrApp exists. If not, ensure RoadRunner is connected first (e.g., via roadrunner-core or manually).
if ~exist('rrApp', 'var') || ~isvalid(rrApp)
error("No active RoadRunner session. Use the roadrunner-core skill.");
endPath setup: Before calling helper functions, ensure the scripts directory is on the MATLAB path:
addpath('<path-to-skill>/scripts');2. Initialize Scenario
openScene(rrApp, sceneName);
newScenario(rrApp);
rrApi = roadrunnerAPI(rrApp);
rrs = rrApi.Scenario;
phaseLogic = rrs.PhaseLogic;
rrprj = rrApi.Project;3. Scene Awareness
Before placing actors, survey the scene to find valid lane positions. Use the helperSceneAwareness script for automated analysis, or query manually via HD Map export:
sceneInfo = helperSceneAwareness(rrApp, NumActors=2, ScenarioType="cut-in");See references/scene-awareness.md for manual HD Map query patterns when the helper is unavailable.
4. Add Actors
Option A — Batch placement (recommended for 2+ actors):
actorSpecs(1) = struct(Name="Ego", AssetPath="Vehicles/Sedan.fbx", ...
AssetType="VehicleAsset", LaneIndex=1, Fraction=0.1, Speed=15);
[actors, report] = helperPlaceActors(rrs, rrprj, phaseLogic, sceneInfo.HDMap, actorSpecs);
ego = actors{1}; % cell array — use curly bracesOption B — Manual placement:
vehicleAsset = getAsset(rrprj, "Vehicles/Sedan.fbx", "VehicleAsset");
actor = addActor(rrs, vehicleAsset, position);
actor.Name = "Ego";
autoAnchor(actor.InitialPoint); % Snaps to nearest roadPost-placement check: Verify actor.InitialPoint.WorldPosition is NOT [0 0 0].
See references/asset-catalog.md for available vehicle/character paths.
5. Position Actors (Anchoring)
Option A — Scene anchors available:
anchors = getAnchors(rrApp);
% Use remapAnchor for cross-scene portability (not findSceneAnchor)
anchorPt = findSceneAnchor(rrs, anchors(1).Name);
anchorToPoint(actor.InitialPoint, anchorPt);
actor.InitialPoint.ForwardOffset = 20;
actor.InitialPoint.LaneOffset = 1;Option B — No scene anchors (use autoAnchor):
actor = addActor(rrs, asset, approximatePosition);
autoAnchor(actor.InitialPoint); % Must be within 5m of roadOption C — Relative to another actor:
anchorToPoint(target.InitialPoint, ego.InitialPoint);
target.InitialPoint.ForwardOffset = 30;
target.InitialPoint.LaneOffset = 1;6. Create Routes (When Needed)
Routes put actors in path-following mode. Actors without routes drive in lane-following mode along their anchored lane.
route = actor.InitialPoint.Route;
fwdPt = addPoint(route, actor.InitialPoint.WorldPosition + [20 0 0]);
autoAnchor(fwdPt);
% For vehicles: disable freeform so route follows road surface
% (Do NOT do this for pedestrians — they need freeform to cross roads)
for i = 1:numel(route.Segments)
route.Segments(i).Freeform = false;
endWhen to add routes:
Character/pedestrian actors — ALWAYS required (validation fails without them)
Vehicles that follow a specific path (e.g., ego driving straight)
Vehicles that do NOT need
ChangeLaneAction
When NOT to add routes:
Vehicles that need
ChangeLaneAction— they MUST be in lane-following mode (no routes)Vehicles that need
ChangeLateralOffsetAction— same requirement
Route point rules:
Always use
autoAnchorfor route points — position must be within 5m of roadDo NOT use
anchorToPoint+ForwardOffseton route points — causes validation failureKeep route offset small (20m) to stay on the road
For junction turns: use multiple waypoints (pre-junction, post-junction, exit) for smooth path
After adding route points, set
seg.Freeform = falseon each segment to follow road geometry (freeform routes ignore road surface and may float above/below the road)
7. Build Phase Logic
See references/actions-and-conditions.md for the complete catalog.
% Get actor's initial phase (auto-created with addActor)
initPhase = initialPhaseForActor(phaseLogic, actor);
% Modify default speed (initial phase already has ChangeSpeedAction)
initPhase.Actions(1).Speed = 20;
% Add sequential phase
nextPhase = addPhaseInSerial(phaseLogic, initPhase, "ActorActionPhase");
nextPhase.Actor = actor; % REQUIRED — never omit
% Set trigger condition on initial phase
cond = setEndCondition(initPhase, "LongitudinalDistanceToActorCondition");
cond.Actor = actor; % REQUIRED
cond.ReferenceActor = otherActor;
cond.Distance = 10;
% Add action to next phase
action = addAction(nextPhase, "ChangeLaneAction");
action.Direction = "left";Multi-actor phase logic: Each actor's phase chain is independent. Any phase that has a subsequent phase MUST have an end condition — without one, the phase runs indefinitely and subsequent phases never execute. For multi-actor scenarios, ensure EVERY phase with a successor has an appropriate end condition set via setEndCondition.
8. Validate and Report
validate(rrs);After validation passes, present a summary of what was created — never simulate unless explicitly asked.
Scenario Decomposition
MANDATORY: Before writing ANY code, complete Steps 0–1 below and present your plan to the user for confirmation. Do not skip this — scenarios built without pre-analysis frequently fail due to wrong timing, missed collisions, or impossible trigger conditions.
Step 0 — Clarify intent: If the user's prompt is ambiguous about ANY of the following, ASK before proceeding:
Number and types of actors
Desired outcome (collision, near-miss, safe completion?)
Speeds, distances, or timing not specified
Which actor is "ego" vs "target"
Scene to use (if not stated)
Step 1 — Physics-first design (REQUIRED for timed interactions): For collisions, near-misses, cut-ins, pedestrian crossings, and any scenario where actors must arrive at the same point at a specific time — you MUST derive kinematic parameters before writing code. Follow the full 5-step process in references/physics-first-design.md:
Parse intent into timeline
Measure spatial parameters (query HD Map)
Build parameter derivation table (show your math)
Verify conditions will trigger
Write code with derived values
Present your decomposition to the user — show actors, placement, speeds, trigger timing, and expected outcome. Get confirmation before executing code.
Actors — How many, what types (vehicle/pedestrian/object), what roles (ego, target, background, stationary)
Placement geometry — Determines which
helperSceneAwarenesstype to use:Same lane (
ScenarioType="following") — leader/follower, overtake start, emergency brakeAdjacent lanes (
ScenarioType="cut-in") — lane changes, merges, parallel drivingCrossing paths — pedestrian crossing, intersection conflicts
Interaction intent — Determines speed/separation/trigger defaults:
Conflict/near-miss: Close proximity, speed differential, tests reaction (small gap, distance triggers)
Cooperative: Safe completion expected, no collision (large gap ≥ 30m, time or duration triggers)
Independent: Actors don't interact directly (background traffic, stationary objects)
Maneuver sequence — Identify each distinct behavior change as a phase: drive → lane change → brake → resume. Each transition needs a trigger condition.
Trigger selection:
Distance-based (
LongitudinalDistanceToActorCondition) — requires speed differential between actorsTime-based (
DurationCondition) — works regardless of speeds, simplerSimulation time (
SimulationTimeCondition) — absolute time, good for choreographed sequences
Safety validation — Before executing, verify lane-change scenarios won't collide:
required_gap = lane_change_distance + closing_rate × (lane_change_distance / actor_speed) + 5m
Scenario Defaults (auto-created by RoadRunner)
When newScenario() is called, RoadRunner automatically creates:
A CollisionCondition as the root phase's fail condition (any actor-to-actor collision fails the scenario)
A SimulationTimeCondition (60s) as the root phase's end condition
Do NOT duplicate these. To modify the default collision condition:
rootPhase = phaseLogic.RootPhase;
% The fail condition already exists — access it directly
% To change the end time:
rootEndCond = setEndCondition(rootPhase, "SimulationTimeCondition");
rootEndCond.Time = 30; % Override default 60sHard Constraints
Always set
.Actoron ActorActionPhase and condition objects — never omitNever add duplicate action types to a single phase — modify
phase.Actions(1)insteadCharacter actors require routes — without them validation fails
autoAnchorrequires proximity — point must be within 5m of road surfaceDistance condition rules — ONLY
"le"and"ge"are valid (not"lt","gt")Speed condition rules — full set:
"eq","gt","lt","ge","le","ne"SerialPhase cannot nest in SerialPhase — use ParallelPhase as intermediate
Distance triggers need speed difference — if actors travel at same speed, gap never changes
Never simulate unless user explicitly asks — authoring and simulation are separate workflows. Never claim scenario outcomes (collision, near-miss) based on math alone — only simulation produces ground truth
Never assume — always clarify ambiguous prompts — if user doesn't specify speeds, distances, timing, actor count, desired outcome (collision vs near-miss vs safe), or scene, ASK before writing code. Present your scenario decomposition and physics analysis to the user for confirmation before executing. Guessing leads to scenarios that validate but produce wrong outcomes.
setEndConditionnotaddEndCondition— only one end condition per phaseChangeLaneActionrequires lane-following mode — do NOT add routes to actors that need lane changesDo NOT use
anchorToPoint+ForwardOffseton route points — causes validation failure; useautoAnchoronlyLaneOffsetcan land on junction connectors — for lane-change scenarios, place actors on verified parallel lanes using HD Map positions +autoAnchorinstead ofLaneOffsetNever guess lane change direction — use
LaneChangeReference="actor"+Direction="same-lane"when targeting another actor's lane, or compute direction via cross product (seereferences/scenario-templates.md)Parallel group end condition goes on
.ParentPhase, not on child phasesVerify WorldPosition after placement —
[0 0 0]means anchoring failed silentlyNo actor removal API —
removeActordoes not exist;WorldPositionis read-only after anchoring. Placement mistakes requirenewScenario()and rebuilding. Plan placement carefully before executing.autoAnchorunreliable at lane boundaries — Use fraction ≥ 0.2 and ≤ 0.8 when querying HD Map positions. At lane start/end points, multiple lanes converge andautoAnchormay snap to the wrong lane.Lane-change safety margin — For lane changes, ensure longitudinal separation exceeds:
lane_change_distance + (closing_rate × maneuver_time) + vehicle_length. A 20m lane change at 5 m/s closing rate needs ≥ 30m initial gap.Vehicle route segments default to freeform — after adding waypoints for vehicles, set
seg.Freeform = falseon each segment so the route follows road geometry. Do NOT disable freeform on pedestrian routes — pedestrians cross roads and need freeform paths.Path-following actors have no collision physics — vehicles in path-following mode (with routes) pass through each other. RoadRunner detects collision when bumpers reach ≤50mm proximity, then terminates the scenario. Design collision timing using bumper-to-bumper distance, not center-to-center.
Collision gap formula — use bumper gap, not center gap:
bumper_gap = center_distance - (vehicle1_length/2 + vehicle2_length/2). Timing:t_collision = bumper_gap / closing_rate. Account for ~1s acceleration ramp from rest (vehicles don't reach target speed instantly).Head-on collisions not supported —
LaneOffsetmust be positive; you cannot place two vehicles facing each other on the same road segment via the programmatic API. Use rear-end or crossing-path collision designs instead.Initial separation must exceed trigger distance — if actors start closer than the trigger condition's distance threshold, the condition fires immediately and phases advance before intended.
Never chain property assignment on
setEndCondition—setEndCondition(phase,"Type").Property = Nfails onActorActionPhase(works only onInitialPhase). Always store the result first:c = setEndCondition(phase, "Type"); c.Property = N;. This two-line pattern works on ALL phase types.One action per
ActorActionPhase— calladdActiononly once per phase. The auto-created initial phase (frominitialPhaseForActor) is the only exception. For concurrent actions, use aParallelPhasewith oneActorActionPhasechild per action.Never use world-space offsets for actor placement —
egoPos + [X, Y, 0]only works on world-axis-aligned roads. For pedestrians and crossing actors, always usehelperGetPositionFromHDMapto get position and heading, then compute the perpendicular crossing direction:hdg2D = hdg(1:2)/norm(hdg(1:2)); perpDir = [-hdg2D(2), hdg2D(1), 0];Junction scenes: do NOT route across intersection arms — on
FourWaySignal,FourWayStop, andT_Intersection, approach lanes do not connect to opposing exit lanes via the routing API. Use single-arm routes (approach → connected exit lane in the same corridor) or lane-following mode only. Cross-arm routes failvalidate.Do not over-filter lane length — for
helperSceneAwarenessthe parameter isMinLaneLength(default 30); forhelperSurveyLanes/helperGetPositionFromHDMapuseMinLength. On intersection scenes use 30 since approach lanes are short. Compute needed length:max_speed × scenario_duration + 20m. Using 80+ on small scenes leaves fewer lanes than expected.
Common Wrong Property Names
| Wrong (hallucinated) | Correct |
|---|---|
RelativeLaneOffset | NumLanesOffset |
SimulationTime | SimulationTimeCondition |
addEndCondition | setEndCondition |
listAssets / getAssets | getAsset(proj, path, type) |
getActors / listActors | rrs.Actors |
actor.Speed | initPhase.Actions(1).Speed |
route.addWaypoint | addPoint(route, position) |
removeActor / deleteActor | Does not exist — use newScenario() |
actor.WorldPosition = ... | Read-only — reposition via newScenario() + re-place |
cond.Comparison | Does not exist on SimulationTimeCondition (just set .Time) |
rrApp.ProjectFolder | Not a public property — use status(rrApp) |
DistanceType = "gap" | Use "space" (spatial) or "time" (time-gap) |
ConstraintType = "acceleration" | Use "asset", "custom", or "none" |
PhaseState = "completed" | Use "end" (valid: "idle", "start", "run", "end") |
MeasureDistance = "euclidean" | Use "lane" or "actor" |
"MovableObjects/..." | Use "Props/TrafficControl/..." with "MovableObjectAsset" type |
syncAction.TargetPoint = actor.InitialPoint | Shared reference trap — use route waypoints as independent Points |
LateralOffsetAction | ChangeLateralOffsetAction (full name required) |
.Offset (on ChangeLateralOffsetAction) | .LateralOffset |
laneTable.LaneIndex | laneTable.Index (column name from helperSurveyLanes) |
action.Rate | Use .DynamicsDimension = "rate"; .DynamicsValue = N; |
setEndCondition(...).Property = N | c = setEndCondition(...); c.Property = N; (two-line pattern) |
MinLength (on helperSceneAwareness) | MinLaneLength — only lower-level helpers use MinLength |
"text" + status(rrApp) | status(rrApp) returns a struct — use s = status(rrApp); s.Scene.Filename |
actors(i) from helperPlaceActors | actors{i} — returns a cell array, use curly-brace indexing |
Key Functions
| Function | Purpose | Since |
|---|---|---|
roadrunnerAPI(rrApp) | Create authoring API handle | R2025a |
addActor(rrs, asset, pos) | Add actor to scenario | R2025a |
getAsset(proj, path, type) | Load asset by path | R2025a |
autoAnchor(point) | Snap point to nearest road | R2025a |
anchorToPoint(pt, anchor) | Anchor point to reference | R2025a |
addPoint(route, pos) | Add waypoint to route | R2025a |
initialPhaseForActor(logic, actor) | Get actor's init phase | R2025a |
addPhaseInSerial(logic, phase, type) | Add sequential phase | R2025a |
addPhaseInParallel(logic, phase, type) | Add concurrent phase | R2025a |
setEndCondition(phase, type) | Set phase trigger | R2025a |
addAction(phase, type) | Add behavior action | R2025a |
validate(rrs) | Check scenario validity | R2025a |
validate(rrs, ObjectRoot=obj) | Validate specific object (phase, actor, route) | R2025a |
findActions(phase, type) | Find actions of a type in a phase | R2025a |
createAsset(proj, path, type) | Create new asset (vehicle, character, behavior) | R2025a |
getAnchors(rrApp) | List scene anchors | R2024a |
getAsset with "CharacterAsset" | Load pedestrian asset | R2025a |
Actor Movement Modes
| Mode | Trigger | Behavior | Compatible Actions |
|---|---|---|---|
| Lane-following | No route waypoints | Follows anchored lane at set speed | ChangeLaneAction, ChangeLateralOffsetAction |
| Path-following | Has route waypoints | Follows waypoint path | ChangeSpeedAction, ChangeLongitudinalDistanceAction |
Vehicles with speed set but NO routes drive along their lane automatically
ChangeLaneActionONLY works in lane-following mode (no routes)Character/pedestrian actors ALWAYS need routes (validation fails without them)
Conventions
Coordinate system: Z-up, world-space positions as
[X Y Z]Asset paths: Use exact relative path from project Assets folder (e.g.,
"Vehicles/Sedan.fbx")File suffixes: Disk shows
.fbx_rrx, API uses.fbx; disk shows.rrchar_rrx, API uses.rrcharSpeed units: Always meters per second (m/s)
Actor naming: Use descriptive names (
"Ego","TargetVehicle","Pedestrian1")Default scene:
ScenarioBasic.rrscene(310 forward lanes, ScenarioStart anchor)Intersection scenes:
FourWaySignal,FourWayStop,T_IntersectionUser clarification (MANDATORY): Before writing code, present your scenario plan (actors, placement, speeds, timing, expected outcome) and get user confirmation. If ANY parameter is ambiguous, ask — never guess. This is the single most important convention for avoiding rework.
Scripts
Deploy these helpers into the user's MATLAB path for automated scene analysis and batch placement. They depend on each other: helperSceneAwareness calls helperSurveyLanes and helperGetPositionFromHDMap.
| Script | Purpose | When to use |
|---|---|---|
scripts/helperSceneAwareness.m | Export HD Map, survey lanes, classify road features, recommend placements | First step before placing actors — provides positions, lane context, and feature classification |
scripts/helperSurveyLanes.m | Filter/sort lanes by type, direction, length | When you need lane geometry data without full scene analysis |
scripts/helperGetPositionFromHDMap.m | Arc-length interpolation along lanes, nearest-point queries | When you need precise positions at specific fractions along a lane |
scripts/helperPlaceActors.m | Batch place, anchor, set speed, verify multiple actors | When placing 2+ actors — automates the manual add/anchor/verify loop |
scripts/helperClassifyRoadFeatures.m | Elevation-based road feature classification (bridge, ground, ramp) | Used internally by helperSceneAwareness; call directly for custom feature queries |
Usage pattern:
% 1. Run scene awareness to get lane data and recommended positions
sceneInfo = helperSceneAwareness(rrApp, NumActors=2, ScenarioType="cut-in");
% 2. Define actor specs (do NOT pre-initialize with actorSpecs=[])
actorSpecs(1) = struct(Name="Ego", AssetPath="Vehicles/Sedan.fbx", ...
AssetType="VehicleAsset", LaneIndex=1, Fraction=0.1, Speed=15);
actorSpecs(2) = struct(Name="Target", AssetPath="Vehicles/Sedan.fbx", ...
AssetType="VehicleAsset", LaneIndex=2, Fraction=0.2, Speed=20);
% 3. Batch place and verify (actors is a cell array)
[actors, report] = helperPlaceActors(rrs, rrprj, phaseLogic, sceneInfo.HDMap, actorSpecs);
assert(report.AllValid, "Placement failed: " + report.Summary);
ego = actors{1}; target = actors{2}; % curly braces requiredScene Feature Awareness
For scenes with multiple elevation levels (bridges, overpasses, underpasses), use the RoadFeature parameter to target a specific road structure:
% Place actors specifically on the bridge deck
sceneInfo = helperSceneAwareness(rrApp, NumActors=2, ScenarioType="following", ...
RoadFeature="bridge");
% LaneIndex=1 now refers to the longest BRIDGE lane, not the longest overall lane
actorSpecs(1) = struct(Name="Leader", AssetPath="Vehicles/Sedan.fbx", ...
AssetType="VehicleAsset", LaneIndex=1, Fraction=0.3, Speed=15, ...
FilterLaneIDs=sceneInfo.FilteredLaneIDs);Available feature labels: "bridge", "ground", "ramp", "overpass"
When to use RoadFeature:
User mentions "on the bridge", "under the bridge", "on the ramp", "on the overpass"
Scene has multiple elevation levels (Bridge, highway interchanges)
Default placement picks the wrong road level (actor at unexpected elevation)
When RoadFeature="" (default): The summary shows all detected features so you can ask the user which one they mean:
Road features detected:
bridge: 10 lanes, total 700m, connected path 192m, elevation 7.9-8.6m
ground: 3 lanes, total 490m, connected path 165m, elevation 2.4-2.8mOutput fields for feature-aware placement:
sceneInfo.RoadFeatures— full feature classification structsceneInfo.FilteredLaneIDs— lane IDs matching the requested feature (pass toactorSpecs.FilterLaneIDs)sceneInfo.LaneNetwork— full lane connectivity fromhelperAnalyzeHDMapLanes
Built-in RoadRunner Helpers
RoadRunner ships example helpers at <RoadRunner-Install>/Tools/MATLAB/api/scenario/common (e.g., helperAddCutInAndSlowCar, helperAddPedestrian). These use anchorToPoint + LaneOffset for actor placement.
When built-in helpers are suitable: Simple scenes with clearly separated lanes (straight roads, known layouts).
When to prefer this skill's approach instead: Scenes with junctions, complex layouts, or unknown geometry — LaneOffset can land actors on junction connectors that don't support lane changes. HD Map positions + autoAnchor works reliably on any scene.
Key pattern from built-in helpers: ChangeLaneAction supports LaneChangeReference="actor" with Direction="same-lane" — the actor moves to the reference actor's lane automatically, eliminating cross-product direction computation. See references/scenario-templates.md Cut-In section for usage.
References
references/actions-and-conditions.md— Complete catalog of all action types, condition types, their properties and valid values. Consult when building phase logic.references/routes-and-points.md— Full Point properties, RouteSegment configuration, time-based trajectories, and route read-only properties. Consult for precise positioning, trajectory timing, or curve configuration.references/collision-utility.md— CollisionUtility configuration for collision analysis between specific actor pairs. Consult when setting up collision monitoring.references/asset-catalog.md— Available vehicle and character asset paths with naming conventions. Consult when adding actors.references/scene-awareness.md— HD Map export pattern for querying lane geometry when helperSceneAwareness is unavailable. Consult when placing actors on unknown scenes.references/lane-connectivity.md— Lane network topology, road feature classification, and connected path queries. Consult when placing actors on specific road features (bridge, ramp) or when you need lane connectivity information.references/scenario-templates.md— Tested code patterns for cut-in, pedestrian crossing, lead-follow, emergency brake. Consult when building specific scenario types.references/physics-first-design.md— Pre-authoring kinematic analysis: derive speeds, gaps, and timing before writing code. Consult for collision/near-miss/timed-interaction scenarios.
Copyright 2026 The MathWorks, Inc.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md26.3 KiB
- manifest.yaml540 B
- references/actions-and-conditions.md11.6 KiB
- references/asset-catalog.md4.2 KiB
- references/collision-utility.md3.2 KiB
- references/lane-connectivity.md4.5 KiB
- references/physics-first-design.md6.2 KiB
- references/routes-and-points.md6.7 KiB
- references/scenario-templates.md16.4 KiB
- references/scene-awareness.md5.0 KiB
- scripts/helperAnalyzeHDMapLanes.m7.5 KiB
- scripts/helperClassifyRoadFeatures.m15.3 KiB
- scripts/helperGetPositionFromHDMap.m6.5 KiB
- scripts/helperPlaceActors.m5.8 KiB
- scripts/helperSceneAwareness.m11.3 KiB
- scripts/helperSurveyLanes.m3.0 KiB
references/scenario-templates.md
Scenario Templates
Tested, validated patterns for common scenario types. All actors using ChangeLaneAction or ChangeLateralOffsetAction must be in lane-following mode (no routes).
Cut-In
Target cuts into ego's lane from an adjacent lane. Both actors in lane-following mode.
Key setup:
Place actors on verified parallel lanes using HD Map positions +
autoAnchorDo NOT use
LaneOffset— it may place actors on junction connectors that don't support lane changesEgo faster than target so gap closes (triggers distance condition)
Neither actor has routes (lane-following mode required for
ChangeLaneAction)helperSceneAwarenesswithScenarioType="cut-in"automatically finds adjacent parallel lanes
Verifying parallel lanes manually (when not using the helper):
% Check two lanes are suitable for cut-in: aligned headings + close proximity
[posI, hdgI] = helperGetPositionFromHDMap(rrHDMap, laneI, 0.5, "TravelDirection", "Forward", "MinLength", 50);
[posJ, hdgJ] = helperGetPositionFromHDMap(rrHDMap, laneJ, 0.5, "TravelDirection", "Forward", "MinLength", 50);
alignment = abs(dot(hdgI(1:2)/norm(hdgI(1:2)), hdgJ(1:2)/norm(hdgJ(1:2))));
lateralDist = norm(posI(1:2) - posJ(1:2));
assert(alignment > 0.95, "Lanes are not parallel (alignment=%.3f)", alignment);
assert(lateralDist < 10, "Lanes are too far apart (%.1fm)", lateralDist);% Step 1: Find two parallel lanes using helperSurveyLanes
sceneInfo = helperSceneAwareness(rrApp, NumActors=2, ScenarioType="cut-in");
% Step 2: Get positions on adjacent lanes (use lanes with similar headings)
[egoPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, egoLaneIdx, 0.1, ...
"TravelDirection", "Forward", "MinLength", 50);
[targetPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, targetLaneIdx, 0.3, ...
"TravelDirection", "Forward", "MinLength", 50);
% Step 3: Place actors — no routes (lane-following mode)
ego = addActor(rrs, vehicleAsset, egoPos);
ego.Name = "Ego";
autoAnchor(ego.InitialPoint);
target = addActor(rrs, vehicleAsset, targetPos);
target.Name = "TargetVehicle";
autoAnchor(target.InitialPoint);
% Step 4: Speeds — ego faster so it catches up
egoInit = initialPhaseForActor(phaseLogic, ego);
egoInit.Actions(1).Speed = 20;
targetInit = initialPhaseForActor(phaseLogic, target);
targetInit.Actions(1).Speed = 15;
% Step 5: Cut-in phase logic
cutPhase = addPhaseInSerial(phaseLogic, targetInit, "ActorActionPhase");
cutPhase.Actor = target;
cond = setEndCondition(targetInit, "LongitudinalDistanceToActorCondition");
cond.Actor = target;
cond.ReferenceActor = ego;
cond.Distance = 8;
cond.Rule = "le";
% Direction depends on lane layout: if target is at higher Y, use "right"
laneChange = addAction(cutPhase, "ChangeLaneAction");
laneChange.Direction = "right"; % Verify: target must move toward ego's lane
laneChange.DynamicsDimension = "distance";
laneChange.DynamicsValue = 20;How to determine lane change direction: "Left"/"right" is relative to the actor's travel direction, not absolute coordinates. Two approaches:
Option A — "same-lane" reference (preferred, simplest):
% Target automatically moves to the reference actor's lane — no direction computation needed
laneChange = addAction(cutPhase, "ChangeLaneAction");
laneChange.LaneChangeReference = "actor";
laneChange.Direction = "same-lane";
laneChange.ReferenceActor = ego;
laneChange.DynamicsDimension = "distance";
laneChange.DynamicsValue = 20;Option B — Explicit direction via cross product (use when targeting a lane that has no actor):
% Vector from target to ego (desired movement direction)
targetToEgo = ego.InitialPoint.WorldPosition(1:2) - target.InitialPoint.WorldPosition(1:2);
targetToEgo = targetToEgo / norm(targetToEgo);
% Target's travel heading
[~, targetHdg] = helperGetPositionFromHDMap(rrHDMap, targetLaneIdx, fraction, ...
"TravelDirection", "Forward", "MinLength", 50);
hdg2D = targetHdg(1:2) / norm(targetHdg(1:2));
% Cross product Z-component: positive = ego is to the LEFT
crossZ = hdg2D(1) * targetToEgo(2) - hdg2D(2) * targetToEgo(1);
if crossZ > 0
direction = "left";
else
direction = "right";
endNever guess direction from coordinates alone — road orientation varies between scenes.
Lane Change (Non-Conflict)
Actor changes into another actor's lane with safe separation — no collision expected. Use this pattern when the intent is a safe merge, overtake entry, or any lane change that should complete without collision.
When to use this instead of Cut-In:
User describes a "merge", "lane change", "move over" (not "cut in front of")
No collision or near-miss is expected
Speeds are matched or actor is already ahead/behind
Key setup differences from Cut-In:
Larger initial separation (≥ 30m)
Matched or similar speeds (no aggressive closing)
DurationConditiontrigger (simpler, no speed differential needed)Actor is already ahead of or behind the other vehicle
% Both actors on adjacent parallel lanes, actor ahead of reference
sceneInfo = helperSceneAwareness(rrApp, NumActors=2, ScenarioType="cut-in");
% Place merger AHEAD on adjacent lane (fraction 0.4 vs ego at 0.2 = ~30m gap)
[mergerPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, ...
sceneInfo.Recommendations(2).LaneIndex, 0.4, ...
"TravelDirection", "Forward", "MinLength", 50);
[egoPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, ...
sceneInfo.Recommendations(1).LaneIndex, 0.2, ...
"TravelDirection", "Forward", "MinLength", 50);
ego = addActor(rrs, vehicleAsset, egoPos);
ego.Name = "Ego";
autoAnchor(ego.InitialPoint);
merger = addActor(rrs, vehicleAsset, mergerPos);
merger.Name = "MergingVehicle";
autoAnchor(merger.InitialPoint);
% Matched speeds — no closing
egoInit = initialPhaseForActor(phaseLogic, ego);
egoInit.Actions(1).Speed = 15;
mergerInit = initialPhaseForActor(phaseLogic, merger);
mergerInit.Actions(1).Speed = 15;
% Time-based trigger — merge after 2 seconds (no speed differential needed)
cond = setEndCondition(mergerInit, "DurationCondition");
cond.Duration = 2;
% Lane change using "same-lane" (simplest, no direction computation)
mergePhase = addPhaseInSerial(phaseLogic, mergerInit, "ActorActionPhase");
mergePhase.Actor = merger;
laneChange = addAction(mergePhase, "ChangeLaneAction");
laneChange.LaneChangeReference = "actor";
laneChange.Direction = "same-lane";
laneChange.ReferenceActor = ego;
laneChange.DynamicsDimension = "distance";
laneChange.DynamicsValue = 20;Safety margin formula: For cooperative lane changes, ensure:
initial_gap ≥ lane_change_distance + (closing_rate × maneuver_time) + 5m
Example: 20m lane change at 15 m/s with 0 m/s closing rate → need ≥ 25m gap. With 5 m/s closing: maneuver_time ≈ 20/15 ≈ 1.3s, so need ≥ 20 + 5×1.3 + 5 = 31.5m.
Same-Lane Placement (Leader/Follower)
Place multiple actors on the same lane at different positions. Used for overtake, follow, and emergency brake scenarios.
% Query single lane at two different fractions
laneIdx = sceneInfo.Recommendations(1).LaneIndex;
% Leader further along the lane (higher fraction)
[leaderPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, laneIdx, 0.4, ...
"TravelDirection", "Forward", "MinLength", 50);
% Follower behind (lower fraction)
[followerPos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, laneIdx, 0.2, ...
"TravelDirection", "Forward", "MinLength", 50);
leader = addActor(rrs, vehicleAsset, leaderPos);
leader.Name = "LeadVehicle";
autoAnchor(leader.InitialPoint);
follower = addActor(rrs, vehicleAsset, followerPos);
follower.Name = "FollowerVehicle";
autoAnchor(follower.InitialPoint);
% Verify separation
sep = norm(leader.InitialPoint.WorldPosition(1:2) - follower.InitialPoint.WorldPosition(1:2));
assert(sep > 10, "Actors too close (%.1fm) — increase fraction difference", sep);Fraction guidelines for same-lane placement:
Use fractions ≥ 0.2 and ≤ 0.8 (avoid lane boundaries where
autoAnchoris unreliable)Minimum fraction difference of 0.1 for ~10-20m separation on typical lanes
Always verify actual separation after placement (
normof positions)
Pedestrian Crossing
Pedestrian crosses the road in front of ego. Pedestrian MUST have a route (required for character actors).
CRITICAL: Never use world-space offsets like pos + [0 -15 0] for pedestrian crossing routes — this only works on world-axis-aligned roads. Always compute the crossing direction from the road heading at the pedestrian's position.
% Load character asset (verify path exists — see asset-catalog.md for project-specific paths)
charAsset = getAsset(rrprj, "Characters/Citizen_Male.rrchar", "CharacterAsset");
% Place pedestrian using HD Map position (not world-space guesses)
[pedPos, pedHdg] = helperGetPositionFromHDMap(sceneInfo.HDMap, pedLaneIdx, pedFraction, ...
"TravelDirection", "Forward", "MinLength", 20);
ped = addActor(rrs, charAsset, pedPos);
ped.Name = "Pedestrian";
autoAnchor(ped.InitialPoint);
% REQUIRED: pedestrian route defines crossing path using road-relative direction
pedRoute = ped.InitialPoint.Route;
hdg2D = pedHdg(1:2) / norm(pedHdg(1:2));
perpDir = [-hdg2D(2), hdg2D(1), 0]; % perpendicular to road heading
crossingDistance = 15; % meters — adjust based on road width
crossTarget = pedPos + perpDir * crossingDistance;
crossPt = addPoint(pedRoute, crossTarget);
autoAnchor(crossPt);
% Note: pedestrian routes remain freeform (default) — they cross roads, not follow them
pedInit = initialPhaseForActor(phaseLogic, ped);
pedInit.Actions(1).Speed = 1.4; % Walking speed ~5 km/hPerpendicular direction selection: The sign of perpDir determines which side the pedestrian crosses toward. Always verify by comparing the landing position against the target lane:
% Check which perpendicular direction points toward egoLane
[ePos, ~] = helperGetPositionFromHDMap(sceneInfo.HDMap, egoLaneIdx, pedFraction, ...
"TravelDirection", "Forward", "MinLength", 20);
perpL = [-hdg2D(2), hdg2D(1), 0];
perpR = [ hdg2D(2),-hdg2D(1), 0];
% Pick the direction that lands closer to egoLane
if norm(pedPos + perpL*5 - ePos) < norm(pedPos + perpR*5 - ePos)
perpDir = perpL;
else
perpDir = perpR;
endLead-Follow (Gap-Keeping)
Follower maintains a fixed gap behind a leader using ChangeLongitudinalDistanceAction. Both in lane-following mode (no routes).
Two gap-keeping modes:
DistanceType="space"— maintain spatial gap in meters (simpler, default)DistanceType="time"— maintain time-gap in seconds (adaptive to speed changes)
% Both actors on same lane, leader ahead
leaderInit = initialPhaseForActor(phaseLogic, leader);
leaderInit.Actions(1).Speed = 15;
followerInit = initialPhaseForActor(phaseLogic, follower);
followerInit.Actions(1).Speed = 20; % Start faster to close initial gap
% Gap-keeping phase
followPhase = addPhaseInSerial(phaseLogic, followerInit, "ActorActionPhase");
followPhase.Actor = follower;
gapAction = addAction(followPhase, "ChangeLongitudinalDistanceAction");
gapAction.ReferenceActor = leader;
gapAction.RelativePosition = "behind";
gapAction.DistanceType = "space"; % "space" for meters, "time" for seconds
gapAction.DistanceOffset = 15; % 15 meters (or 2.0 seconds if "time")
gapAction.SamplingMode = "continuous";
gapAction.ConstraintType = "asset"; % Use actor's kinematic limits
% Trigger: start gap-keeping immediately after a short duration
cond = setEndCondition(followerInit, "DurationCondition");
cond.Duration = 1;With custom kinematic constraints (for tighter control over acceleration):
gapAction.ConstraintType = "custom";
gapAction.MaxSpeed = 25; % m/s
gapAction.MaxAcceleration = 3.0; % m/s^2
gapAction.MaxDeceleration = 5.0; % m/s^2Emergency Brake
Lead vehicle brakes suddenly; test ego's response.
% Leader ahead in same lane, both lane-following
leaderInit = initialPhaseForActor(phaseLogic, leader);
leaderInit.Actions(1).Speed = 20;
% Brake phase: leader decelerates to 0
brakePhase = addPhaseInSerial(phaseLogic, leaderInit, "ActorActionPhase");
brakePhase.Actor = leader;
brakeAction = addAction(brakePhase, "ChangeSpeedAction");
brakeAction.Speed = 0;
brakeAction.DynamicsDimension = "rate";
brakeAction.DynamicsValue = 8; % 8 m/s^2 deceleration
% Trigger: after 3 seconds
cond = setEndCondition(leaderInit, "DurationCondition");
cond.Duration = 3;Junction Turn (Right/Left Turn + Stop)
Vehicle approaches junction, turns, and stops on the exit road. Uses path-following mode (routes with multiple waypoints for smooth turn).
CRITICAL LIMITATION: On intersection scenes, routes can ONLY follow connected lane segments. Do NOT route from one approach arm to a non-adjacent exit arm (e.g., North approach → South approach). These are separate unconnected arms. Valid routing: approach lane → connected exit lane in the same corridor (e.g., North approach → East exit for a right turn). Cross-arm routes fail validate.
Key setup:
Use an intersection scene (
FourWaySignal,FourWayStop,T_Intersection)Identify approach lane and exit lane from HD Map survey — lanes must be connected
Add multiple route waypoints: pre-junction → post-junction → exit point
More waypoints = smoother turn path
For vehicles that just need to approach and stop at the junction, use lane-following mode (no routes) instead
% Survey lanes to identify approach and exit lanes
laneTable = helperSurveyLanes(sceneInfo.HDMap, TravelDirection="Forward", MinLength=30);
% Place vehicle on approach lane
[startPos, ~] = helperGetPositionFromHDMap(rrHDMap, approachLaneIdx, 0.2, ...
"TravelDirection", "Forward", "MinLength", 30);
vehicle = addActor(rrs, vehicleAsset, startPos);
vehicle.Name = "EgoVehicle";
autoAnchor(vehicle.InitialPoint);
% Multi-waypoint route for smooth turn
route = vehicle.InitialPoint.Route;
% WP1: End of approach lane (pre-junction)
[preJunctionPos, ~] = helperGetPositionFromHDMap(rrHDMap, approachLaneIdx, 0.9, ...
"TravelDirection", "Forward", "MinLength", 30);
wp1 = addPoint(route, preJunctionPos);
autoAnchor(wp1);
% WP2: Start of exit lane (post-junction — defines the turn)
[postJunctionPos, ~] = helperGetPositionFromHDMap(rrHDMap, exitLaneIdx, 0.1, ...
"TravelDirection", "Forward", "MinLength", 30);
wp2 = addPoint(route, postJunctionPos);
autoAnchor(wp2);
% WP3: Further along exit lane (stop area)
[exitPos, ~] = helperGetPositionFromHDMap(rrHDMap, exitLaneIdx, 0.6, ...
"TravelDirection", "Forward", "MinLength", 30);
wp3 = addPoint(route, exitPos);
autoAnchor(wp3);
% Phase logic: drive then stop
initPhase = initialPhaseForActor(phaseLogic, vehicle);
initPhase.Actions(1).Speed = 10;
stopPhase = addPhaseInSerial(phaseLogic, initPhase, "ActorActionPhase");
stopPhase.Actor = vehicle;
stopAction = addAction(stopPhase, "ChangeSpeedAction");
stopAction.Speed = 0;
stopAction.DynamicsDimension = "rate";
stopAction.DynamicsValue = 3;
% Trigger: time-based (estimate from distance/speed)
cond = setEndCondition(initPhase, "SimulationTimeCondition");
cond.Time = 9; % Adjust based on approach distance and speedHow to identify approach/exit lanes: Survey all forward lanes. Lanes ending near the intersection center are approach lanes. Lanes starting near the center are exit lanes. Compare start/end positions to determine direction (N/S/E/W).
How to Use These Templates
These templates are building blocks, not recipes. Most real scenarios combine elements from multiple templates. The agent should:
Classify the user's intent into placement geometry (same-lane or adjacent-lane) and interaction type (conflict or non-conflict)
Pick the relevant placement pattern (Same-Lane or Cut-In/Lane Change)
Compose phase logic by chaining actions from any template that fits the maneuver
Validate safety margins before executing
Key Rules for All Templates
Lane-following vs path-following: Actors needing
ChangeLaneActionmust NOT have routesSpeed differential for triggers:
LongitudinalDistanceToActorConditiononly fires if actors have different speedsCharacter actors always need routes: Validation fails without them
Always set
.Actor: On everyActorActionPhaseand condition objectFraction boundaries: Use fractions ≥ 0.2 and ≤ 0.8 for
helperGetPositionFromHDMap— lane start/end points cause unreliableautoAnchorsnappingNo undo:
removeActordoes not exist. Plan placement before executing. Mistakes requirenewScenario()and rebuilding.
Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/automotive/roadrunner-scenario-authoring · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks or MathWorks.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download roadrunner-scenario-authoring.zip