RoadRunner Scenario Simulation

SkillMonitoring & ops

Expert guidance for simulating RoadRunner scenarios via the MATLAB programmatic API and Simulink co-simulation. Use when the user wants to run a simulation, step through a simulation, control actors during co-simulation, add observers, attach sensors, retrieve simulation logs, or read/write scenario variables. Covers simulateScenario, createSimulation, ScenarioSimulation set/get, ActorSimulation getAttribute/setAttribute, addObserver, SensorSimulation, Simulink co-sim blocks, and publishActorBehavior. NOT for project setup, scene building, scenario authoring, or trajectory export.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the RoadRunner Scenario Simulation skill

What this skill tells your AI

The instructions your AI receives, as published by matlab/matlab-agentic-toolkit in skills-catalog/automotive/roadrunner-scenario-simulating/SKILL.md and read by ahel’s review.

Simulate RoadRunner scenarios, step through simulations, control actors in co-simulation, add observers, attach sensors, and retrieve results — from MATLAB and Simulink.

When to Use

  • User wants to run a RoadRunner scenario simulation
  • User wants to step through a simulation frame-by-frame
  • User wants to control an actor externally (co-simulation)
  • User wants to observe simulation state (read-only monitoring)
  • User wants to add sensors and read target poses or lane boundaries
  • User wants to retrieve simulation logs programmatically
  • User wants to read or write scenario variables
  • User asks about co-simulation with Simulink
  • User asks about publishing actor behaviors

When NOT to Use

  • Launching or connecting to RoadRunner
  • Authoring scenarios (adding actors, paths, behaviors in the editor)
  • Building or editing scenes (roads, terrain, assets)
  • Exporting actor trajectories to CSV — use exportActorTrajectoryToCSV directly
  • Working with drivingScenario (that is a DIFFERENT toolbox — Automated Driving Toolbox)

Critical: Do NOT Confuse With drivingScenario

drivingScenario (from Automated Driving Toolbox) is a MATLAB-native scenario tool. RoadRunner Scenario simulation uses completely different APIs on the roadrunner object. Never mix these two — they are unrelated.


Decision Tree

User wants to simulate →
  ├── Just run to completion? → Workflow A (simulateScenario)
  ├── Need step control OR programmatic log? → Workflow B (createSimulation)
  ├── Need to read actor state during sim? → Workflow C (getAttribute)
  ├── Need external actor control? →
  │     ├── From MATLAB? → Workflow D (System object co-sim)
  │     └── From Simulink? → Workflow E (Simulink blocks)
  ├── Need read-only monitoring? → Workflow F (Observers)
  └── Need sensor data during sim? → Workflow G (SensorSimulation)

Workflow A: Simple Simulation (run to completion)

Use when the user just wants to simulate an already-open scenario:

openScenario(rrApp, "MyScenario");
simulateScenario(rrApp, EnableLogging=true);

Options: Pacing, IsBlocking, IsSteppingStart, EnableLogging.

Important: simulateScenario does NOT return a log object. Use it when you only need to run to completion. If you need programmatic access to the simulation log in MATLAB, use createSimulation (Workflow B) instead.


Workflow B: Step-by-Step Simulation

Use when the user needs frame-by-frame control or programmatic log access.

CRITICAL call order: createSimulation must be called BEFORE simulateScenario. The Scenario Server rejects new connections while a simulation is running or paused. Also, do NOT use set(rrSim, SimulationCommand="Start") then "Step""Start" runs the sim freely to completion.

% 1. Get the simulation handle FIRST (before anything is running)
rrSim = createSimulation(rrApp);
stepSize = 0.01;
set(rrSim, StepSize=stepSize);
set(rrSim, MaxSimulationTime=30);

% 2. THEN start simulation in stepping mode
simulateScenario(rrApp, IsSteppingStart=true, IsBlocking=false, EnableLogging=true);
pause(0.5);  % Allow sim to initialize before stepping

% 3. Step through the simulation
for i = 1:numSteps
    set(rrSim, SimulationCommand="Step");
    pause(stepSize);  % REQUIRED — Step is async, must wait for frame to complete
end
set(rrSim, SimulationCommand="Stop");

simLog = get(rrSim, "SimulationLog");

CRITICAL: "Step" is asynchronous — you MUST add pause(stepSize) after each Step. Without it, commands pile up and are silently dropped. Do NOT use dot-method syntax (rrSim.step()) — always use set(rrSim, SimulationCommand=...). Do NOT set Logging="On" during stepping — use EnableLogging=true in the simulateScenario call.

