Data Model¶
This page describes every data entity in XRXP, how they relate to each other, and what they are used for.
Overview¶
Experiment
└── Session ←── User (participates with a role via comments)
├── Environment (properties describing the application/setup)
├── Sub-session (nested session for individual exercises)
├── Log Event (actor - verb - object, with properties)
├── Media Event (files: images, audio, video, documents)
├── Question (label - answer, with properties)
└── Internal System (signal tracking over time)
└── Internal Event (one record at a specific timestamp)
└── System Type (format of the value: WorldPosition, QuantitativeValue)
All recorded entities are sent as traces through the storage layer. Each trace has:
Id— unique identifier (UUID)Protocol— the entity type name (e.g."Session","LogEvent","InternalEvent")
User¶
A User represents a participant. Users are generic: you can attach any key-value properties to them.
Each user participates in a session. The user's role in the session is described in the comments field of the session participation.
A user is created automatically when you call StartSession or JoinSession. If you provide a userId, that ID is used; otherwise a UUID is generated.
Trace format¶
Adding properties¶
XRXPManager.Recorder.AddUserProperties(new Dictionary<string, string> {
{ "age", "25" },
{ "experience_level", "beginner" },
{ "dominant_hand", "right" },
{ "group", "control" }
});
Each property is sent as a separate UserProperty trace:
Session¶
The Session is the center of the data model. A session represents one recorded run of an experience.
A session:
- belongs to one Experiment (identified by
ExperimentId) - has one User who participates in it (with a role described in
Comments) - has one Environment describing the application configuration
- can have a parent session (sub-sessions)
Sub-sessions¶
Sessions can be nested. When you start a new session while one is already active, the new session becomes a sub-session of the current one.
This is useful when:
- the top-level session represents the overall participation of a user
- sub-sessions represent individual exercises or tasks the participant performs
// Overall participation session
string mainSession = XRXPManager.Recorder.StartSession(
comments: "Full experiment participation",
userId: "participant-001"
);
// Exercise 1 (sub-session of the main session)
string exercise1 = XRXPManager.Recorder.StartSession(
comments: "Exercise 1 - Navigation task"
);
// ... record events for exercise 1 ...
XRXPManager.Recorder.StopSession(); // stops exercise 1
// Exercise 2 (another sub-session)
string exercise2 = XRXPManager.Recorder.StartSession(
comments: "Exercise 2 - Object manipulation"
);
// ... record events for exercise 2 ...
XRXPManager.Recorder.StopSession(); // stops exercise 2
// End the overall session
XRXPManager.Recorder.StopSession(); // stops main session
Multi-user sessions¶
Multiple users can participate in the same session. The first user creates the session with StartSession, others join it with JoinSession:
// Host creates the session
string sessionId = XRXPManager.Recorder.StartSession(
comments: "Surgeon role",
userId: "surgeon-001"
);
// On another device, a second user joins
XRXPManager.Recorder.JoinSession(
sessionId: sessionId,
comments: "Assistant role",
userId: "assistant-001"
);
Trace format¶
{
"Protocol": "Session",
"Id": "550e8400-e29b-41d4-a716-446655440000",
"ExperimentId": "VR_Surgery_Training",
"UserId": "surgeon-001",
"StartDate": 1710507600000,
"EndDate": 0,
"Comments": "Surgeon role",
"EnvironmentId": "env-uuid",
"Parent": null
}
When stopped, the session is re-sent with Protocol: "Session_updateenddate" and a filled EndDate.
Environment¶
An Environment describes the application or setup where the session takes place. This includes anything that characterizes the conditions of the experiment:
- application version
- configuration parameters
- difficulty level
- scene name
- room setup
- hardware configuration
An environment is created automatically when a session starts. You can pass initial properties via StartSession, or add them later.
Example¶
// At session start
XRXPManager.Recorder.StartSession(
comments: "Participant 001",
environmentProperties: new Dictionary<string, string> {
{ "application_version", "2.1.0" },
{ "difficulty", "hard" },
{ "scene", "OperatingRoom" },
{ "haptic_feedback", "enabled" }
}
);
// Add more properties later
XRXPManager.Recorder.AddEnvironmentProperties(new Dictionary<string, string> {
{ "lighting_condition", "dim" },
{ "noise_level", "low" }
});
Trace format¶
Each property is a separate EnvironmentProperty trace:
{
"Protocol": "EnvironmentProperty",
"Id": "...",
"Property": "difficulty",
"Value": "hard",
"EnvironmentId": "env-uuid"
}
Log Event¶
A Log Event records a discrete action using the Actor - Verb - Object pattern, with optional key-value properties.
Log events are related to a session and a user.
How to think about it¶
| Field | Meaning | Example |
|---|---|---|
| Actor | Who or what performed the action | "Bob", "User", "System", "NPC_Guide" |
| Verb | The action performed | "eats", "clicked", "grabbed", "entered" |
| Object | The target of the action | "an apple", "StartButton", "RedCube", "Zone_A" |
| Properties | Additional context | { "color": "red", "hand": "right" } |
Examples¶
// Simple action
XRXPManager.Recorder.AddLogEvent("User", "clicked", "StartButton");
// With properties
XRXPManager.Recorder.AddLogEvent(
"Bob", "eats", "apple",
new Dictionary<string, string> {
{ "apple_color", "red" },
{ "location", "kitchen" }
}
);
// System event
XRXPManager.Recorder.AddLogEvent(
"System", "loaded", "Level_3",
new Dictionary<string, string> {
{ "load_time_ms", "1250" }
}
);
// Task milestone
XRXPManager.Recorder.AddLogEvent(
"User", "completed", "NavigationTask",
new Dictionary<string, string> {
{ "errors", "2" },
{ "time_seconds", "45.3" }
}
);
Trace format¶
{
"Protocol": "LogEvent",
"Id": "...",
"Time": 1710507612345,
"Actor": "Bob",
"Verb": "eats",
"Object": "apple",
"Duration": null,
"UserId": "user-042",
"SessionId": "session-uuid"
}
Each property is a separate LogEventProperty trace:
{
"Protocol": "LogEventProperty",
"Id": "...",
"Property": "apple_color",
"Value": "red",
"LogEventId": "logevent-uuid"
}
Media Event¶
A Media Event records a file produced during the experiment. This can be any document type: images, audio recordings, video captures, PDFs, or other files.
Media events are related to a session and a user.
Examples¶
// A photo the participant took during a task
XRXPManager.Recorder.AddMediaEvent(
filePath: "/storage/photos/task1_capture.png",
mimeType: "image/png",
name: "Task1_Photo",
duration: 0
);
// An audio recording
XRXPManager.Recorder.AddMediaEvent(
filePath: "/storage/audio/participant_response.wav",
mimeType: "audio/wav",
name: "Verbal_Response_Q3",
duration: 12000 // 12 seconds in ms
);
// From a byte array (auto-saved to disk and uploaded to file server)
byte[] screenshot = CaptureScreenshot();
XRXPManager.Recorder.AddMediaEvent(
bytes: screenshot,
mimeType: "image/png",
name: "Environment_Overview",
duration: 0 // optional, in ms for audio/video
);
Trace format¶
{
"Protocol": "MediaEvent",
"Id": "...",
"Time": 1710507612345,
"MimeType": "image/png",
"Name": "Task1_Photo",
"Duration": 0,
"UserId": "user-042",
"SessionId": "session-uuid"
}
Internal and External Systems¶
Systems describe signals in any format that you want to track through time. They are related to a session and a user.
There are two kinds:
| Kind | Meaning | Examples |
|---|---|---|
| Internal system | Integrated in the application where the participant is | Body position tracking, object position, average speed in VR, gaze direction |
| External system | Data produced by a device not integrated into the application | Heart rate monitor, EEG headset, galvanic skin response sensor |
The Unity SDK currently creates InternalSystem traces. External systems follow the same data structure but are typically ingested through other means (API, import).
How systems work¶
A system is a named container that groups many events over time. For example:
- System
"BodyTracking"with typeWorldPositiontracks body parts — the events under it record the position of"Head","LeftHand","RightHand"at each timestamp - System
"SpeedMonitor"with typeQuantitativeValuetracks a numeric signal — the events record the speed value at each timestamp - System
"HeartRateTracker"(external) tracks heartbeat — events record BPM at each timestamp
Trace format¶
{
"Protocol": "InternalSystem",
"Id": "system-uuid",
"Name": "BodyTracking",
"SystemTypeName": "WorldPosition",
"UserId": "user-042",
"SessionId": "session-uuid"
}
System Type¶
The System Type describes the format of the recorded values. It is set when the system is created.
| System Type | Description | Use cases |
|---|---|---|
WorldPosition | A 3D position (Vector3) and rotation (Quaternion) | Object tracking, head tracking, hand tracking, gaze target |
QuantitativeValue | A numeric value with a unit | Heart rate, speed, score, distance, temperature |
The value is serialized to JSON via the Jsonable interface. WorldPosition is provided by the SDK. For QuantitativeValue, implement your own Jsonable class (see Tracking data).
Internal Event and External Event¶
Events are individual records at a specific timestamp, linked to a system. They are the actual data points.
For example, with a "BodyTracking" system:
- At time T1:
property: "Head",value: { position: (0,1.7,0), rotation: (0,0,0,1) } - At time T1:
property: "LeftHand",value: { position: (-0.3,1.0,0.5), rotation: ... } - At time T1:
property: "RightHand",value: { position: (0.3,1.0,0.5), rotation: ... } - At time T2:
property: "Head",value: { position: (0.1,1.7,0.2), rotation: ... } - ...
One system tracks all related properties. The property field distinguishes what is being measured within that system.
Code example¶
using XRXP.Recorder.Models;
// Track body position — all events go under the same "BodyTracking" system
XRXPManager.Recorder.AddInternalEvent(
SystemType.WorldPosition, "BodyTracking", "Head",
new WorldPosition(head.position, head.rotation)
);
XRXPManager.Recorder.AddInternalEvent(
SystemType.WorldPosition, "BodyTracking", "LeftHand",
new WorldPosition(leftHand.position, leftHand.rotation)
);
XRXPManager.Recorder.AddInternalEvent(
SystemType.WorldPosition, "BodyTracking", "RightHand",
new WorldPosition(rightHand.position, rightHand.rotation)
);
Trace format¶
{
"Protocol": "InternalEvent",
"Id": "...",
"Time": 1710507612345,
"Property": "Head",
"Value": "{\"Position\":{\"x\":0.0,\"y\":1.7,\"z\":0.0},\"Rotation\":{\"x\":0.0,\"y\":0.0,\"z\":0.0,\"w\":1.0}}",
"InternalSystemId": "system-uuid"
}
Transformation Type¶
The Transformation Type is the XRXP data versioning system. It allows stacking multiple versions of data recordings and their transformations in the same database.
| Transformation | When it appears | Meaning |
|---|---|---|
raw | During recording with the Unity SDK | The original data as captured |
resampled | During data analysis | Data that has been resampled to a uniform time interval |
| Custom | During data analysis | Any transformation applied during post-processing (filtering, interpolation, normalization, etc.) |
This means the same system events can exist in multiple versions:
- The raw version is what the Unity SDK recorded in real-time
- A resampled version may be created during analysis, where events are interpolated to fixed intervals (e.g. every 10ms)
- Other transformations can be added as needed (e.g. smoothed, filtered, normalized)
This versioning allows researchers to keep the original data intact while working with processed versions.
Complete example scenario¶
A VR surgical training experiment:
using XRXP;
using XRXP.Recorder.Models;
using System.Collections.Generic;
public class SurgeryExperiment : UnityEngine.MonoBehaviour
{
void Start()
{
// 1. Start the overall participation session
string mainSession = XRXPManager.Recorder.StartSession(
comments: "Surgeon trainee",
userId: "trainee-007",
environmentProperties: new Dictionary<string, string> {
{ "application_version", "3.0.1" },
{ "scenario", "appendectomy" },
{ "difficulty", "intermediate" },
{ "haptic_feedback", "enabled" }
}
);
// 2. Add user properties
XRXPManager.Recorder.AddUserProperties(new Dictionary<string, string> {
{ "experience_years", "2" },
{ "dominant_hand", "right" },
{ "group", "experimental" }
});
// 3. Start first exercise (sub-session)
XRXPManager.Recorder.StartSession(comments: "Exercise 1 - Incision");
}
void LateUpdate()
{
if (!XRXPManager.IsReady || !XRXPManager.Recorder.IsRecording()) return;
// 4. Track tool position (internal system events)
XRXPManager.Recorder.AddInternalEvent(
SystemType.WorldPosition, "ToolTracking", "Scalpel",
new WorldPosition(scalpel.position, scalpel.rotation)
);
}
public void OnIncisionMade()
{
// 5. Log a discrete event
XRXPManager.Recorder.AddLogEvent(
"User", "performed", "incision",
new Dictionary<string, string> {
{ "accuracy_mm", "2.3" },
{ "tissue_layer", "dermis" }
}
);
}
public void OnScreenshot(byte[] imageData)
{
// 6. Record a media event
XRXPManager.Recorder.AddMediaEvent(
imageData, "image/png", "Incision_Result"
);
}
public void OnExerciseComplete()
{
// 7. Record a questionnaire answer
XRXPManager.Recorder.AddQuestion(
"Confidence_Level", "4",
new Dictionary<string, string> { { "scale", "1-5" } }
);
// 8. Stop exercise sub-session, start next one
XRXPManager.Recorder.StopSession();
XRXPManager.Recorder.StartSession(comments: "Exercise 2 - Suturing");
}
void OnApplicationQuit()
{
// 9. Clean up
while (XRXPManager.Recorder.IsRecording())
XRXPManager.Recorder.StopSession();
XRXPManager.Recorder.EndTracing();
}
}
Entity relationship summary¶
| Entity | Related to | Relationship |
|---|---|---|
| Session | Experiment, User, Environment, Parent Session | A session belongs to one experiment, one user, one environment, and optionally one parent session |
| User | Sessions | A user participates in one or many sessions |
| Environment | Session | An environment is created per session to describe conditions |
| Log Event | Session, User | Records a discrete action |
| Media Event | Session, User | Records a file produced during the experiment |
| Internal System | Session, User, System Type | Groups signal events over time |
| Internal Event | Internal System | One data point at a specific time |
| User Property | User | Key-value metadata about the user |
| Environment Property | Environment | Key-value metadata about the environment |
| Log Event Property | Log Event | Key-value metadata about the event |
| Question | Session | A question-answer pair |
| Question Property | Question | Key-value metadata about the question |