Events
Irregular • File:
EventPoints.csv• Writer:LaboEventPointDataWriter.cs
At a glance
EventPoints is the authoritative log of experimental events — everything that increments the global EventNumber or marks an epoch transition. Every event is stamped at trigger time with the exact frame and MonotonicExecutionTime, so the row reflects the real moment the event fired, not when the writer got around to serialising it.
Think of EventPoints as the answer to "when and why did things happen?" Every EventNumber change you see in other CSVs has a corresponding row in this file explaining what that number means.
When it writes
A row is emitted whenever external code calls LaboDataCapture.Instance.EventEntry(observation). Internally that calls the writer's RecordEntry, and the observation is stamped at construction time (via LaboObservation.Stamp()) with the current frame / time / epoch context.
Triggers include:
- Event-point firing — an
EventPointobject in the experience fires. - Action execution — an action bound to an event runs.
- Response generation — responses generated by the experience (prompts, stimuli).
- Epoch lifecycle transitions — epoch start, epoch end.
Every event can have child observations via the entry.children chain. The writer flattens these — one row per child — so a parent event with three children produces four rows in the file.
File & location
- Default:
EventPoints.csvin the run directory. - Writer:
LaboEventPointDataWriter.cs. - Observation:
Runtime/DataCapture/Observations/EventPointObservation.cs.
Configuration
EventPointsDataCapture SO (subclass of DataCaptureConfig). Fields:
| Field | What it does |
|---|---|
enabledLogging | Master enable/disable. |
fileName | Override the default name. |
flushEveryNRows | Batched flush cadence. Default 30. |
writeHeader | Write the header row on first emission. Default true. |
Auto-created on experience open. WindowUtility.OpenExperience() populates this slot if missing — no need to create it manually before first use. To find trigger sites in code, grep for LaboDataCapture.Instance.EventEntry.
Columns
Shared prefix
See Column conventions — The shared prefix. The shared columns are captured by Stamp() at trigger time, so FrameNumber / MonotonicExecutionTime reflect the real moment the event fired.
Domain columns
| Column | Type | Description |
|---|---|---|
Event_Type | string | What kind of event — EpochStart, EpochEnd, Response, EventPoint, etc. |
Event_Description | string | Short description set on the event (from the event-point or epoch config). |
Event_Note | string | Additional free-text note on the event itself. |
Observation_Note | string | Free-text note specific to this observation (separate from Event_Note). |
Event_Source | string | What object / entity produced the event (e.g. Epoch, EventPoint, Interactive, Response). |
Event_Source_Parent | string | The parent of the source if relevant (e.g. the Interactive's parent, the Epoch the EventPoint belongs to). |
Event_Trigger | string | What caused it (Timer, UserInput, Collision, Manual, etc.). |
Action_Number | int | Index of the action fired, if the event involves an action. 0 if not action-related. |
Action_Type | string | Type of action (Visual, Audio, Haptic, Undefined, etc.). |
Sample rows
...shared...,Event_Type,Event_Description,Event_Note,Observation_Note,Event_Source,Event_Source_Parent,Event_Trigger,Action_Number,Action_Type
...,EpochStart,Baseline epoch begins,,,Epoch,,,0,Undefined
...,Response,Stimulus flash,user_note_example,obs_note,EventPoint,Interactive,Timer,3,Visual
Join with other streams
Every row already carries the shared prefix, so it's natively joinable to anything else on FrameNumber. The most common pattern is attaching the per-frame state context to each event:
import pandas as pd
exp = pd.read_csv("ExperienceState.csv")
events = pd.read_csv("EventPoints.csv")
enriched = events.merge(exp, on="FrameNumber", how="left", suffixes=("_event", "_frame"))
# Flag events that fired during slow frames:
slow_events = enriched[enriched["deltaTime"] > 0.025]
Or bucket a continuous stream by event:
eye = pd.read_csv("EyeTracking.csv")
# For each eye-tracking sample, find the most recent event before it:
eye_sorted = eye.sort_values("MonotonicExecutionTime")
events_sorted = events.sort_values("MonotonicExecutionTime")
bucketed = pd.merge_asof(eye_sorted, events_sorted, on="MonotonicExecutionTime", direction="backward")
Gotchas
- Children are flattened. A single source event with three child observations produces four rows (parent + three children). Don't treat row count as a unique-event count without deduplicating on
(FrameNumber, Event_Source, Event_Description)first — or use the explicitEventNumberfrom the shared prefix. Event_Sourcevs.Event_Source_Parentis not always intuitive. An interactive object that fires an event through a response hasEvent_Source = EventPointandEvent_Source_Parent = Interactive. Check the writer'sGetColumnName-equivalent logic if you need exact source mapping.EventNumberin the shared prefix is not monotonic-per-row. It's the latest fired event number at trigger time. Two rows with the sameEventNumbercan exist (parent + children), orEventNumbercan stay flat across several rows if none of them incremented the global counter.- Stamped at trigger time, not write time. The
MonotonicExecutionTimein an EventPoints row is the real event moment, accurate to microseconds. An ExperienceState row for the sameFrameNumberwill have a slightly laterMonotonicExecutionTime(cached at top-of-frame, written at LateUpdate). This is real, not noise. - Commas in free-text notes are replaced with semicolons. See Column conventions.
Analysis recipes
- Reaction time — pair an event row with a subsequent Input key-press row.
- Stimulus-locked averaging — use EventPoints to define onset times; epoch-align other streams around them.