SimulationCommand values

"Start", "Step", "Pause", "Continue", "Stop", "Replay"

Replay uses positional syntax: set(rrSim, "SimulationCommand", "Replay", fileName)

Polling SimulationStatus

get(rrSim, "SimulationStatus") returns: "Inactive", "Running", "Paused", "Done"

Use in wait loops when running non-blocking simulations. Check BOTH "Done" and "Inactive" — short scenarios may transition past "Done" before the poll catches it:

status = get(rrSim, "SimulationStatus");
while ~ismember(status, ["Done", "Inactive"])
    pause(0.1);
    status = get(rrSim, "SimulationStatus");
end

Workflow C: Reading Actor State

To get actor information during a step-by-step simulation:

Note: get(rrSim, "ActorSimulation") always includes the world actor (ID 0) at index 1. This is a non-movable root actor, not a vehicle. Skip it or filter by ID when iterating.

Note: Actors are only queryable while the simulation is active (Running or Paused). After "Stop", get(rrSim, "ActorSimulation") returns empty.

% Get all actors — returns a CELL ARRAY, use {idx} not (idx)
actors = get(rrSim, "ActorSimulation");
actorSim = actors{2};  % cell indexing required; index 1 is world actor (ID 0)

% Or find a specific actor by ID (returns a single object)
% NOTE: ActorID must be uint64 — double will fail silently or error
actorSim = Simulink.ScenarioSimulation.find("ActorSimulation", ActorID=uint64(1));

% Read runtime attributes — use getAttribute, NOT property access
pose = getAttribute(actorSim, "Pose");              % 4x4 matrix
velocity = getAttribute(actorSim, "Velocity");      % 1x3 vector
angVel = getAttribute(actorSim, "AngularVelocity"); % 1x3 vector

CRITICAL: Do NOT use actorSim.Pose or actorSim.Velocity — these are NOT public properties. Always use getAttribute(actorSim, "AttrName").

Runtime attributes: "ID", "Pose", "Velocity", "AngularVelocity", "WheelPoses", "LaneLocation", "Children", "Parent", "PhaseStatus", "ActorType", "TrafficSignalRuntime", "TrafficSignalControllerRuntime"

Static Attributes (Name, BoundingBox, etc.)

To get an actor's name or other static properties, use ActorModel — NOT getAttribute(actorSim, "Name") (which does not exist):

actorModel = get(actorSim, "ActorModel");
actorName = getAttribute(actorModel, "Name");
boundingBox = getAttribute(actorModel, "BoundingBox");

Static attributes on ActorModel: "ID", "Name", "PaintColor", "BoundingBox", "WheelSpec", "TrafficSignalSpec", "TrafficSignalControllerSpec"


Workflow D: Co-Simulation with MATLAB System Object

Architecture

  1. Assign a behavior to the actor (see below)
  2. In MATLAB: Write a System object that controls the actor at runtime
  3. The System object finds itself using Simulink.ScenarioSimulation.find

Assigning Behaviors

R2024a: Assign behaviors in the RoadRunner Scenario Editor UI only (no MATLAB API).

R2025a+: Assign behaviors programmatically via the roadrunnerAPI authoring interface:

rrApi = roadrunnerAPI(rrApp);
prj = rrApi.Project;
scnro = rrApi.Scenario;

% Get or create a behavior asset
behaviorAsset = getAsset(prj, "Behaviors/MyBehavior.rrbehavior", "BehaviorAsset");

% Assign to an existing actor
car.BehaviorAsset = behaviorAsset;

Key points:

  • roadrunnerAPI(rrApp) provides access to Project and Scenario objects
  • Use getAsset(prj, path, "BehaviorAsset") to retrieve existing .rrbehavior assets
  • Use createAsset(prj, path, "BehaviorAsset") to create new behavior assets
  • After creating a behavior asset, set its platform: setPlatform(behaviorAsset, "SimulinkPlatform") — this is correct for BOTH MATLAB System objects and Simulink models. Do NOT use "MATLAB", "External", or "MATLABSystem" (they are not registered)
  • Assign via the BehaviorAsset property on Vehicle/Character/MovableObject
  • There is NO setBehavior() function — use property assignment instead

System Object Pattern

