DOI: 10.18419/darus-5612
title: Digital Twin task logs for Collective Robotic Construction (CRC) — ROB|ARCH 2024

description: &lt;!doctype html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
 &lt;meta charset="utf-8" /&gt;
 &lt;meta name="viewport" content="width=device-width, initial-scale=1" /&gt;
 &lt;title&gt;Digital Twin task logs for Collective Robotic Construction (CRC) — ROB|ARCH 2024&lt;/title&gt;
 &lt;style&gt;
 :root{
 --bg:#ffffff; --fg:#111827; --muted:#6b7280; --border:#e5e7eb;
 --codebg:#0b1020; --codefg:#e5e7eb; --link:#2563eb;
 }
 html,body{background:var(--bg); color:var(--fg); margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; line-height:1.6;}
 main{max-width:980px; margin:0 auto; padding:32px 20px 64px;}
 h1,h2,h3{line-height:1.25; margin:1.2em 0 .5em;}
 h1{font-size:2rem; margin-top:0;}
 h2{font-size:1.35rem; border-top:1px solid var(--border); padding-top:1.1rem;}
 h3{font-size:1.1rem;}
 p{margin:.75em 0;}
 ul,ol{padding-left:1.3em;}
 code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; font-size:.95em;}
 pre{background:var(--codebg); color:var(--codefg); padding:14px 16px; border-radius:10px; overflow:auto; border:1px solid rgba(255,255,255,.08);}
 pre code{color:inherit;}
 a{color:var(--link); text-decoration:none;}
 a:hover{text-decoration:underline;}
 hr{border:0; border-top:1px solid var(--border); margin:1.6em 0;}
 table{width:100%; border-collapse:collapse; margin:1em 0; font-size:.95rem;}
 th,td{border:1px solid var(--border); padding:10px 10px; vertical-align:top;}
 th{background:#f9fafb; text-align:left;}
 .muted{color:var(--muted);}
 &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
 &lt;main&gt;
 &lt;h1&gt;Digital Twin task logs for Collective Robotic Construction (CRC) — ROB|ARCH 2024&lt;/h1&gt;

 &lt;h2&gt;Overview&lt;/h2&gt;
 &lt;p&gt;
 This repository contains a dataset of &lt;strong&gt;Digital Twin (DT) task logs&lt;/strong&gt; from a
 &lt;strong&gt;Collective Robotic Construction (CRC)&lt;/strong&gt; workshop held at &lt;strong&gt;ROB|ARCH 2024&lt;/strong&gt;.
 Over three days, 14 participants programmed eight low-cost mobile robots (&lt;strong&gt;RADr&lt;/strong&gt;) to develop
 and test &lt;em&gt;decentralised&lt;/em&gt; construction behaviours, while a &lt;strong&gt;Vicon motion tracking system&lt;/strong&gt;
 provided global state feedback.
 &lt;/p&gt;
 &lt;p&gt;The dataset captures the DT’s &lt;strong&gt;task-level interaction&lt;/strong&gt; with:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;8 RADr robots&lt;/strong&gt; (mobile, magnetic gripper, onboard sensors)&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Vicon&lt;/strong&gt; (external tracking of robot + material poses)&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Digital material modules&lt;/strong&gt; (passive tracked objects; labelled &lt;code&gt;DM0…&lt;/code&gt;)&lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;
 Each experimental run is recorded as a JSON array of &lt;em&gt;task records&lt;/em&gt; (e.g., &lt;code&gt;Move&lt;/code&gt;,
 &lt;code&gt;Grip&lt;/code&gt;, &lt;code&gt;Read&lt;/code&gt;) with timestamps, task parameters, and the corresponding responses
 from the physical actors.
 &lt;/p&gt;

 &lt;h2&gt;Case study context (CRC)&lt;/h2&gt;
 &lt;ul&gt;
 &lt;li&gt;Workspace: ~4.6 × 5.8 m floor divided into a 4 × 5 grid.&lt;/li&gt;
 &lt;li&gt;Tracking: Vicon motion capture (overhead coverage).&lt;/li&gt;
 &lt;li&gt;Robots: 8 × RADr (2-wheel drive, magnetic gripper, onboard proximity/boundary sensors).&lt;/li&gt;
 &lt;li&gt;Materials: passive “digital material” modules with retroreflective markers (tracked by Vicon).&lt;/li&gt;
 &lt;li&gt;
 Execution mode: &lt;strong&gt;Adaptive multi-actor execution&lt;/strong&gt; (robots act in parallel; robots can be
 inserted/removed/reprogrammed during a run; DT maintains shared situational awareness).
 &lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;The DT architecture instantiated two principal task families:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;RADr tasks:&lt;/strong&gt; &lt;code&gt;Move&lt;/code&gt;, &lt;code&gt;Grip&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Vicon tasks:&lt;/strong&gt; &lt;code&gt;Read&lt;/code&gt; (a.k.a. &lt;code&gt;ReadState&lt;/code&gt; snapshots)&lt;/li&gt;
 &lt;/ul&gt;

 &lt;h2&gt;Repository structure&lt;/h2&gt;
 &lt;pre&gt;&lt;code&gt;.
├── Data/
│ ├── Run-1.json
│ ├── Run-2.json
│ ├── ...
│ └── Run-12.json
├── Figures/
│ ├── Tasks and durations per CRC run.png
│ ├── Average task rate per RADr robot.png
│ └── Per RADr control loop times.png
├── Analysis.py
└── Replay_Run.gh
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;What each top-level item is&lt;/h3&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;Data/Run-*.json&lt;/code&gt;: &lt;strong&gt;Primary dataset.&lt;/strong&gt; One JSON file per CRC run (job).&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;Figures/&lt;/code&gt;: Example plots generated from &lt;code&gt;Analysis.py&lt;/code&gt; (included for convenience).&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;Analysis.py&lt;/code&gt;: Reference analysis script that loads the runs and reproduces the included plots.&lt;/li&gt;
 &lt;li&gt;
 &lt;code&gt;Replay_Run.gh&lt;/code&gt;: Grasshopper definition (Rhino/Grasshopper) for replaying/visualising a run
 (see “Replay” section below).
 &lt;/li&gt;
 &lt;/ul&gt;

 &lt;h2&gt;Runs included&lt;/h2&gt;
 &lt;p&gt;
 The dataset contains &lt;strong&gt;12 runs&lt;/strong&gt; (&lt;code&gt;Run-1&lt;/code&gt; … &lt;code&gt;Run-12&lt;/code&gt;).
 Each run is a task log containing a mixture of:
 &lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;Vicon &lt;code&gt;Read&lt;/code&gt; snapshots (typically ~1 Hz)&lt;/li&gt;
 &lt;li&gt;RADr &lt;code&gt;Move&lt;/code&gt; tasks (motor commands)&lt;/li&gt;
 &lt;li&gt;RADr &lt;code&gt;Grip&lt;/code&gt; tasks (motor commands + gripper toggle)&lt;/li&gt;
 &lt;/ul&gt;

 &lt;p&gt;&lt;strong&gt;Overall totals (all runs combined):&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;Total task records: &lt;strong&gt;22,571&lt;/strong&gt;&lt;/li&gt;
 &lt;li&gt;Vicon &lt;code&gt;Read&lt;/code&gt;: &lt;strong&gt;12,688&lt;/strong&gt;&lt;/li&gt;
 &lt;li&gt;RADr &lt;code&gt;Move&lt;/code&gt;: &lt;strong&gt;8,080&lt;/strong&gt;&lt;/li&gt;
 &lt;li&gt;RADr &lt;code&gt;Grip&lt;/code&gt;: &lt;strong&gt;1,234&lt;/strong&gt;&lt;/li&gt;
 &lt;li&gt;Mean run duration: &lt;strong&gt;18.43 min&lt;/strong&gt; (range: &lt;strong&gt;8.98–40.72 min&lt;/strong&gt;)&lt;/li&gt;
 &lt;/ul&gt;

 &lt;h2&gt;Data format&lt;/h2&gt;
 &lt;p&gt;Each &lt;code&gt;Data/Run-*.json&lt;/code&gt; file is a &lt;strong&gt;JSON array&lt;/strong&gt;:&lt;/p&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;[
 { "task_id": "...", "task_type": "Read", "main_actor": "Vicon", ... },
 { "task_id": "...", "task_type": "Move", "main_actor": "RADr_3", ... },
 ...
]&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Task record schema (common fields)&lt;/h3&gt;
&lt;p&gt;Task Data Schema that includes the following fields:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;task_id:&lt;/strong&gt; A unique identifier for the task record (UUID).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;name:&lt;/strong&gt; A human-readable task name (e.g., &lt;code&gt;"Wander"&lt;/code&gt;, &lt;code&gt;"Pick"&lt;/code&gt;, &lt;code&gt;"Vicon_ReadValues"&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;task_type:&lt;/strong&gt; The task category/type (one of &lt;code&gt;Read&lt;/code&gt;, &lt;code&gt;Move&lt;/code&gt;, &lt;code&gt;Grip&lt;/code&gt;; plus one &lt;code&gt;Capture&lt;/code&gt; placeholder).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;main_actor:&lt;/strong&gt; The physical actor that executed the task (e.g., &lt;code&gt;Vicon&lt;/code&gt;, &lt;code&gt;RADr_0…RADr_7&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;description:&lt;/strong&gt; Optional description text for the task.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;message:&lt;/strong&gt; A log message associated with the task.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;element_id:&lt;/strong&gt; Optional list of related design element references (empty in these runs).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;job:&lt;/strong&gt; The run identifier associated with this record (e.g., &lt;code&gt;"Run-4"&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;level:&lt;/strong&gt; Process hierarchy level (mostly &lt;code&gt;0&lt;/code&gt; in these logs).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;task_index:&lt;/strong&gt; Task order/index value (&lt;code&gt;null&lt;/code&gt; in these logs).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;actors_data:&lt;/strong&gt; Task input parameters grouped per actor (nested details omitted).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;start_time:&lt;/strong&gt; Task start timestamp (ISO 8601 string) or &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;end_time:&lt;/strong&gt; Task end timestamp (ISO 8601 string) or &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;progress:&lt;/strong&gt; Task state indicator (observed values: &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;response:&lt;/strong&gt; Task output/response payload for the actor (may be &lt;code&gt;null&lt;/code&gt;; nested details omitted).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;project:&lt;/strong&gt; Project identifier (e.g., &lt;code&gt;"col_robots"&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;Task types and their data structures&lt;/h2&gt;

 &lt;h3&gt;1) Vicon — &lt;code&gt;Read&lt;/code&gt;&lt;/h3&gt;
 &lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; request a global environment snapshot from Vicon.&lt;/p&gt;
 &lt;p&gt;Typical fields:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;main_actor&lt;/code&gt;: &lt;code&gt;"Vicon"&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;task_type&lt;/code&gt;: &lt;code&gt;"Read"&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;actors_data&lt;/code&gt;: &lt;code&gt;{"Vicon": {"ReadData": "true"}}&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;response&lt;/code&gt;: &lt;code&gt;{"Vicon": "&lt;JSON string&gt;"}&lt;/code&gt;&lt;/li&gt;
 &lt;/ul&gt;

 &lt;h3&gt;Vicon response payload structure&lt;/h3&gt;
 &lt;p&gt;After &lt;code&gt;json.loads(record["response"]["Vicon"])&lt;/code&gt;, you obtain a dictionary:&lt;/p&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;{
 "RADr_0": [ [ [x,y,z], flag ], [ [qx,qy,qz,qw], flag ] ],
 "RADr_1": [ ... ],
 ...
 "DM0": [ [ [x,y,z], flag ], [ [qx,qy,qz,qw], flag ] ],
 ...
}&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;Where:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;x,y,z&lt;/code&gt; are positions in the Vicon world frame (values suggest &lt;strong&gt;millimetres&lt;/strong&gt;, z ~ 60–130 mm for floor objects).&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;qx,qy,qz,qw&lt;/code&gt; are a quaternion orientation.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;flag&lt;/code&gt; is a Boolean that indicate measurement validity/occlusion (in these logs it is typically &lt;code&gt;false&lt;/code&gt;).&lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;Entity keys include:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;Robots: &lt;code&gt;RADr_0&lt;/code&gt; … &lt;code&gt;RADr_7&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;Digital materials: &lt;code&gt;DM0&lt;/code&gt; … &lt;code&gt;DM24&lt;/code&gt;&lt;/li&gt;
 &lt;/ul&gt;

 &lt;h3&gt;2) RADr — &lt;code&gt;Move&lt;/code&gt; and &lt;code&gt;Grip&lt;/code&gt;&lt;/h3&gt;
 &lt;p&gt;
 RADr tasks control an individual two-wheeled robot (&lt;code&gt;RADr_i&lt;/code&gt;). In this dataset there are two closely
 related task types:
 &lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;Move&lt;/code&gt;&lt;/strong&gt;: send a wheel-motor command sequence.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;Grip&lt;/code&gt;&lt;/strong&gt;: send a wheel-motor command sequence &lt;strong&gt;and&lt;/strong&gt; toggle the magnetic gripper.&lt;/li&gt;
 &lt;/ul&gt;

 &lt;p&gt;Typical fields:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;main_actor&lt;/code&gt;: &lt;code&gt;"RADr_6"&lt;/code&gt; (or any &lt;code&gt;RADr_i&lt;/code&gt;)&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;task_type&lt;/code&gt;: &lt;code&gt;"Move"&lt;/code&gt; or &lt;code&gt;"Grip"&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;actors_data["RADr_i"]["move_vectors"]&lt;/code&gt;: a &lt;em&gt;string&lt;/em&gt; representing a list of 2D wheel-force vectors, one per control step&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;actors_data["RADr_i"]["gripper"]&lt;/code&gt; (&lt;em&gt;only for&lt;/em&gt; &lt;code&gt;Grip&lt;/code&gt;): &lt;code&gt;"True"&lt;/code&gt; / &lt;code&gt;"False"&lt;/code&gt; (stored as a string)&lt;/li&gt;
 &lt;/ul&gt;

 &lt;p&gt;Example &lt;code&gt;actors_data&lt;/code&gt; snippets:&lt;/p&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;// Move
"actors_data": {
 "RADr_6": {
 "move_vectors": "[[-0.1, 0.0], [0.0, 0.65], [0.0, 0.8], [0.0, 1.0]]"
 }
}&lt;/code&gt;&lt;/pre&gt;

 &lt;pre&gt;&lt;code class="language-json"&gt;// Grip
"actors_data": {
 "RADr_6": {
 "gripper": "True",
 "move_vectors": "[[0.0, 0.5], [0.0, 1.0], [0.0, 1.0]]"
 }
}&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;RADr response payload (common to &lt;code&gt;Move&lt;/code&gt; and &lt;code&gt;Grip&lt;/code&gt;)&lt;/h3&gt;
 &lt;p&gt;
 &lt;code&gt;response&lt;/code&gt; typically contains a per-robot entry whose value is a JSON &lt;em&gt;string&lt;/em&gt; that must be parsed
 with &lt;code&gt;json.loads(...)&lt;/code&gt;. After parsing, the response provides an updated local state from onboard sensors:
 &lt;/p&gt;

 &lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;&lt;th&gt;Key&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;&lt;td&gt;&lt;code&gt;Material_Sensor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;bool&lt;/td&gt;&lt;td&gt;Whether a material module is detected nearby.&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;code&gt;Gripper&lt;/code&gt;&lt;/td&gt;&lt;td&gt;bool&lt;/td&gt;&lt;td&gt;Current magnetic gripper state.&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;code&gt;Robot_Sensor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;int&lt;/td&gt;&lt;td&gt;Robot proximity sensor indicator (values observed: -1, 0, …).&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;code&gt;Boundary_Sensor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;bool&lt;/td&gt;&lt;td&gt;Boundary detection (e.g., leaving the work area).&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;code&gt;Counter&lt;/code&gt;&lt;/td&gt;&lt;td&gt;int&lt;/td&gt;&lt;td&gt;A robot-side counter / tick value (useful for debugging timing).&lt;/td&gt;&lt;/tr&gt;
 &lt;/tbody&gt;
 &lt;/table&gt;

 &lt;h2&gt;Derived metrics (as used in &lt;code&gt;Analysis.py&lt;/code&gt;)&lt;/h2&gt;
 &lt;p&gt;The included analysis script derives two useful “DT performance” proxies from the task logs:&lt;/p&gt;

 &lt;h3&gt;Task rate per robot&lt;/h3&gt;
 &lt;p&gt;For each robot and run:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;code&gt;task_rate = (# Move + Grip tasks) / (robot active time)&lt;/code&gt;&lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;
 Active time is computed from the robot’s first task start to its last task end within a run.
 &lt;/p&gt;

 &lt;p&gt;Average task-rate summary (mean ± std across runs):&lt;/p&gt;
 &lt;p&gt;Overall mean across robots (mean of robot means): &lt;strong&gt;8.44 tasks/min&lt;/strong&gt;.&lt;/p&gt;

 &lt;h3&gt;Approximate per-robot control-loop time&lt;/h3&gt;
 &lt;p&gt;For each robot, an “ABM-like loop time” is approximated as:&lt;/p&gt;
 &lt;p&gt;&lt;code&gt;loop_time_s = start_time(next task) - end_time(current task)&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;This captures the combined effects of:&lt;/p&gt;
 &lt;ul&gt;
 &lt;li&gt;participant logic / ABM compute time,&lt;/li&gt;
 &lt;li&gt;DT engine scheduling,&lt;/li&gt;
 &lt;li&gt;communication latency,&lt;/li&gt;
 &lt;li&gt;and any short pauses between tasks.&lt;/li&gt;
 &lt;/ul&gt;
 &lt;p&gt;Overall mean (after outlier removal): &lt;strong&gt;0.66 s&lt;/strong&gt;.&lt;/p&gt;

 &lt;h2&gt;Usage&lt;/h2&gt;

 &lt;h3&gt;1) Load the dataset in Python&lt;/h3&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import json
import pandas as pd

with open("Data/Run-1.json", "r") as f:
 tasks = json.load(f)

df = pd.DataFrame(tasks)
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;2) Reproduce the included figures&lt;/h3&gt;
 &lt;p&gt;Install dependencies:&lt;/p&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;pip install numpy pandas matplotlib&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Run:&lt;/p&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;python Analysis.py&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;The script reads all files in &lt;code&gt;Data/&lt;/code&gt; and writes plots to &lt;code&gt;Figures/&lt;/code&gt;.&lt;/p&gt;

 &lt;h3&gt;3) Replay in Grasshopper (Rhino)&lt;/h3&gt;
 &lt;p&gt;
 &lt;code&gt;Replay_Run.gh&lt;/code&gt; is a Grasshopper definition intended to visualise a run by reading Vicon snapshots
 from the task logs and animating robot/material poses. Open it in Rhino/Grasshopper and ensure the
 &lt;code&gt;Data/&lt;/code&gt; folder path is correctly set inside the definition.
 &lt;/p&gt;
 &lt;p class="muted"&gt;
 (&lt;em&gt;Exact inputs/parameters depend on your Grasshopper environment; this repository does not include a
 &lt;code&gt;*.ghx&lt;/code&gt; parameter manifest.&lt;/em&gt;)
 &lt;/p&gt;

 &lt;h2&gt;Notes and known quirks&lt;/h2&gt;
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Timestamps:&lt;/strong&gt; &lt;code&gt;start_time&lt;/code&gt;/&lt;code&gt;end_time&lt;/code&gt; are ISO 8601 strings; a small fraction of records may contain &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;

 &lt;li&gt;&lt;strong&gt;Units:&lt;/strong&gt; Vicon positions are presented in &lt;strong&gt;millimetres&lt;/strong&gt; in a fixed world frame (based on magnitude vs. the physical workspace size).&lt;/li&gt;

 &lt;/ul&gt;

 &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;