Episode Log#
The episode log is one MCAP file with the state of each agent after each physics step, the farm contacts and the conditions. The game and ACRES Core write the same file, and the game can show it again as an episode replay.
The messages are ROS 2 messages of the package acres_interfaces in the CDR format.
The tools for ROS 2 bags read the file without a conversion. The fields of each message are on the page Messages.
| Item | Type | Function |
|---|---|---|
| Write a Log | Procedure | The five methods to start a recording. |
| File Structure | Format | The MCAP records in the file. |
| Schemas and Encoding | Format | The message definitions and the CDR format. |
| Metadata Record | Format | The keys of the record acres_episode. |
| Clock and Time Stamps | Format | The simulation time in the file. |
| Frames | Format | The world frame and the vehicle frame. |
/sim/episode |
Channel | The episode clock. |
/sim/agents |
Channel | The description of the agents. |
/sim/agent_states |
Channel | The state of each agent, at 120 Hz. |
/sim/farm_stamps |
Channel | The contacts of the tyres and the body with the farm. |
/sim/farm_events |
Channel | The changes of the crop and the soil. |
/sim/conditions |
Channel | The clock, the weather and the soil water. |
/sim/shifts |
Channel | The hardware shifts. |
/sim/task |
Channel | The task status of a learner. |
| Command Channels of ACRES Core | Channel | The drive-by-wire commands that the vehicle received. |
| Read a Log | Procedure | Python, ROS 2 tools and the episode replay. |
| Differences from a ROS 2 Bag | Table | The episode log and a recording of ros2 bag record. |
| Tools | Tools | The tests of the format and the comparison of two logs. |
Recording#
Write a Log#
A recording starts with the messages of the episode, the agents, the conditions and the shifts. Then the writer adds the messages of each physics step until the recording stops.
| Method | Command |
|---|---|
| Option of the game | -EpisodeLog=<file.mcap> |
| Simulator control channel | The operation record |
| ROS 2 | The service /acres/record: refer to Services and Actions |
| Core server | core_sim --episode-log <file.mcap> |
| ACRES Core in Python | Batch.start_log() and Batch.stop_log(): refer to Python API |
The game queues the agent states on the physics thread and writes them at the end of each frame.
A reset does not start a new file. The writer adds a new /sim/episode message to the same file.
import math
import acres_core
import numpy as np
batch = acres_core.Batch(".", num_envs=1)
batch.reset([0], np.array([[-38.71, -140.513, math.pi / 2, 0.0]]))
batch.start_log(0, "/data/core-episode.mcap")
for k in range(300): # 30 s of simulation time
curvature = 0.08 * math.sin(2 * math.pi * k / 200)
batch.step_curvature_speed(np.array([curvature]),
np.array([2.0]))
batch.stop_log(0)
Format#
File Structure#
The file obeys the MCAP specification, version 0, with the profile ros2.
The writer is FMcapWriter in Acres/Source/Acres/AcresEpisodeLog.cpp. It uses no Unreal types and no ROS 2 library.
| Part | Records | Content |
|---|---|---|
| Start | Magic, Header | The profile ros2 and the library acres-episode-log 1. |
| Data | Schema, Channel | One schema and one channel for each topic. |
| Data | Metadata | The record acres_episode. |
| Data | Chunk | The Message records. The writer closes a chunk at 1 MiB. |
| Data | Message Index | After each chunk: one record for each channel in the chunk. |
| Data | Data End | The end of the data section. |
| Summary | Schema, Channel | A copy of all schemas and channels. |
| Summary | Statistics | The message counts and the first and last time. |
| Summary | Chunk Index, Metadata Index | The positions of the chunks and of the metadata. |
| Summary | Summary Offset | The position of each group of the summary. |
| End | Footer, Magic | The positions of the summary and its CRC. |
The chunks have no compression. Each chunk has a CRC-32 of its records, and the footer has a CRC-32 of the summary. The CRC of the data section in the Data End record is 0. This means "not calculated".
The writer flushes the file after each chunk. When the simulator stops abnormally, the file has no summary. A reader then loses the messages of the last chunk only.
magic 89 4D 43 41 50 30 0D 0A
header profile=ros2 library="acres-episode-log 1"
schema 1 acres_interfaces/msg/Episode ros2msg
channel 0 /sim/episode cdr
...
metadata acres_episode
chunk messages, about 1 MiB, CRC-32
index channel 2: (log time, offset) ...
chunk ...
data end
summary schemas, channels, statistics,
chunk indexes, metadata index
footer summary start, offsets start, CRC-32
magic
A log of 31 s with one Polaris:
Schemas and Encoding#
Each channel has the message encoding cdr: the wire format of ROS 2, little-endian, with a header of 4 bytes.
Each schema has the encoding ros2msg. The schema data is the text of the .msg file and of each message type that it uses.
This is the text that rosbag2 writes.
The schema texts are in the generated header Acres/Source/Acres/AcresEpisodeSchemas.h.
The script Tools/EpisodeLog/gen_schemas.py makes this header from the folder ROS/acres_interfaces/msg.
Run the script again after each change of a message.
Each channel has the metadata key offered_qos_profiles. ros2 bag play uses it to publish the topic.
The topics /sim/episode, /sim/agents and /sim/shifts are reliable and transient local. The other topics are reliable and volatile.
The start of the schema of /sim/agent_states:
Metadata Record#
The file has one metadata record with the name acres_episode. All values are strings.
| Key | Writer | Value |
|---|---|---|
schema_version |
all | 1. |
source |
all | unreal for the game, core for ACRES Core. |
map |
all | The map, for example V03ACRE. |
created_utc |
all | The UTC time at which the writer opened the file, ISO 8601. |
physics_hz |
all | 120. |
command_line |
game, Core server | The command line of the simulator. |
seed |
game, Core server | The seed of the sensor noise. |
utm_zone |
game, Core server | 16N. |
utm_origin |
game, Core server | The UTM easting and northing of the world origin in metres. |
grid_rotation_deg |
game, Core server | The UTM grid azimuth of the world y axis in degrees. |
render_tier |
game | The render tier: low or high. |
sensor_render_profile |
game | The sensor profile, for example calibrated. |
sensor_profile |
game, Core server | The sensor configuration of each agent that records, as <agent>=<name>, or none. |
ACRES Core in Python writes the first five keys and the keys that the caller gives to start_log().
The georeference converts a world position to UTM zone 16N:
Here \( (E_0, N_0) \) is utm_origin and \( \gamma \) is grid_rotation_deg.
metadata acres_episode
command_line = -RenderOffscreen -VehicleDemo
-Vehicle=polaris ... -EpisodeLog=/data/run0.mcap
created_utc = 2026-10-02T07:14:11.776Z
grid_rotation_deg = 0.054091589
map = V03ACRE
physics_hz = 120
render_tier = high
schema_version = 1
seed = 42
sensor_profile = none
sensor_render_profile = calibrated
source = unreal
utm_origin = 500476.9350 4480099.7548
utm_zone = 16N
Clock and Time Stamps#
The log time and the publish time of each message are the simulation time in nanoseconds. The header stamp of the message has the same value. The file contains no wall-clock time of the messages.
The simulation time is the physics clock: 0 at the start of the world and 1/120 s for each physics step.
The field step of the step messages counts the physics steps. The stamp of a state is the time after its step.
A reset does not set the clock back. The field episode_time_s gives the time since the last reset.
The messages are in the file in the sequence of their steps.
The stamps of /sim/agent_states, /sim/farm_stamps and /sim/farm_events are exact multiples of 1/120 s.
Frames#
All positions are in the frame world: the grid of the tile in metres with x east, y north and z up.
The origin is the centre of the tile. The units are SI units.
The pose of an agent is the pose of its frame base_footprint: the point on the ground below the centre of the rear axle.
The axes of this frame are x forward, y left and z up. The twist of an agent is in this body frame.
Channels#
The rate of a channel is its rate in simulation time.
/sim/episode#
The episode clock. The first message of the file is on this channel.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/Episode |
| Written | At the start of the recording, after each reset and after each change of the simulation state. |
| Content | The episode identifier and number, the physics step, the simulation state, the seed, the source, the map and the agent names. |
To divide a file into episodes, use the messages of this channel.
/sim/agents#
The description of each agent. The episode replay uses it to make the vehicles of the log.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/Agents |
| Written | At the start of the recording and after each reset. |
| Content | For each agent: the name, the vehicle, the implement and the paint. Also the body origin in base_footprint, the wheelbase, the wheel radii and the names of the implement rig controls. |
/sim/agent_states#
The state of all agents after one physics step. One message contains the agents in the sequence of their index.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/AgentStates |
| Rate | 120 Hz: one message for each physics step. |
| Content | For each agent: the pose, the twist, the wheel states, the engine speed, the gear and the applied controls. Also the drive-by-wire state, the energy ledger, the fuel, the crushed crop area and the implement state. |
The wheel states are the steering angle, the rotation angle, the speed, the suspension travel, the slip and the sinkage. They also contain the forces, the contact flag, the surface class and the field. The energy ledger is on the page Energy and Fuel.
/sim/farm_stamps#
The contacts of the vehicles with the farm in one physics step. A step without a contact has no message. A replay gives the same contacts to the farm, thus the farm makes the same crushed crop, ruts and tyre prints.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/FarmStamps |
| Rate | 120 Hz maximum. |
| Content | For each contact: the kind, the agent, the wheel, the position, the heading and the size of the contact patch. Also the pressure, the sinkage, the tyre diameter, the body clearance, the crushed area and the field. |
/sim/farm_events#
The changes of the farm in one physics step. A step without a change has no message. The game writes this channel. ACRES Core does not write it.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/FarmEvents |
| Rate | 120 Hz maximum. |
| Content | For each event: the kind, the agent, the field, the crop patch and its samples, the work flags, the position and a value. |
| Kind | Event | Value |
|---|---|---|
| 1 | The vehicle crushed crop. | The area in m². |
| 2 | The implement harvested crop. | The area in m². |
| 3 | Field work on a cell of 25 cm: tilled, seeded or sprayed. | The area of the cell in m². |
| 4 | A rut became deeper on a cell of 25 cm. | The new depth in m. |
| 5 | A reset restored a field. |
/sim/conditions#
The clock, the weather and the soil water of the session.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/Conditions |
| Written | At the start, after each reset, after each set_conditions and one time for each second. |
| Content | The local date and hour, the air temperature, the rain rate, the wind, the cloud cover and the visibility. Also the sun elevation, the surface wetness, the weather preset and the soil water content of each field. |
The episode replay sets the sky from the first message of this channel. ACRES Core writes this channel only at the start, after a reset and after a change.
/sim/shifts#
The hardware shifts that are in effect: refer to set_vehicle_shift.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/VehicleShifts |
| Written | At the start of the recording and after each change of a shift. |
| Content | For each agent with a shift: the label, the actuator offsets and the offsets of the camera mount and the LiDAR mount. |
/sim/task#
The status of a task: the reward terms, the instruction and the result. The simulators do not write this channel. The task code of a learner writes it through ACRES Core. The file always has the channel, and its message count is 0 in a log without a task.
| Name | Value |
|---|---|
| Type | acres_interfaces/msg/TaskStatus |
| Written | By the task code. |
| Content | The task, the instruction, the names and values of the reward terms, the reward, done and success. |
Command Channels of ACRES Core#
ACRES Core also writes the drive-by-wire commands in the form in which the Polaris received them. The topics have the name of the agent as their namespace. The game does not write these channels. The stamp of a command is the start time of the physics step that applied it.
| Topic | Type | Written |
|---|---|---|
/<agent>/vehicle/steering/cmd |
ds_dbw_msgs/msg/SteeringCmd |
At each new command. |
/<agent>/vehicle/throttle/cmd |
ds_dbw_msgs/msg/ThrottleCmd |
At each new command. |
/<agent>/vehicle/brake/cmd |
ds_dbw_msgs/msg/BrakeCmd |
At each new command. |
/<agent>/vehicle/ulc/cmd |
ds_dbw_msgs/msg/UlcCmd |
At each new command. |
/<agent>/vehicle/gear/cmd |
ds_dbw_msgs/msg/GearCmd |
At each change of the gear command. |
/<agent>/vehicle/dbw_enabled |
std_msgs/msg/Bool |
At each change of the enable state. |
A reader that does not know a topic ignores it. The episode replay ignores these channels.
Reading#
Read a Log#
Python. The packages mcap and mcap-ros2-support read the file without a ROS 2 installation.
They use the schemas in the file to decode the messages.
ROS 2. Source the ROS 2 environment of the repository first: source ROS/Env/setup_env.sh.
ros2 bag info reads the summary. ros2 bag play publishes the topics, and the option --clock also publishes /clock.
The package rosbag2_py and the messages of acres_interfaces decode the messages in Python.
Game. The option -EpisodeReplay= shows the log: refer to Replay.
The reader of the game reads the complete file at the start. It refuses a file with compression in its chunks.
To remove the compression of a file from a different program, use mcap convert --compression none.
from mcap.reader import make_reader
from mcap_ros2.decoder import DecoderFactory
with open("/data/run0.mcap", "rb") as stream:
reader = make_reader(stream, decoder_factories=[DecoderFactory()])
stats = reader.get_summary().statistics
print("messages:", stats.message_count)
for schema, channel, message, states in reader.iter_decoded_messages(
topics=["/sim/agent_states"]):
agent = states.agents[0]
print(states.step, agent.name, agent.pose.position.x,
agent.pose.position.y, agent.speed_mps)
break
Files: run0.mcap
Bag size: 6.0 MiB
Storage id: mcap
Duration: 31.000001617s
Start: Dec 31 1969 19:00:00.000000000 (0.000000000)
End: Dec 31 1969 19:00:31.000001617 (31.000001617)
Messages: 8563
Topic information:
Topic: /sim/task | Type: acres_interfaces/msg/TaskStatus | Count: 0
Topic: /sim/shifts | Type: acres_interfaces/msg/VehicleShifts | Count: 1
Topic: /sim/conditions | Type: acres_interfaces/msg/Conditions | Count: 33
Topic: /sim/farm_stamps | Type: acres_interfaces/msg/FarmStamps | Count: 3705
Topic: /sim/farm_events | Type: acres_interfaces/msg/FarmEvents | Count: 1102
Topic: /sim/agent_states | Type: acres_interfaces/msg/AgentStates | Count: 3720
Topic: /sim/agents | Type: acres_interfaces/msg/Agents | Count: 1
Topic: /sim/episode | Type: acres_interfaces/msg/Episode | Count: 1
Differences from a ROS 2 Bag#
A bag is a recording of ROS 2 topics that ros2 bag record makes from the ROS 2 bridge. Both files use the MCAP format.
| Item | Episode Log | Bag of ros2 bag record |
|---|---|---|
| Writer | The simulator, without ROS 2. | The ROS 2 recorder, from the topics of the ROS 2 bridge. |
| Content | The ground truth of the simulation: states, farm contacts, conditions. | The sensor topics and the reports that a robot gets. |
| Rate | Each physics step, 120 Hz. The writer loses no message. | The rates of the topics. The recorder can lose messages. |
| Time | The log time is the simulation time. | The log time is the time of the recorder: the wall clock, or /clock with use_sim_time. |
| Layout | One .mcap file. |
A folder with a .mcap file and the file metadata.yaml. |
| Compression | None. | An option of the recorder. |
| Use | Episode replay, render-later, comparison of two simulators. | Tests of ROS 2 nodes with recorded sensor data. |
ros2 bag info and ros2 bag play need the option -s mcap for an episode log, because the file has no metadata.yaml.
Tools#
Tools/EpisodeLog/build.sh#
Compiles and runs the tests of the format without Unreal: the CDR layout, each message type and a complete MCAP file.
It also examines a file that ends too early and a chunk with compression.
The script first examines the generated schema header.
When the ROS 2 environment is active, check_ros.py reads the test file with rosbag2_py.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
BUILD_DIR |
path | $TMPDIR/acres-episode-log |
The folder of the test program and the test file. | |
CXX |
string | g++ |
The C++ compiler. |
schemas up to date
built /tmp/acres-episode-log/episode_log_tests
CDR layout
[PASS] time, string, uint8, float64: 28 bytes
...
MCAP write and read
[PASS] close: 3029 messages
[PASS] truncated file: 1767 states
[PASS] compressed chunk refused: compressed chunk
(zstd): write the log uncompressed
19 passed, 0 failed
Tools/SimControl/compare_episodes.py#
Compares the trajectories of the agents in two episode logs, step by step.
It prints the largest difference of the position and of the heading for each agent.
The script needs the ROS 2 environment with the package acres_interfaces.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
a.mcap |
path | The first episode log. | ||
b.mcap |
path | The second episode log. |