Lockstep Stepping#
In lockstep, the simulation advances only when a client requests physics steps. This tutorial steps the game from Python and from ROS 2, records an episode log and examines the determinism of two runs.
Before You Start#
- Build or install the packaged game: refer to Run the Packaged Game.
- For the ROS 2 procedure, build the ROS 2 workspace: refer to Build the ROS 2 Workspace.
- Use Python 3. The Python client uses only the standard library.
- Do all commands in the repository root.
The procedures use three sockets of the game.
| Socket | Option | Port |
|---|---|---|
| Simulator Control Channel | -SimControl= |
5600 |
| Vehicle Bridge | -RlPort= |
5556 |
| Sensor Stream | -SensorStream= |
5601 |
How a Step Request Operates#
One physics step is 1/120 s. A step request of 12 steps advances the simulation by 0.1 s.
| Rule | Description |
|---|---|
| Hold | Between the requests, the simulator holds the world. The physics, the clock, the weather and the traffic do not advance. |
| One step for each frame | During a request, the frame time is one physics step. The game does one physics step in each frame. |
| Commands | A command that arrives before the request applies at the first step of the request. |
| Sensors | With a sensor stream or a sensor recording, the game releases the next step only when the sensors are idle. A LiDAR scan or a camera image thus shows the world of its own step. |
| Barrier | After the last step, the simulator sends a barrier on each vehicle bridge, each sensor stream and each CAN transport. |
| Reply | The reply to the request comes after the barriers. The client then reads each channel as far as its barrier. |
Note
The wall-clock limits of the vehicle bridge do not apply in lockstep. A controller can use as much time as necessary between two requests.
Step from Python#
-
Start the game in lockstep with one Polaris and its three sockets.
Packaged/Linux/Acres.sh -VehicleDemo -Vehicle=polaris \ -SensorStream=5601 -RlPort=5556 -SimControl=5600 -LockstepExpected Result
The log contains these lines. The window shows the Polaris in front of the ICSC garage, and the clock of the HUD does not move.
-
Start Python and connect to the simulator control channel and to the vehicle bridge.
-
Send the commands for the next step: enable the drive-by-wire, select the low gear and command 2 m/s.
-
Request 12 physics steps.
-
Read the vehicle bridge as far as the barrier of the request.
-
Start an episode log and run a control loop at 10 Hz of simulation time for 10 s.
ctl.call("record", action="start", path="/data/lockstep.mcap") for k in range(100): dbw.send({"dbw": {"enable": True}}) dbw.send({"gear_cmd": {"cmd": 5}}) dbw.send({"ulc_cmd": {"cmd": 2.0, "cmd_type": 1, "enable": True}}) dbw.send({"steering_cmd": {"cmd": 60.0, "cmd_type": 2, "cmd_rate": 200.0, "enable": True}}) reply = ctl.call("step", steps=12) dbw.wait_barrier(reply["barrier"]) print(reply["time_s"], dbw.report["ulc_report"]["vel_meas"]) print(ctl.call("record", action="stop"))Expected Result
The Polaris accelerates in a left curve. After 10 s its speed is 1.54 m/s. The episode log contains one state for each of the 1200 physics steps.
-
Stop the game. If you continue with the procedure Step from ROS 2, do not do this step.
Expected Result
The log contains
ACRES_SIM_CONTROL_QUITand the game stops.
CAUTION
Do not use the method SimControl.call() for a step request with progress equal to true. The method uses the first progress event as the reply.
Step from ROS 2#
The ROS 2 bridge gives the step request as the service /step_simulation and as the action /simulate_steps.
The bridge sends the commands that it holds to the game before it sends the request.
The service returns after the bridge published all sensor data and reports of the steps.
WARNING
The drive-by-wire topics of the simulator have the same names as the topics of the real vehicle. Source ROS/Env/setup_env.sh in each terminal. It keeps all ROS 2 traffic on the loopback address.
-
Use the game of the procedure Step from Python, but do not stop it. Stop only the Python client.
Expected Result
The game stays in the paused state. Each socket of the game accepts one client, thus the Python client must disconnect first.
-
In a second terminal, start the ROS 2 bridge.
-
In a third terminal, request 12 physics steps with the service.
-
Request 36 steps with the action. The action gives feedback during the steps.
-
Run the worked example. It records an episode log and drives the Polaris for 20 s of simulation time.
Expected Result
Each 0.1 s, the example publishes the drive-by-wire commands and then requests 12 steps.
recording: /data/lockstep-ros.mcap t 0.0 s utm 500434.61 4479976.26 v 1.06 m/s t 5.0 s utm 500431.65 4479982.85 v 1.98 m/s t 10.0 s utm 500424.25 4479988.99 v 2.45 m/s t 15.0 s utm 500415.99 4479993.98 v 2.07 m/s 20 s simulated in 67.0 s of wall time; log /data/lockstep-ros.mcap: 5905 messages, 20.0 s -
Stop the game with the service of the simulation state. Then stop the ROS 2 bridge with Ctrl+C.
The sensor messages of a request are on their topics when the service returns. This table shows the messages that a ROS 2 node received for each of five requests of 12 steps.
| Request | Simulation Time after the Request | /clock |
/vehicle/odom |
Stamp of /lidar/points |
Stamp of /camera/image_raw |
|---|---|---|---|---|---|
| 1 | 10.6000 s | 12 messages | 10 messages | 10.5083 s | 10.5083 s |
| 2 | 10.7000 s | 12 messages | 10 messages | 10.6083 s | 10.6083 s |
| 3 | 10.8000 s | 12 messages | 10 messages | 10.7083 s | 10.7083 s |
| 4 | 10.9000 s | 12 messages | 10 messages | 10.8083 s | 10.8083 s |
| 5 | 11.0000 s | 12 messages | 10 messages | 10.9083 s | 10.9083 s |
Each request gives one clock message for each physics step, ten INS epochs, one LiDAR scan and one camera image. The stamp of the scan and of the image is the time of the physics step at which the sensor took its sample.
The services and the action are on the page Services and Actions.
Examine the Determinism#
Two runs with the same seed and the same commands at the same physics steps give the same trajectory.
The tool Tools/SimControl/lockstep_determinism.py starts the game two times, drives the same command program and compares the poses.
-
Close all instances of the game. The tool starts the game itself, one instance at a time.
-
Run the tool with two runs of 30 s.
Expected Result
The tool prints the summary and writes it to
summary.json. The exit code is 0 when the largest position difference is 1 cm or less. In this test the two runs of 56.7 m are equal: the difference is 0.{ "runs": [ {"steps": 3720, "wall_s": 65.72795515193138, "realtime_factor": 0.47164102288505594, "log_messages": 8563, "start_step": 0}, {"steps": 3720, "wall_s": 65.85412743897177, "realtime_factor": 0.4707373889165606, "log_messages": 8563, "start_step": 0} ], "comparisons": [ {"samples": 310, "max_position_diff_m": 0.0, "at_time_s": null, "max_yaw_diff_deg": 0.0, "path_length_m": 56.72085725036201, "steps_equal": true} ], "headless": false, "npc": false, "pass_1cm": true } -
Compare the two episode logs step by step.
-
Do the same test with ACRES Core. The option
--game corestarts the Core server in place of the game. This option needs the ROS 2 workspace.
Obey these rules to get a run that you can repeat.
| Rule | Reason |
|---|---|
| Send commands only between step requests. | A command that arrives during a request applies at a step that changes with the timing. |
| Use the same options and the same seed. | The sensor noise and the weather use the seed. |
| Start each run with a new session. | A reset does not set the physics clock or the soil water back to the start. |
Start the game with -Lockstep. |
The soil water then follows the simulation time and not the wall clock. |
The model of the time steps is on the page Time Stepping and Determinism.
Stepping Rate#
In the game, one physics step uses one frame, thus the frame rate limits the stepping rate. These rates are measurements on a workstation with one RTX 5060 Ti (16 GB) and the high render tier.
| Simulator and Session | Client | Steps for Each Second | Ratio to Real Time |
|---|---|---|---|
| Game, 1280 × 720, one Polaris, no sensor stream | One request of 600 steps. | 63 | 0.53 |
| Game, 640 × 360, one Polaris, no sensor stream, episode log | One request of 12 steps for each 0.1 s. | 56.5 | 0.47 |
| Game, 640 × 360, one Polaris with the sensor stream | The Python procedure of this page. | 36.8 | 0.31 |
| Game, 640 × 360, one Polaris with the sensor stream | The ROS 2 service, 12 steps for each request. | 36.8 | 0.31 |
| Game, 1280 × 720, episode replay in lockstep | One request of 1200 steps. | 63 | 0.53 |
| Core server, one Polaris | One request of 12 steps and one pose request for each 0.1 s. | 6800 | 57 |
| Core server, one Polaris, no client on the sensor stream | One request of 600 steps. | 100 000 | 850 |
The sensor stream makes the game record the camera and the LiDAR. The game then waits for the sensors after each step, thus the rate decreases. The Core server has no frames. Its rate is the rate of the physics model and of the client.
Next Steps#
- Simulator Control Channel: all operations of the channel.
- Vehicle Bridge: the commands, the reports and the barrier.
- Episode Log: the file that the steps record.
- Replay: show the episode log in the game from a different camera.
- Headless Core Runs: the same steps without a renderer.