Skip to content

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 flow of the episode log: the game and ACRES Core give agent states, farm contacts and session messages to the same episode writer, the writer puts the messages into the chunks of an MCAP file, and ROS 2 tools or an episode replay in the game read the file. SOURCES WRITER AND READER FILE AND TOOLS ACRES Core Core step agent states and farm stamps DBW commands that the vehicle received Game Physics thread the state of each agent after each physics step goes into a queue Farm farm stamps: tyre and body contacts farm events: crop, field work, ruts Session episode and agents: start and reset conditions: each second and on change shifts: start and on change Vehicles and farm in a replay each vehicle follows its logged state the farm repeats the stamps and the events no physics model runs Episode writer the same C++ file in the game and in ACRES Core ROS 2 messages as CDR time = simulation time game: at the end of each frame AcresEpisodeLog.cpp Episode log episode.mcap magic, header: profile ros2 schemas, channels, metadata chunk: 1 MiB of messages message indexes of the chunk more chunks and indexes summary: statistics, indexes footer with CRC, magic chunks ROS 2 and MCAP tools ros2 bag info ros2 bag play Python mcap, Foxglove Episode reader -EpisodeReplay= reads the file at the start all messages
The game and ACRES Core give their messages to the same episode writer. ROS 2 tools and the episode replay read the file. Open the diagram

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.

Packaged/Linux/Acres.sh -VehicleDemo -Vehicle=polaris \
    -SimControl=5600 -Lockstep -RlPort=5556 \
    -EpisodeLog=/data/run0.mcap
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)
ACRES_EPISODE_LOG_START /data/run0.mcap
ACRES_EPISODE_LOG_STOP /data/run0.mcap messages=8563

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:

messages: 8563  chunks: 6  size: 6.0 MiB

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.

python Tools/EpisodeLog/gen_schemas.py --check
schemas up to date

The start of the schema of /sim/agent_states:

std_msgs/Header header
uint64 step
AgentState[] agents
================================================================================
MSG: std_msgs/Header
builtin_interfaces/Time stamp
string frame_id

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:

\[ E = E_0 + x \cos\gamma + y \sin\gamma, \qquad N = N_0 - x \sin\gamma + y \cos\gamma \]

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.

first state: log_time 8333333 publish_time 8333333
             step 1 stamp 0 s 8333333 ns frame world
last state:  log_time 31000000000 step 3720

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.

agent polaris position [-38.71, -141.661, 0.839]

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/episode  acres_interfaces/msg/Episode  count 1

/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/agents  acres_interfaces/msg/Agents  count 1

/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/agent_states  acres_interfaces/msg/AgentStates  count 3720

/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_stamps  acres_interfaces/msg/FarmStamps  count 3705

/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/farm_events  acres_interfaces/msg/FarmEvents  count 1102

/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/conditions  acres_interfaces/msg/Conditions  count 33

/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/shifts  acres_interfaces/msg/VehicleShifts  count 1

/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.
/sim/task  acres_interfaces/msg/TaskStatus  count 0

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.

A log of ACRES Core in Python, 30 s:

/sim/agent_states              AgentStates  3600
/sim/farm_stamps               FarmStamps   3600
/polaris/vehicle/steering/cmd  SteeringCmd  600
/polaris/vehicle/ulc/cmd       UlcCmd       600
/polaris/vehicle/dbw_enabled   Bool         1

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.

source ROS/Env/setup_env.sh
ros2 bag info -s mcap /data/run0.mcap
ros2 bag play -s mcap /data/run0.mcap --clock \
    --topics /sim/agent_states
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.
Tools/EpisodeLog/build.sh
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.
source ROS/Env/setup_env.sh
python Tools/SimControl/compare_episodes.py \
    /data/run0.mcap /data/run1.mcap
polaris: 3720 common steps, max position difference
0.000e+00 m (step None), max heading difference 0.000e+00 deg
steps only in a: 0, only in b: 0