ExperienceState (per-frame timeline)
Regular • Files:
UpdateLoop.csvand/orFixedInterval.csv• Writer:LaboExperienceStateDataWriter.cs• Configs:UpdateLoopDataCapture+FixedIntervalDataCapture
At a glance
The authoritative per-frame timeline for the session — one row per sampled frame, containing the shared LaboExperienceState columns plus a few frame-timing extras. Every other CSV from the same run can be aligned to it on FrameNumber or MonotonicExecutionTime. If you're doing any analysis that spans multiple streams, this is where you start.
The same writer class produces two files via two configs:
UpdateLoop.csv— one row perLateUpdate(render frame). Driven byUpdateLoopDataCapture.FixedInterval.csv— one row perFixedUpdate(physics tick). Driven byFixedIntervalDataCapture.
Each config is independent. You can enable one, the other, or both. Both files have the same column shape.
When it writes
Each writer instance samples its loop:
UpdateLoop.csvwriter samples inLateUpdate.FixedInterval.csvwriter samples inFixedUpdate.
Sampling honors:
skip— record every (skip + 1)th sample.0writes every sample.captureHitches— emit an extra row whenever the frame delta exceedshitchThresholdSeconds.
File & location
UpdateLoop.csv— default name; override viaUpdateLoopDataCapture.fileName.FixedInterval.csv— default name; override viaFixedIntervalDataCapture.fileName.- Both files are created on first row write — see the lifecycle note in File layout.
- Both configs are auto-created on experience open if missing — see Configuration below.
Configuration
UpdateLoopDataCapture and FixedIntervalDataCapture are both subclasses of LoopDataCapture, which inherits from DataCaptureConfig. They share the same field set:
| Field | Where it lives | What it does |
|---|---|---|
enabledLogging | DataCaptureConfig | Master enable/disable. If false, no file is created. |
fileName | DataCaptureConfig | Override the default filename. Blank → use the default. |
flushEveryNRows | DataCaptureConfig | Batched flush cadence. Default 30. |
writeHeader | DataCaptureConfig | Write the header row on first emission. Default true. |
skip | LoopDataCapture | Record every (skip + 1)th sample. 0 = every sample. |
captureHitches | LoopDataCapture | Emit an extra row on slow frames. Default false. |
hitchThresholdSeconds | LoopDataCapture | Slow-frame threshold. Default 0.033 (≈30 fps). |
Auto-create: not for these two — they're created when the experience is set up, not on demand. Create them manually in the editor if missing.
Columns
Shared prefix
See Column conventions — The shared prefix. In order: ObservationTime, MonotonicExecutionTime, FixedIntervalNumber, FrameNumber, EventNumber, EpochNumber, EpochName, FrameTime, FixedIntervalTime (plus the three *LSL columns when USING_LSL is defined).
Frame-timing columns
Appended after the shared prefix:
| Column | Type | Units | Description |
|---|---|---|---|
deltaTime | float | seconds | Time.deltaTime — scaled delta for this frame. Affected by Time.timeScale. |
unscaledDeltaTime | float | seconds | Time.unscaledDeltaTime — real wall-clock delta. Use for true frame timing. |
fps | float | Hz | 1.0 / unscaledDeltaTime. Convenience column. |
smoothDeltaTime | float | seconds | Time.smoothDeltaTime — Unity's smoothed estimate. |
Sample rows
ObservationTime,MonotonicExecutionTime,FixedIntervalNumber,FrameNumber,EventNumber,EpochNumber,EpochName,FrameTime,FixedIntervalTime,deltaTime,unscaledDeltaTime,fps,smoothDeltaTime,
12.3451,12.3451,612,745,0,1,Baseline,12.3450,12.3400,0.01111,0.01111,90.0,0.01111,
12.3562,12.3562,613,746,0,1,Baseline,12.3561,12.3512,0.01111,0.01111,90.0,0.01111,
12.3673,12.3673,614,747,1,1,Baseline,12.3672,12.3623,0.01111,0.01111,90.0,0.01111,
Note the jump in EventNumber between frames 746 and 747 — an event fired between those LateUpdates. Every irregular-stream row with EventNumber = 1 and matching FrameNumber = 747 aligns to this row.
Join with other streams
UpdateLoop.csv is the join target for any other regular or irregular stream sampled in the render-frame world. Join on FrameNumber:
import pandas as pd
exp = pd.read_csv("UpdateLoop.csv")
eye = pd.read_csv("EyeTracking.csv")
joined = exp.merge(eye, on="FrameNumber", how="inner", suffixes=("_exp", "_eye"))
# Now every eye-tracking sample has the session's epoch context attached:
joined.groupby("EpochName")["Eye_Center_DirZ"].mean()
For irregular streams, left-join them to UpdateLoop.csv to attach per-frame context (fps, deltaTime) to each trigger event:
events = pd.read_csv("EventPoints.csv")
enriched = events.merge(exp, on="FrameNumber", how="left", suffixes=("_event", "_frame"))
slow_events = enriched[enriched["deltaTime"] > 0.025] # events that fired during slow frames
For physics-loop analyses, use FixedInterval.csv and join on FixedIntervalNumber instead. See Join keys for the full pattern library.
Gotchas
- Two files, two clocks.
UpdateLoop.csvadvances on render frames;FixedInterval.csvon physics ticks. Don't merge them on each other — merge each to the irregular stream they share a clock with. skip > 0means gaps. Withskip = 1you only get every other sample, so consecutive rows may haveFrameNumberdiffering by 2, 3, or more. Joins still work, butFrameNumberdiffs in your analysis can't be assumed to be 1.deltaTimevs.unscaledDeltaTime. If your experience usesTime.timeScale(for slow-motion or pauses),deltaTimeis scaled andunscaledDeltaTimeis real. UseunscaledDeltaTimefor true frame timing andMonotonicExecutionTimefor cross-row temporal math.- Hitch rows are interleaved, not separate. Extra rows emitted by
captureHitchesgo into the same file, in order. To find them in analysis, filter onunscaledDeltaTime > hitchThresholdSeconds. fpsis derived, not measured independently. It's1.0 / unscaledDeltaTimefor that frame. For a smoother rate estimate, roll your own fromunscaledDeltaTimeover a window.- Files only appear after the first row.
SessionBoundCsvWriterBaseis lazy — if a writer is enabled but never produces a row, no CSV is created. See File layout.
Analysis recipes
- Stimulus-locked averaging — uses
EventNumberto define stimulus-onset frames. - Cross-device sync — uses
MonotonicExecutionTimeas the reference clock for aligning external devices. - Reaction time — pairs an event with a key press in
InputCapture.csv.