classdef MyActorController < matlab.System
    properties (Access = private)
        ActorSim  % ActorSimulation handle
    end

    methods (Access = protected)
        function setupImpl(obj)
            obj.ActorSim = Simulink.ScenarioSimulation.find( ...
                "ActorSimulation", SystemObject=obj);
        end

        function stepImpl(obj)
            action = getAction(obj.ActorSim, "PathAction");
            currentPose = getAttribute(obj.ActorSim, "Pose");

            % Modify pose
            currentPose(1,4) = currentPose(1,4) + 0.5;
            setAttribute(obj.ActorSim, Pose=currentPose);

            % Signal action completion (guard: ActionID may not exist)
            if ~isempty(action) && isfield(action, "ActionID")
                sendEvent(obj.ActorSim, "ActionComplete", action.ActionID);
            end
        end
    end
end

Actor Control Methods

setAttribute(actorSim, Pose=poseMatrix);            % 4x4
setAttribute(actorSim, Velocity=[vx vy vz]);        % 1x3
setAttribute(actorSim, AngularVelocity=[wx wy wz]); % 1x3

action = getAction(actorSim, "PathAction");
action = getAction(actorSim, "SpeedAction");
action = getAction(actorSim, "LaneChangeAction");

sendEvent(actorSim, "ActionComplete", actionID);
sendEvent(actorSim, "UserDefinedEvent", eventName, eventStruct);
eventData = receiveEvent(actorSim, "UserDefinedEvent", eventName);

Workflow E: Co-Simulation with Simulink

Architecture

  1. Create a Simulink model with the RoadRunner Scenario block at root level
  2. Use RoadRunner Scenario Reader blocks to receive data from RoadRunner
  3. Use RoadRunner Scenario Writer blocks to send data back
  4. Publish the behavior: Simulink.publish.publishActorBehavior("myModel", OutputFile="output.slprotodata")
  5. Assign the behavior to an actor (via RoadRunner UI, or programmatically in R2025a+ — see Workflow D)
  6. Start simulation via set_param("myModel", SimulationCommand="start") — NOT sim()

Key Constraints

  • The RoadRunner Scenario block must be at the model root level
  • Use set_param(..., SimulationCommand="start") to start — never sim("model")
  • Sample time in the RoadRunner Scenario block defines the co-sim step size (default 0.02s)
  • One MATLAB instance connects to one RoadRunner co-sim at a time
  • Bus types are loaded from rrScenarioSimTypes.mat

Reader Topics

Actor Pose, Specifications, Lane Location, Vehicle Wheel Poses, Traffic Signals, Target Poses, Lane Boundaries, Actions (Path/Speed/Lane Change/etc.), User-Defined Events

Writer Topics

Actor Pose, Vehicle Pose, Action Complete, User-Defined Events, Diagnostics

See references/simulink-cosim-details.md for block configuration and bus types.


Workflow F: Observers (Read-Only Monitoring)

Observers monitor simulation state without modifying it. Unlike co-sim behaviors (assigned in UI), observers are added programmatically.

rrSim = createSimulation(rrApp);
addObserver(rrSim, "VelocityMonitor", "MyVelocityObserver");
simulateScenario(rrApp, IsBlocking=false);
% Observer's stepImpl is called each step automatically

Note: The observer .m file must be on the MATLAB path. Use addpath if needed. Call addObserver BEFORE starting the simulation.

Observer System Object Pattern

classdef MyVelocityObserver < matlab.System
    properties (Access = private)
        ScenarioSim
    end

    methods (Access = protected)
        function setupImpl(obj)
            obj.ScenarioSim = Simulink.ScenarioSimulation.find( ...
                "ScenarioSimulation");
        end

        function stepImpl(obj)
            actors = get(obj.ScenarioSim, "ActorSimulation");
            for idx = 1:numel(actors)
                vel = getAttribute(actors{idx}, "Velocity");
                % Read-only analysis — logging, visualization, etc.
            end
        end
    end
end

CRITICAL: Observers are READ-ONLY. They may call getAttribute but must NOT call setAttribute. They find the ScenarioSimulation (not ActorSimulation) in setupImpl.

Management:

  • addObserver(rrSim, name, fileName) — add (returns logical success)
  • removeObserver(rrSim, name) — remove
  • get(rrSim, "Observers") — query registered observers

Observers can also be Simulink models (.slx) since R2024b.


Workflow G: Sensor Simulation

Attach driving sensors to actors and read ground-truth data during simulation.

rrSim = createSimulation(rrApp);
sensorSim = get(rrSim, "SensorSimulation");

% Create and attach sensors to ego vehicle (actor ID 1)
visionSensor = visionDetectionGenerator(SensorIndex=1, ...
    SensorLocation=[2.4 0], MaxRange=50);
