Provides assets for creating and executing Virtual Reality (VR) tasks for Sollertia platform data acquisition systems.
This project is part of the Sollertia AI-assisted scientific data acquisition and processing platform, built on the Ataraxis framework and developed in the Sun (NeuroAI) lab at Cornell University. It provides the Unity-side assets and runtime bindings for building VR tasks consumed by Sollertia platform data acquisition systems. The current task surface targets an infinite linear corridor environment displayed to the animal during runtime across a three-monitor VR rig.
This project is the Unity counterpart of sollertia-experiment,
the Python acquisition runtime. The two libraries communicate over an MQTT 5.0 broker:
sollertia-experiment publishes treadmill motion, lick events, and runtime toggles; this project publishes cue sequences,
scene metadata, stimulus events, and brake-activation requests. Task templates, experiment configurations, and the data
schema are owned by sollertia-shared-assets, whose slsa mcp
server doubles as the agentic Unity Editor relay for AI-driven task authoring.
The Unity-side runtime is a ground-up refactor of the GIMBL VR framework,
inlined under Assets/Gimbl/. The refactor preserves GIMBL's core actor, controller, and display abstractions with
extensive editor, MQTT, and runtime enhancements focused on agentic task authoring and Sollertia platform integration.
- Generates infinite-corridor VR tasks from YAML templates via the Editor menu or the
slsa mcpUnity relay. - Supports five stimulus trigger modes (interaction, collision, occupancy-disarm, occupancy-arm, occupancy-trigger) with optional guidance modes.
- Supports probabilistic transitions between trial structures within a single task template.
- Exposes HTTP-based McpBridge that exposes 14 Editor operations to AI agents (task lifecycle, scene management, asset inspection, Play Mode control, parameter read/write).
- Maintains bidirectional MQTT 5.0 contract with
sollertia-experiment, centralized in a single
MQTTTopicsconstant set. - Apache 2.0 License.
External requirements that must be installed before working with this Unity project:
- Unity Game Engine 6000.3.15f1 LTS (Unity 6). Installed via Unity Hub.
- An MQTT broker supporting MQTT 5.0, such as Mosquitto 2.0 or later. The project
defaults to
127.0.0.1:1883for the broker; both the IP and port are configurable from the Task Parameters window. - Blender 4.5.0 LTS is required only for authoring or modifying 3D assets (corridor models). It is not required to build or run existing tasks.
- .NET SDK 8.0 or later and CSharpier only when contributing source changes (see Formatting and Style).
Two managed dependencies ship as committed DLLs under Assets/Plugins/ and require no separate installation:
- MQTTnet — the MQTT 5.0 client used by
MQTTClient.cs. - YamlDotNet — the YAML deserializer used by
ConfigLoader.cs.
This project is a Unity 6 application that is not distributed via package managers. To install:
- Install Unity Hub and use it to install the required Unity Editor version (6000.3.15f1 LTS).
- Download this repository to the local machine using the preferred method, such as git-cloning. Use one of the stable releases when available.
- From Unity Hub, select Add project from disk and navigate to the local folder containing the downloaded
repository:

Note, if the correct Unity version is not installed when the project is imported, Unity Hub displays a warning
next to the project name. Selecting the warning offers to install the recommended Unity version:

