Skip to content

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

{
  "Protocol": "User",
  "Id": "user-042"
}

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:

{
  "Protocol": "UserProperty",
  "Id": "...",
  "Property": "age",
  "Value": "25",
  "UserId": "user-042"
}

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

{
  "Protocol": "Environment",
  "Id": "env-uuid"
}

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 type WorldPosition tracks body parts — the events under it record the position of "Head", "LeftHand", "RightHand" at each timestamp
  • System "SpeedMonitor" with type QuantitativeValue tracks 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