radarSensor = drivingRadarDataGenerator(SensorIndex=2, ...
    MountingLocation=[1.8 0 0.2]);
addSensors(sensorSim, {visionSensor, radarSensor}, 1);

% During step loop
set(rrSim, SimulationCommand="Start");
for i = 1:numSteps
    set(rrSim, SimulationCommand="Step");
    targets = targetPoses(sensorSim, 1);       % struct array in ego frame
    lanes = laneBoundaries(sensorSim, 1);      % lane boundary data
end
set(rrSim, SimulationCommand="Stop");

Key points:

  • SensorSimulation is obtained via get(rrSim, "SensorSimulation") — NOT constructed directly
  • Each sensor must have a unique SensorIndex
  • The second argument in targetPoses(sensorSim, actorID) is the actorID of the ego vehicle (the one you attached sensors to via addSensors) — NOT the SensorIndex
  • targetPoses returns positions/velocities relative to the host vehicle
  • laneBoundaries supports OutputOption: "EgoLane", "EgoAdjacentLanes", "AllLanes"

Supported sensors: visionDetectionGenerator, drivingRadarDataGenerator, ultrasonicDetectionGenerator, lidarPointCloudGenerator, lidarSensor


Scenario Variables

% Get a variable
value = getScenarioVariable(rrApp, "EgoSpeed");

% Set a variable (value MUST be a string)
setScenarioVariable(rrApp, "EgoSpeed", "30");

% Get all variables — returns struct ARRAY with Name/Value fields
allVars = getAllScenarioVariables(rrApp);
for i = 1:numel(allVars)
    fprintf("%s = %s\n", allVars(i).Name, allVars(i).Value);
end

Do NOT use fieldnames(allVars) — that gives struct field names (Name, Value), not variable names.


Simulation Log

Programmatic log access requires stepping mode or a completed simulation:

% After simulation completes (or during stepping)
simLog = get(rrSim, "SimulationLog");

% Query actor pose history — returns struct array with .Time and .Pose fields
poseLog = get(simLog, "Pose", "ActorID", 1);
% poseLog(i).Time  — scalar timestamp
% poseLog(i).Pose  — 4x4 transform matrix