The runtime and editor code lives under Assets/. Hand-authored assets are protected from agentic deletion via the
McpBridge's protected-paths list; generated assets live under separate folders and are rebuilt on demand.
| Directory | Purpose |
|---|---|
Assets/InfiniteCorridorTask/Scripts/ |
Runtime C# (Task, zone scripts, ConfigLoader, schema mirror classes) |
Assets/InfiniteCorridorTask/Scripts/Editor/ |
CreateTask pipeline, McpBridge HTTP listener, TaskEditor, MiniJson |
Assets/InfiniteCorridorTask/Configurations/ |
YAML task templates |
Assets/InfiniteCorridorTask/Cues/ |
Generated cue prefabs (length-suffixed, shared across templates) |
Assets/InfiniteCorridorTask/Prefabs/ |
Hand-authored zone prefabs and generated segment prefabs |
Assets/InfiniteCorridorTask/Tasks/ |
Generated task prefabs (one per template) |
Assets/InfiniteCorridorTask/Materials/ |
Generated cue materials and the canonical _CueShaderReference.mat |
Assets/InfiniteCorridorTask/Textures/ |
Cue textures referenced by YAML templates |
Assets/UI-lick-reward/ |
On-screen lick and stimulus feedback canvas |
Assets/Gimbl/ |
Inlined GIMBL runtime and the consolidated Task Parameters window |
Assets/Scenes/ |
ExperimentTemplate.unity plus per-task generated scenes |
Assets/Plugins/ |
Inlined MQTTnet.dll and YamlDotNet.dll |
A task represents an infinite linear corridor sequence built from a fixed catalog of reusable (prefab) parts. Under this hierarchy, a prefab is Unity's serializable template for a hierarchy of GameObjects — a piece of a scene saved as a file so it can be instantiated repeatedly and updated in one place.
Any task hierarchy can be described in terms of four distinc levels, finest to coarsest:
Task
│
├── Corridor ─┐ A corridor is a fixed sequence of segments stacked along the animal's
├── Corridor │ motion axis. A task pre-instantiates one corridor for every possible
├── Corridor │ combination of segments, so corridors enumerate the configuration space
└── … (every ─┘ the task can take.
combination)
one Corridor
├── Segment ← active: the trial currently driving behavior
├── Segment ← lookahead: visible only, no behavior
└── Segment ← lookahead: visible only, no behavior
one Segment (= one trial)
├── Cue
├── Cue ← the cue sequence declared by this trial, laid out along the corridor
└── …
- Cues are individual visual panels displayed along the walls of the corridor. They are the smallest unit of the corridor and are shared across every trial — and every task — that declares the same cue identity.
- Segments are trials. Each trial declared by the task produces exactly one segment. A segment owns its cue sequence and the behavioral element associated with that trial (the stimulus trigger zone, the reward or aversive contingency, etc.).
- Corridors are fixed-length windows of segments. The first segment in a corridor is the active trial — it drives behavior. The remaining segments are pure visual lookahead, so the animal can see what is coming without yet experiencing it. The number of segments per corridor is a per-task parameter; setting it to one collapses corridor and segment into the same thing.
- Tasks are the full set of corridors plus a transition graph that describes how trials chain during a session. Each trial may declare a probability distribution over the trials that can follow it; trials without an explicit distribution are followed by a uniformly random trial.
Iterative corridor traversal. At session start, the task walks its trial-transition graph to build a flat sequence of trials that overshoots the configured track length. The runtime then slides a window the size of the corridor (in segments) over that sequence. The current window names one corridor in the pre-built catalog; the animal is teleported to that corridor's lane (its x-position). Whenever the animal finishes the first segment of the current corridor, the window slides one trial forward and the animal jumps to the corridor that matches the new window. Adjacent corridors share all but one segment, so the visible cue sequence stays continuous across the teleport and the animal experiences a single infinite track.
The corridor count grows exponentially with the number of segments per corridor, so raising the lookahead depth is a deliberate choice: it adds visual context at the cost of an exponentially larger task. Most paradigms use one or two segments per corridor.
On disk, a task lives as three artifacts that share the same basename. The template is the authoritative description; the task prefab and the task scene are derived from it, and the three files together represent one task end to end:
<name>.yaml ─┐ the template — an abstract description of the task's cues, trials, and transitions
▼
<name>.prefab ─┐ the task prefab — the runtime hierarchy of corridors, segments, and cues
▼ introduced in the previous section, built from the template
<name>.unity ─┐ the task scene — a runnable scene that instantiates the task prefab and wraps
▼ it in the auxiliary infrastructure a session needs
- The template is the only artifact authored by hand. It is a plain text description of the task — its cues, the trials those cues compose into, and the transition probabilities between trials.
- The task prefab is the runtime hierarchy described in the previous section: every corridor the task can take, with the segments and cues that fill them. It is fully regenerable — the same template always produces the same task prefab.
- The task scene wraps one instance of the task prefab in the auxiliary GameObjects a session needs (the animal avatar, the display rig, the broker client, the controllers that drive avatar motion). Play mode runs against the task scene, not the bare prefab. One hand-authored base scene serves as the template that every task scene is copied from; that base is the only scene that is not a task scene.
The basename convention is enforced end to end: regenerating from a template named <name>.yaml always produces a
<name>.prefab and a <name>.unity. One name identifies the task across all three layers.
Two more file types complete the picture:
- Segment prefabs live alongside the task prefab and are owned by their parent template — their filenames embed both the template name and the trial name, so each task's segments are addressable on their own without colliding with any other task's segments.
- Cue prefabs are the one shared layer. They are keyed by cue identity and cue length, so two tasks that declare the same cue identity reuse the same cue prefab file. The generation pipeline aborts up front if two tasks declare the same cue identity with different visuals, so the shared file can never silently corrupt a sibling task.
Task templates are YAML files under Assets/InfiniteCorridorTask/Configurations/. The schema mirrors the Python
TaskTemplate dataclasses in sollertia-shared-assets, and
the deserializer in ConfigLoader.cs uses UnderscoredNamingConvention to map snake_case YAML keys to camelCase C#
fields.
A task template defines:
- cues: A list of visual cue panels. Each cue has a unique
name, acode(0–255 byte) used for MQTT and downstream data analysis, alength_cm, and atexturefilename resolved againstAssets/InfiniteCorridorTask/Textures/. - vr_environment: The Unity corridor configuration —
corridor_spacing_cm,segments_per_corridor,padding_prefab_name,cm_per_unity_unit, andcue_offset_cm. - trial_structures: A dictionary mapping trial names (e.g.,
ABCD) to their spatial configuration: the cue sequence, the stimulus trigger zone start and end positions, the stimulus location, an optional collision-boundary visibility flag, a trigger type (one of"interaction","collision","occupancy_disarm","occupancy_arm", or"occupancy_trigger"), and an optional probability distribution over successor trials.
The five trigger modes share the same Stimulus event but differ in how that event is fired:
- interaction: an animal interaction (e.g., a lick) detected while inside the trigger zone fires the stimulus.
- collision: crossing an invisible boundary wall — a thin collider at
stimulus_location— fires the stimulus unconditionally, with no sensor and no occupancy requirement. TheshowStimulusCollisionBoundaryflag toggles the boundary's visibility. - occupancy_disarm: colliding with the boundary while the occupancy requirement is not met fires the stimulus.
- occupancy_arm: the inverse of
occupancy_disarm— occupying the zone for the required duration arms the boundary, and colliding with the now-armed boundary fires the stimulus. - occupancy_trigger: occupying the zone for the required duration fires the stimulus immediately, with no boundary collision.
All three occupancy modes keep the occupancy-guidance brake: the OccupancyGuidanceZone publishes Delay to guide
the animal toward completing the occupancy requirement when running in guidance mode.
Template filenames follow the ProjectAbbreviation_TaskDescription.yaml convention (for example, SSO_Merging.yaml).
The template name is derived from the filename and is reused verbatim as the Unity scene name, the task prefab name,
and the prefix for every generated segment prefab. Each template must include a YAML comment header with Project,
Purpose, Layout, and Related fields:
# Project: StateSpaceOdyssey
# Purpose: Merges ABC and AGFE trial structures by sharing the A cue.
# Layout: Segment ABC with the rewarding stimulus trigger zone in cue C.
# Segment AGFE with the rewarding stimulus trigger zone in cue E.
# Related: SSO_Shared_Base (ABC base training), SSO_Merging_Base (AGFE base training)Note, detailed schema authoring guidance is owned by the /task-templates skill in the sollertia marketplace's
assets plugin. See sollertia-shared-assets for the
authoritative dataclass definitions.
Tasks can be generated from the Editor menu or programmatically via the McpBridge.
Editor menu flow. Select CreateTask → New Task from the Unity menu bar. A file dialog seeded at
Assets/InfiniteCorridorTask/Configurations/ opens; only templates inside that directory are accepted. After selecting
a template, the pipeline:
- Runs a cross-template cue-texture preflight (
ValidateCueDefinitionsAcrossTemplates) — if two templates declare the same(cue name, length_cm)pair with different textures, the generation aborts before any asset is written. - Wipes every segment prefab the template owns (
CleanGeneratedSegments) so trial-parameter edits take effect. - Builds or reuses cue prefabs keyed by
(name, length_cm)underAssets/InfiniteCorridorTask/Cues/. - Builds every segment prefab from scratch under
Assets/InfiniteCorridorTask/Prefabs/<TemplateName>_<TrialName>.prefab. - Assembles the task prefab at
Assets/InfiniteCorridorTask/Tasks/<TemplateName>.prefab. - Copies
Assets/Scenes/ExperimentTemplate.unitytoAssets/Scenes/<TemplateName>.unity, instantiates the task prefab into it, and runsMainWindow.EnsureControllersso both theLinearTreadmill(hardware) andSimulatedLinearTreadmill(keyboard testing) controllers are present.
MCP-driven flow. The same pipeline is reachable via AI agents over the slsa mcp server's Unity relay. A single
create_task_tool call builds both the task prefab and the matching scene from the same CreateTask.CreateFromTemplate
and CreateTask.CreateSceneFromTemplate methods used by the Editor menu, so the resulting assets are byte-equivalent.
When a task is generated via the Editor menu, the scene is created and opened automatically. To load an existing task into a new scene manually:
- Open the scene at
Assets/Scenes/<TemplateName>.unityviaFile → Open Scene(the file already contains the task prefab instance, the Display rig, the Actor, both controllers, and the MQTT client). - Configure displays and per-scene parameters via the Task Parameters window.
- Enter Play Mode (
Window → Task Parametersis always available, and the Play button starts execution).
Note, scene-specific settings such as the camera-to-monitor mapping are scene-bound. Always verify the mapping after every reboot, since the operating system may reorder display ports.
Window → Task Parameters opens the consolidated editor surface for every per-scene configuration. The window is
docked next to the Inspector tab and auto-opens on Editor start, scene open, and Play Mode entry. The surface exposes
five sections:
| Section | Controls |
|---|---|
| Actor | Animal model selection and active controller (Linear or Simulated Linear) |
| MQTT | Broker IP and port; the Test Connection button performs a one-shot connect/disconnect probe |
| Display | Brightness, height in VR, and a Blank/Show toggle for the active display |
| Camera Mapping | Refresh Monitor Positions plus a per-monitor row (one per OS-detected monitor) with a camera dropdown (Left View, Center View, Right View for the default display rig) and a Show Full-Screen Views action |
| Task | Require Interaction, Require Wait, Track Length, and Track Seed for the active scene's Task component |
The Task component's public fields are marked [HideInInspector]; TaskEditor replaces the default Inspector with
a HelpBox pointing at this window. Configure every task field through Task Parameters, not the Inspector. The
Require Interaction and Require Wait controls are hidden when the active scene lacks the corresponding
GuidanceZone or OccupancyZone.
Warning! Verify monitor assignments after every system reboot. The operating system can reassign display ports, and the camera-to-monitor mapping is scene-bound — a mismatch causes the wrong camera to render to each physical monitor.
For manual testing without hardware, select Simulated Linear as the Actor's controller. The
SimulatedLinearTreadmill reads keyboard input via the Unity Input System and publishes a synthetic Interaction
message on every press of the Jump action (spacebar).
This project communicates with sollertia-experiment over MQTT
5.0. All topics are flat PascalCase identifiers (no hierarchical separators), declared as public const string values
in Assets/Gimbl/Scripts/MQTT/MQTTTopics.cs.
| Topic | Direction (Unity) | Payload |
|---|---|---|
SessionStart |
Publish | Empty trigger |
SessionStop |
Publish | Empty trigger |
Motion |
Subscribe | {movement: float} |
Interaction |
Publish + Subscribe | Empty trigger |
Stimulus |
Publish + Subscribe | {trialName: string} |
Delay |
Publish | {delayMilliseconds: uint} |
CueSequenceTrigger |
Subscribe | Empty trigger |
CueSequence |
Publish | {cueSequence: byte[]} |
SceneNameTrigger |
Subscribe | Empty trigger |
SceneName |
Publish | {name: string} |
RequireInteraction |
Subscribe | {value: bool} |
RequireWait |
Subscribe | {value: bool} |
When the broker is unreachable, MQTTClient.Publish routes messages in-process so keyboard-only test runs still reach
local subscribers (for example, the on-screen LickStimulusSpawner indicator). Only Interaction and Stimulus
have a local Unity-side subscriber, so they alone can be exercised without a broker; every other topic requires a
real MQTT 5.0 broker because sollertia-experiment is the counterparty.
Note, the /mqtt-contract skill in the sollertia marketplace's unity plugin is the canonical reference for
topic ownership and payload shape. Any topic addition or rename must be coordinated with sollertia-experiment in the
same release.
The McpBridge editor plugin (Assets/InfiniteCorridorTask/Scripts/Editor/McpBridge.cs) starts an HttpListener on
127.0.0.1:8090, [::1]:8090, and localhost:8090 when the Unity Editor loads. The listener exposes Editor
operations to AI agents via JSON request/response. The backing MCP server (slsa mcp from
sollertia-shared-assets) relays each agent tool call to this
bridge over HTTP.
The bridge dispatches 14 tools:
| Tool | Description |
|---|---|
create_task |
Builds the task prefab and the matching scene from a YAML template in one call |
delete_task |
Removes the scene + companion + task prefab + every segment prefab for a template |
inspect_prefab |
Returns hierarchy, components, transforms, and collider details |
clone_zone_prefab |
Clones a base zone prefab into a new trigger-zone prefab (script + field swaps) |
delete_asset |
Deletes a regenerable non-scene asset (refuses hand-authored protected paths) |
list_assets |
Lists Unity assets by type filter within a search path |
list_scenes |
Enumerates every .unity asset and reports the active scene |
open_scene |
Opens a scene with explicit unsaved_changes policy |
inspect_scene |
Returns the active scene's root hierarchy and dirty flag |
enter_play_mode |
Triggers EditorApplication.EnterPlaymode |
exit_play_mode |
Triggers EditorApplication.ExitPlaymode |
get_play_state |
Returns playing, compiling, or edit plus the active scene name |
read_task_parameters |
Snapshots Actor, MQTT, Display, Camera Mapping, and Task fields |
write_task_parameters |
Applies a subset of Task Parameters fields and returns a new snapshot |
All responses are JSON objects carrying a success boolean plus a payload or error string. delete_asset is bounded
by an allow-prefix list (Assets/InfiniteCorridorTask/Tasks/, Prefabs/, Cues/, Materials/) and rejects scene
paths under Assets/Scenes/ — scene cleanup goes through delete_task exclusively so the cascade-delete of the
matching Assets/VRSettings/Displays/<scene>-savedFullScreenViews.asset companion can never be bypassed. A
protected-paths set covers the four hand-authored prefabs (StimulusTriggerZone.prefab, OccupancyTriggerZone.prefab,
ResetZone.prefab, Padding.prefab), the four hand-authored materials (_CueShaderReference.mat, Floor.mat,
Wall.mat, TargetMat.mat), and the scene base template (ExperimentTemplate.unity). Path traversal sequences and
absolute paths are rejected.
Note, AI agents do not call this bridge directly. They use the slsa mcp server's Unity relay tools, which are
listed in the sollertia-shared-assets README. The bridge
exists so that any MCP-driven action against this project shares the same code paths as the Editor menu.
These notes apply to project developers and task authors who modify source code, segment prefabs, or templates.
Three categories of assets coexist in this project:
- Hand-authored (protected):
StimulusTriggerZone.prefab,OccupancyTriggerZone.prefab,ResetZone.prefab,Padding.prefab,Materials/_CueShaderReference.mat,Materials/Floor.mat,Materials/Wall.mat,Materials/TargetMat.mat, andScenes/ExperimentTemplate.unity. These are the source templates and shared assets thatCreateTaskreferences at generation time (and that the trigger zone prefabs reference in turn viaTargetMat.mat). They must remain untouched;McpBridge.DeleteProtectedPathsrefuses to delete them. - Generated (regenerable): every cue prefab under
Cues/, every segment prefab underPrefabs/matching<TemplateName>_<TrialName>.prefab, every cue material underMaterials/Cue_*_*cm.mat, every prefab underTasks/, and every scene other thanExperimentTemplate.unity. These are produced byCreateTaskand are safe to delete viadelete_task(whole-task cleanup) ordelete_asset(individual cue prefab / material) so the next generation pass rebuilds them. - Shared inputs: the YAML templates under
Configurations/and the cue textures underTextures/. Templates are the source of truth for trial structure; textures are imported from external sources (for example, vr-visual-cues) and referenced by thecues[].texturefield.
Note, cue prefabs are shared across templates by (name, length_cm). Editing a cue's texture without renaming
the cue requires deleting the affected Cue_<name>_<length>cm.prefab and Cue_<name>_<length>cm.mat before
regenerating; the cross-template cue-texture preflight catches conflicts before they corrupt downstream assets.
This project uses CSharpier for code formatting and an .editorconfig for naming, brace,
and spacing conventions. Run csharpier . before committing, or csharpier --check . to verify without modifying.
See the /csharp-style skill in the ataraxis marketplace's automation plugin for the complete C# convention
reference.
The project exposes six concentrated extension points. Each has a matching skill in the sollertia marketplace's unity or assets plugin, listed alongside the touch points below.
| Extension | Touch points | Owner skill |
|---|---|---|
| New task template | YAML in Configurations/; generate via /task-prefabs |
/task-templates |
| New cue texture | PNG in Textures/; reference it from a YAML texture field |
/task-templates |
| New trigger zone type | New zone script + prefab (via /zone-prefabs) + ConfigLoader literal + CreateTask branch |
/zone-prefabs |
| New MQTT topic | MQTTTopics constant + matching publisher / subscriber on Unity and sollertia-experiment sides |
/mqtt-contract |
New McpBridge tool |
Dispatch switch case + handler method + @mcp.tool() wrapper in unity_tools.py |
n/a (manual) |
| New treadmill controller | ControllerObject subclass + ControllerTypes enum entry |
/gimbl-framework |
Adding a new trigger zone type is the most cross-cutting extension. The /zone-prefabs skill documents the
copy-and-edit workflow for the prefab itself: start from StimulusTriggerZone.prefab (interaction and collision modes)
or OccupancyTriggerZone.prefab (the three occupancy modes) under Prefabs/, swap the modifier script GUIDs, rename
regions, and override field defaults. The new prefab path must then be added to McpBridge.DeleteProtectedPaths, a new
branch must be added in CreateTask.BuildSegmentPrefabs with a matching Place...Zone helper, and
ConfigLoader.ValidateTemplate must accept the new trigger_type literal. CreateTask sets the TriggerMode enum
field on StimulusTriggerZone from the trigger_type, and the zone dispatches on that enum. The Python side requires
a matching TriggerType registry update via the /library-extension skill in the assets plugin. Adding a
TriggerType member does not require a from_task_template branch in every acquisition system: the platform
TriggerType enum carries all members, but each system maps only the subset it supports and may leave a mode
unmapped. A config that uses an unmapped mode raises a clear "not mapped to a runtime trial class" error. The
Mesoscope-VR system, for example, maps interaction (MesoscopeWaterRewardTrial) and occupancy_disarm
(MesoscopeGasPuffTrial), and does not map collision, occupancy_arm, or occupancy_trigger.
Adding a new MQTT topic requires the constant in MQTTTopics.cs (with Direction, Payload, and Callers
remarks), a runtime script that publishes or subscribes, an in-lockstep update in sollertia-experiment, and a refresh
of the /mqtt-contract skill catalog.
Adding a new McpBridge tool requires a new switch case in McpBridge.Dispatch, a handler method that returns
Ok(...) or Error(...), optional integration with AcquireSceneComponents and BuildSnapshot when the tool reads
or writes scene state, and a matching @mcp.tool() wrapper in
sollertia-shared-assets/src/sollertia_shared_assets/interfaces/unity_tools.py.
Claude Code skills and AI development assets for this project are distributed through two marketplaces:
- sollertia marketplace:
- unity plugin — Unity Editor skills that drive McpBridge tools, document the MQTT contract, document the
CreateTaskpipeline, and guide manufacturing of new trigger zone prefabs. - assets plugin — registers the
slsa mcpserver (which fronts the Unity relay), and provides configuration and experiment-authoring skills (task templates, experiment configurations, library extension).
- unity plugin — Unity Editor skills that drive McpBridge tools, document the MQTT contract, document the
- ataraxis marketplace:
- automation plugin — shared development skills that enforce coding conventions (C# style, README style, commit messages, project layout) and general-purpose codebase exploration tools.
Install all three plugins to make the full skill set available to compatible AI coding agents. The unity plugin depends on the assets plugin for the backing MCP server that drives the Unity Editor relay.
This project uses semantic versioning. See the tags on this repository for the available project releases.
This project is licensed under the Apache 2.0 License: see the LICENSE file for details.
- All Sun lab members for providing the inspiration and comments during the development of this library.
- The creators of the original GIMBL package, whose framework is inlined under
Assets/Gimbl/. - The creators of MQTTnet and YamlDotNet,
whose libraries ship as committed DLLs under
Assets/Plugins/. - The vr-visual-cues project for the cue texture set.