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
pNumthreshold) and the experiment world has finished initializing; ends when the state machine reachesEnd. Identified by asID(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:
- The
sIDis created server-side, by the Web Console, when the first participant successfully passes eligibility. Subsequent participants joining the same instance receive the samesID. - Eligibility is checked per-participant. Reasons for rejection include: session count limit reached, platform mismatch with
allowedPlatforms, prior session that excluded this participant. - 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.
- 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:
- Compute every possible combination of between-subjects values (Cartesian product of the variable values).
- Count how many sessions in
existingConditionshave each combination. - Pick the combination(s) with the lowest count.
- 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, includingsID,pID(1-indexed within session),IDFC, environment info (platform, VR or not, OS, device).betweenSubjectsConditions— the assignment for this session, persisted viasaveSessionConditions.customData— whatever your experiment logged viaSendDataToCollectorandProcess and save collected data.questionnaireAnswers— one record per (participant, question) combination submitted viaQuestionnaireprefabs.
All four are downloadable from the Web Console as CSV/JSON/ZIP. See Web Console → Downloading data.
Where to go next
- Within vs between subjects — what variables actually look like before this page's machinery kicks in.
- Web Console → Sessions and balancing — the UI side of session limits and exclusions.