% Extract positions for plotting
positions = arrayfun(@(s) s.Pose(1:3,4)', poseLog, UniformOutput=false);
positions = vertcat(positions{:});  % Nx3 matrix

% Save/load logs
save(rrSim, "SimulationLog", "myLog.mat");
log = load(rrSim, "SimulationLog", "myLog.mat");

Log return format: get(simLog, "Pose", "ActorID", id) returns a struct array (not a numeric array). Each element has .Time (scalar) and .Pose (4x4 matrix). Use arrayfun to extract positions for plotting.


Key Functions

FunctionPurposeSince
simulateScenario(rrApp)Run simulation to completionR2024a
createSimulation(rrApp)Create ScenarioSimulation for step controlR2022a
prepareSimulation(rrApp)Submit sim data before co-sim clients connectR2024a
set(rrSim, SimulationCommand=cmd)Control simulationR2022a
get(rrSim, param)Query simulation stateR2022a
addObserver(rrSim, name, file)Add read-only observerR2022a
getAttribute(actorSim, attr)Read actor runtime stateR2022a
setAttribute(actorSim, NV)Set actor state (co-sim only)R2022a
getAction(actorSim, name)Get assigned actionR2022a
sendEvent(actorSim, event, ...)Send event to scenarioR2022b
receiveEvent(actorSim, event, name)Receive event from scenarioR2023a
addSensors(sensorSim, sensors, actorID)Attach sensorsR2023a
targetPoses(sensorSim, sensorID)Get target poses in ego frameR2023a
laneBoundaries(sensorSim, sensorID)Get lane boundary dataR2023a
Simulink.ScenarioSimulation.find(...)Find active simulation/actorsR2022a
Simulink.publish.publishActorBehavior(model)Publish Simulink behaviorR2022a
roadrunnerAPI(rrApp)Get authoring API (Project, Scenario)R2025a
getAsset(prj, path, type)Retrieve asset (BehaviorAsset, VehicleAsset, etc.)R2025a
createAsset(prj, path, type)Create new asset in projectR2025a
addActor(scnro, asset, position)Add actor to scenario programmaticallyR2025a

See references/simulation-api-reference.md for full function signatures, name-value pairs, and return types.


Architecture: Actor Behavior Types

R2024a: Behaviors are assigned in the RoadRunner Scenario Editor UI only. R2025a+: Behaviors can also be assigned programmatically via car.BehaviorAsset = behaviorAsset (see Workflow D).

Behavior TypeWhat It MeansMATLAB Role
Ready-to-RunActor follows built-in path/logicNo code needed — just observe
MATLAB SystemActor controlled by a System objectWrite System object (Workflow D)
Simulink ModelActor controlled by Simulink modelBuild model with RR blocks (Workflow E)

Common Mistakes

MistakeWhy It's WrongCorrect Approach
rrSim.start() / .step() / .stop()Dot-method syntax does not existset(rrSim, SimulationCommand="Start"|"Step"|"Stop")
actorSim.Pose or actorSim.VelocityNot public propertiesgetAttribute(actorSim, "Pose")
get(rrSim, "Actors")Wrong parameter nameget(rrSim, "ActorSimulation")
set(rrSim, Logging=true)Value must be stringset(rrSim, Logging="On")
Using drivingScenario for RoadRunnerCompletely different toolboxUse simulateScenario/createSimulation
roadrunnerScenario(name)This class does not existopenScenario(rrApp, name) then simulate
sim("model") for co-simWrong command for co-simulationset_param("model", SimulationCommand="start")
setBehavior(actor, ...)This function does not existUse car.BehaviorAsset = behaviorAsset (R2025a+) or assign in RoadRunner UI
fieldnames(getAllScenarioVariables(rrApp))Gets struct fields, not variable namesallVars(i).Name
log = simulateScenario(rrApp, ...)No return valueUse createSimulation + get(rrSim, "SimulationLog")
setAttribute in observerObservers are read-onlyUse getAttribute only
addSensors(rrSim, ...)Wrong objectaddSensors(sensorSim, ...) on SensorSimulation
getAttribute(actorSim, "Name")Name is static, not runtimeget(actorSim, "ActorModel") then getAttribute(actorModel, "Name")
actors(idx) after get(rrSim, "ActorSimulation")Returns a cell array, not an object arrayUse actors{idx} (cell indexing)
set(rrSim, SimulationCommand="Start") then "Step""Start" runs freely — sim completes before you can stepUse simulateScenario(rrApp, IsSteppingStart=true, IsBlocking=false) first
Step loop without pause(stepSize)"Step" is async — commands pile up and are silently droppedAdd pause(stepSize) after each set(rrSim, SimulationCommand="Step")
set(rrSim, Logging="On") during steppingErrors "Failed to enable or disable logging while simulation is running"Use simulateScenario(rrApp, ..., EnableLogging=true) before createSimulation
Treating actors{1} as the ego vehicleIndex 1 is always the world actor (ID 0), not a vehicleUse actors{2} or find by ID with Simulink.ScenarioSimulation.find("ActorSimulation", ActorID=uint64(id))
Simulink.ScenarioSimulation.find(..., ActorID=1)ActorID must be uint64, not doubleUse ActorID=uint64(1)
createSimulation after simulateScenario in stepping modeServer rejects connections while sim is pausedCall createSimulation(rrApp) BEFORE simulateScenario(rrApp, IsSteppingStart=true, ...)
targetPoses(sensorSim, sensorIndex)Second argument is the actorID, not sensorIndexUse targetPoses(sensorSim, actorID) where actorID is the ego vehicle's ID
setPlatform(behaviorAsset, "MATLAB")Not a registered type stringUse setPlatform(behaviorAsset, "SimulinkPlatform") for both MATLAB and Simulink behaviors

Conventions

  • Always use set/get with ScenarioSimulation — never dot-methods
  • Always use getAttribute/setAttribute for actor runtime state
  • Use simulateScenario for simple run-to-completion — don't over-engineer with createSimulation
  • Use createSimulation when you need step control, actor introspection, or log access
  • Behavior assignment: In R2024a, use RoadRunner UI. In R2025a+, use car.BehaviorAsset = getAsset(prj, path, "BehaviorAsset")
  • Co-sim System objects must use Simulink.ScenarioSimulation.find("ActorSimulation", SystemObject=obj) in setupImpl
  • Observers find "ScenarioSimulation" (not "ActorSimulation") and are read-only
  • Lifecycle: Opening a new scenario (openScenario) invalidates any existing rrSim — call createSimulation again

Copyright 2026 The MathWorks, Inc.

Signals

GitHub stars
1k
Forks
128
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
roadrunner-scenario-simulating
Source
github.com/matlab/matlab-agentic-toolkit