FAQ & pitfalls
The questions that actually come up — with concrete answers. If your issue isn't here, check the relevant stream page's Gotchas section first; most surprises are covered there.
My CSV is missing entirely
Why are NO files appearing when I'm running in the editor?
The master DataDirectoryUtility.SaveData gate is off. In editor, this requires:
- The application state to be running (not
None/View), AND saveSettings.saveTestDatato beOn.
In builds, SaveData is always true. So if you're testing in editor and see nothing on disk, check the editor save settings first before debugging individual writers.
Why does BodyTracking.csv / FaceTracking.csv / LeftHandTracking.csv not exist?
The stream was gated out at startup, OR it was enabled but never produced a row (writers create files lazily on first emission — see File layout → Lazy file creation). Check the gate first:
| Stream | Gate |
|---|---|
| Body | ExperienceUtilities.CaptureBodyData enabled and a SilicoSkeletonBody assigned |
| Face | ExperienceUtilities.CaptureFaceData enabled and a SilicoSkeletonFace assigned |
| Hand (per side) | Hand capture enabled, skeleton assigned, skeleton initialized == true |
| Eye | Eye capture enabled, eyes tracked, head skeleton assigned |
See File layout → Gated streams.
Why does EventPoints.csv / Variables.csv / etc. not exist when no triggers fired?
Lazy file creation. SessionBoundCsvWriterBase opens the file on the first WriteDataRow() call — not on run-init. An irregular stream that never had a trigger fire (no events, no variable writes, no key presses, etc.) leaves no file behind. Empty CSVs do not exist by design.
Why does AgentsData.csv not exist?
USING_AGENTS scripting define isn't active in the LABO package compilation. Without that define, the agent writer isn't built at all.
Why does InputCapture.csv exist but have zero rows for the key I pressed?
The key isn't in the KeyCodeDataCapture SO's keyCodes list. Only configured keys are logged. Add the key to the SO, rebuild, re-run.
My CSV has gaps
Why do consecutive rows jump multiple frame numbers?
Skip > 0on the capture SO — the writer is sampling every Nth update.Skip = 1means every other update, soFrameNumberadvances by 2 between rows.- Distance / rotation thresholds on Participant tracking — rows only emit when the participant moved far enough. Stationary frames produce no row.
- Focus loss with Run In Background off — when the app loses focus,
Time.timestops advancing and capture halts. See below.
Why does my CSV stop in the middle of a session?
The application lost focus and Run In Background is off. Time effectively stops; the CSV ends cleanly and resumes when focus returns. Turn on Run In Background if you need continuous capture while unfocused — pacing becomes irregular but rows keep appearing.
Why are my last few rows missing?
The app was force-quit before the writer flushed. Writes are batched and flushed every N rows; a crash can lose the most recent unflushed batch. Closing cleanly (run ends normally, editor exits play mode) flushes everything.
My timestamps look wrong
Why are multiple rows in different files showing the same MonotonicExecutionTime?
Expected, for regular streams. MonotonicExecutionTime is cached once per frame for regular streams — all regular rows emitted in the same frame share an identical value. See Timing → When is MonotonicExecutionTime captured.
Why is an irregular row's MonotonicExecutionTime a millisecond earlier than the ExperienceState row with the same FrameNumber?
Also expected. Irregular streams stamp at trigger time (the moment the event fired). ExperienceState's regular row for that frame is written later during LateUpdate, with a time cached at top-of-frame. The small gap is real — it reflects the trigger happening inside the frame before LateUpdate ran. Both rows share the same FrameNumber, so joins work correctly.
Why does my ObservationTime drift from MonotonicExecutionTime over long sessions?
ObservationTime comes from Unity's clock, which can be affected by Time.timeScale changes and float-precision decay in very long sessions. MonotonicExecutionTime comes from Stopwatch — drift-free and unaffected by those. Use MonotonicExecutionTime for analysis. See Timing.
How do I translate a CSV row's time into "when the participant saw it"?
Add the display offset:
display_time ≈ cpu_time + maxQueuedFrames × (1 / refresh)
The offset is constant per session as long as V Sync and Max Queued don't change. See Timing → Display-time correction formula.
Joins aren't working
Why does my merge_asof pair a LABO frame with an external sample from a different frame?
Your tolerance is too wide. Default rule: tolerance = half the faster device's sample interval. At 90 Hz LABO + 1000 Hz EEG, use tolerance=0.0005 (0.5 ms). See Cross-device sync.
Why does an inner join on FrameNumber drop most of my data?
One of the streams is gated or threshold-filtered and doesn't have rows for every frame. Use how="left" from ExperienceState (the dense reference) to the sparse stream instead. See Join keys → Gotchas.
Which file is the "authoritative timeline"?
ExperienceState.csv. Always. Everything else joins to it.
My columns are weird
Why are the columns in two runs' BodyTracking.csv different?
The active skeleton differed. Body / Face / Hand column sets are generated from the assigned skeleton at run-start. Two runs with different skeletons = different headers. Scripts that hard-code column names need to handle missing / extra columns gracefully.
Why is the rotation quaternion flipping between adjacent rows?
Quaternion sign ambiguity — q and -q represent the same rotation, but the CSV writer doesn't normalise. A near-stationary pose can show RotW jumping from e.g. 0.99 → -0.99 with no real rotation happening. For continuous analysis, normalise to a canonical hemisphere (q if q.w >= 0 else -q) or convert to axis-angle first.
Why are list values separated by semicolons instead of commas?
CSV-safety. Commas inside list / string values are replaced with semicolons at write time to preserve the CSV structure. See Column conventions → Value encoding.
Process / workflow
Should I commit my run data to git?
No. Run directories are per-session output, not source. Keep them out of the repo (.gitignore).
How do I re-run an analysis on a session after modifying the experience?
The session's CSVs are frozen — modifying the experience doesn't change them. To re-run with new parameters, either re-record a session (preferred) or process the existing CSVs with new analysis code.
Can I merge runs from multiple participants into one analysis?
Yes, but keep them as separate DataFrames and identify them by a participant-id column you add yourself before concatenating. Don't conflate MonotonicExecutionTime across runs — each run's clock starts at 0, so cross-run absolute times are only meaningful as within-run offsets.
How do I know what LABO package version recorded a given run?
Currently, you don't — runs don't embed package version metadata. If you need this for reproducibility, record the package version as an application-level variable write (via Variables) at session start.
Still stuck?
- Check the relevant stream page's Gotchas section.
- Read the writer's source file —
Runtime/DataCapture/DataWriters/*.csin the LABO package. Writer source is the canonical answer to "what actually gets written." - Check
RuntimeLoopInfoPopup.cs— in-editor popup covering timing, streams, and presets in plain language.