LUIDA Docs

Conditions, sessions, participants

How LUIDA tracks participants, gates sessions, and assigns conditions.

Three terms that LUIDA uses with specific meanings, and that researchers sometimes interchange: participant, session, condition. This page describes what each one means in LUIDA's runtime, and walks through the session-start handshake that ties them together.

Definitions

  • Participant — one human user who joins a Cluster instance to participate in an experiment. Identified by an IDFC (Cluster's per-user identifier; opaque to you), and re-identified across sessions if the same user joins again. Participants are 1-indexed in LUIDA: PARTICIPANTS[1], PARTICIPANTS[2], etc.
  • Session — one execution of an experiment. Begins when enough participants are present to start (the pNum threshold) and the experiment world has finished initializing; ends when the state machine reaches End. Identified by a sID (session ID, generated when the session starts).
  • Condition — the combination of variable values active for a session (between-subjects vars) and trial (within-subjects vars). Stored as a JSON object keyed by variable name.

A single-participant experiment has 1 participant per session, so for those participant and session are roughly synonymous from a data-analysis perspective. A multi-participant experiment has 2+ per session — for example, two participants in a paired conversation study — and the distinction matters: there's one session, but multiple participants in it, all sharing the same between-subjects condition.

How a session starts

When the first participant enters your Experiment World instance, LUIDA does a multi-step handshake:


sequenceDiagram
autonumber
participant P as Participant N
participant W as Experiment World<br/>(Cluster instance)
participant B as Web Console
participant DB as MongoDB

P->>W: enters world
W->>B: callExternal: checkJoinEligibility<br/>{eID, sID?, envInfo}
B->>DB: lookup experiment + recent sessions
alt session full or experiment over capacity
  B-->>W: { eligible: false, reason: "..." }
  W->>P: teleport to gate world (rejection)
else first eligible joiner
  B-->>W: { eligible: true, sID: <new>, existingConditions: [...] }
  W->>W: ConditionManager.assignBetweenSubjects(<br/>existingConditions)
  W->>B: callExternal: saveSessionConditions<br/>{eID, sID, betweenSubjectsConditions}
  W->>P: ready
else later eligible joiner
  B-->>W: { eligible: true, sID: <existing> }
  W->>P: ready
end
Note over W: When PARTICIPANTS.length >= pNum,<br/>start state machine

Key points:

  1. The sID is created server-side, by the Web Console, when the first participant successfully passes eligibility. Subsequent participants joining the same instance receive the same sID.
  2. Eligibility is checked per-participant. Reasons for rejection include: session count limit reached, platform mismatch with allowedPlatforms, prior session that excluded this participant.
  3. Between-subjects conditions are assigned exactly once per session — when the first eligible participant joins. They're persisted to MongoDB so subsequent joiners read the same assignment, and so the next session can balance against this one.
  4. Rejected participants are teleported away — to a remote dummy location at (-100, -100, -100) plus the rejection-target item's offset. They never reach your real experiment scene.

What pNum does

pNum is the number of participants required for a session to start. You configure it in LUIDA › Configure experiment identifiers. Default is 1.

  • For single-participant experiments, leave pNum=1. The state machine begins as soon as the first eligible participant arrives.
  • For two-participant studies (e.g., dyads), set pNum=2. The state machine waits until two participants are present before starting; if a participant drops out before the threshold is met, the session never starts.
  • The waiting room — what participants see while waiting for others — is a state-listening item you build, typically attached to the state before Trial - Start.

pNum does not cap the maximum participants per session — it's a minimum. If your scene supports it, more participants can keep joining. The room capacity (set in the Web Console) is what caps the maximum.

Session counts and exclusion

The Web Console lets you set a maxSessionCount per experiment. When that limit is reached:

  • New session attempts are rejected with eligible: false.
  • Already-in-progress sessions complete normally.

This is useful for capping how much data you collect, or for staging a publication: pilot at maxSessionCount=20, then bump it.

You can also exclude specific sessions or participants from condition balancing — for example, if a participant did the experiment but their data is unusable due to a hardware failure, mark their sID excluded so the next session's condition balancer doesn't count them. This is currently a manual operation via the Web Console; see Web Console → Sessions and balancing.

Between-subjects assignment

When existingConditions is returned to ConditionManager.js, the assignment algorithm is:

  1. Compute every possible combination of between-subjects values (Cartesian product of the variable values).
  2. Count how many sessions in existingConditions have each combination.
  3. Pick the combination(s) with the lowest count.
  4. If multiple are tied, pick one randomly.

This is straightforward least-recently-used balancing. It does not currently account for demographic stratification — i.e., balancing within demographic strata so each cell sees a similar mix of (e.g.) age and gender. That's on the roadmap; see luida-frontend/controlled-variable-plan.md for the design.

Debug overrides

In LUIDA › Configure experiment automation › Variables, each between-subjects variable has a debugValue field. If you set debugValue=near for the depth variable, then in test mode (isTestMode=true in identifiers), every session you start will be assigned depth=near regardless of what the balancer would have chosen.

Use this to:

  • Reproduce a specific bug that only happens in one condition.
  • Hand-control your test sessions while still running real experiments in production.

isTestMode=true is automatically set when you run via CSEmulator. It's also set on Cluster test spaces if you upload there. The flag never leaks into production unless you forget to flip it before publishing — keep an eye on it.

What gets logged about each session

After a session starts, LUIDA logs to the Web Console:

  • participantInfo — one record per participant, including sID, pID (1-indexed within session), IDFC, environment info (platform, VR or not, OS, device).
  • betweenSubjectsConditions — the assignment for this session, persisted via saveSessionConditions.
  • customData — whatever your experiment logged via SendDataToCollector and Process and save collected data.
  • questionnaireAnswers — one record per (participant, question) combination submitted via Questionnaire prefabs.

All four are downloadable from the Web Console as CSV/JSON/ZIP. See Web Console → Downloading data.

Where to go next