Skip to Content
This documentation is provided with the HEAT environment and describes this HEAT instance.

Interface control document

1. Identification

ItemValue
DocumentICD-CMHTCGAZE-001 rev 1
ProductHTC Vive gaze module (HEAT.HTCGazeModule, display name HTC Vive SDK)
Product versions covered32bbc15
PeerHTC Vive Streaming SDK (VS_PC_SDK.dll) and, through it, the Vive Business Streaming service and the headset eye tracker
HostHEAT Capture, HEAT.Capture.ModuleSDK 2.0.5
Runtime.NET 10, Windows x64
Data modelSimulationFrame carrying one Entity with a GazeComponent and a PupillometryDataComponent
Date2026-09-02
OwnerVRAI Simulation

At this revision the module does not use SRanipal. Eye data is obtained through HTC’s Vive Streaming SDK, whose managed declarations are in HTC Vive/VS_PC_SDK.cs. Any earlier reference to an SRanipal interface for this module is incorrect.

2. Scope

This document defines the native calls the module makes into VS_PC_SDK.dll, the callback interface the native library uses to deliver status transitions and eye-data payloads, the payload layout and its validity rules, and the gaze and pupillometry fields published to HEAT Capture.

The module is receive-only with respect to measurement data. Its only outbound native calls are library initialisation, release, callback registration, and start and stop of face tracking. It does not configure the headset, calibrate the eye tracker, or write anything back on behalf of HEAT Capture.

3. Interface inventory

IDNameDirectionTransportPeer
IF-CMHTCGAZE-01Vive Streaming SDK controlModule to native libraryIn-process P/Invoke, cdeclVS_PC_SDK.dll
IF-CMHTCGAZE-02Status and eye-data callbackNative library to moduleIn-process function pointer callbacksVS_PC_SDK.dll
IF-CMHTCGAZE-03Frame publicationModule to hostModule SDK publication pathHEAT Capture

4. IF-CMHTCGAZE-01 Vive Streaming SDK control

4.1 Transport and addressing

ItemValue
BindingDllImport("VS_PC_SDK.dll"), CallingConvention.Cdecl
AddressingNone. The library is loaded from the module’s own output directory by name; there is no host, port, path or device identifier to configure.
Shipped native payloadVS_PC_SDK.dll, VSWPipeVarClient64U_MT.dll and RRServerManageAPI.dll, all marked CopyToOutputDirectory=Always in the project file and copied into the distributable by the packaging script
Process capabilityThe module declares process.unrestricted, so HEAT Capture launches it without the default restricted token

4.2 Entry points used

Entry pointSignature in moduleUsed for
VS_SetCallbackFunctionvoid(VS_StatusUpdateCallback, VS_SettingChangeCallback, VS_LoggerCallback)Registers the three callbacks. Called before VS_Init so initialisation failures are still reported.
VS_Initint()Starts the SDK. 0 is success; any other value is a failure code reported verbatim in the log.
VS_Versionint()Read once, only to compose the success log line.
VS_StartFaceTrackingbool()Requests eye and facial tracking. The return value is discarded.
VS_StopFaceTrackingbool()Stops tracking. The return value is discarded.
VS_Releaseint()Releases the SDK. The return value is discarded.

VS_SDKVersion is declared in HTC Vive/VS_PC_SDK.cs but never called at this revision, so the SDK version string is not logged or published.

4.3 Connection lifecycle

InitialiseModule runs the clean-up path first, so VS_StopFaceTracking (when a tracker was already initialised) and VS_Release are called before VS_Init. It then assigns the three callback delegates to fields, registers them, and calls VS_Init. On 0 it logs Init ViveStreamingSDK <version> success. followed by a trace-level Waiting for server., and allocates the sample buffer. On any other value it logs Init ViveStreamingSDK failed(<code>). and returns false, which fails module startup.

StartCapture logs Starting HTC Gaze Data Session and calls the tracking-start helper. That call is a no-op until the native library has reported the streaming state, because the helper returns immediately while no client connection is recorded. Tracking therefore begins from the status callback described in 5.2, not from capture start.

HandleCapture calls the tracking-start helper on every cycle. This is the retry mechanism: while the headset is connected and tracking has not yet produced data, each cycle may start one initialisation worker thread.

EndCapture runs the clean-up path only when the tracker initialisation flag is set. If tracking never produced data during the session, VS_Release is not called at capture end; the release happens on the next InitialiseModule instead.

The InitialiseModule and StartCapture calls are bounded by the SDK StartupTimeout of 5 seconds, which this module does not override. VS_Init is synchronous and runs inside that budget. The per-cycle HandleCapture call has no timeout. See the Module SDK ICD for the shared contract.

4.4 Tracking start, retry and stop

ItemValue
PreconditionThe status callback must have reported server state 0; otherwise the start helper returns without doing anything
Retry budgetMAX_RETRY_COUNT = 1000 start attempts per module instance. Once exceeded, the helper returns permanently until the module restarts.
Concurrency guardAt most one initialisation worker thread at a time; a new one is created only when the previous thread is null or no longer alive
Worker behaviourLogs Starting face tracking., calls VS_StartFaceTracking, then polls the tracker initialisation flag in 100 ms sleeps for up to FACE_TRACKER_DATA_TIMEOUT_MS = 3000 ms
Worker outcome logFace tracking started or Face tracking failed to start
Stop helperCalls VS_StopFaceTracking only when the initialisation flag is set, then clears the flag, the new-sample flag and the retained payload string; always resets the retry counter; logs Stopping face tracking
Clean-up helperLogs Performing clean up of ViveStreamingSDK, runs the stop helper, calls VS_Release, resets the retry counter

The worker’s outcome log is derived from the first-payload flag rather than the tracker initialisation flag, and that flag is never cleared for the lifetime of the module instance. After the first payload of a session, every later worker logs Face tracking started whether or not the attempt succeeded. Treat the outcome log as unreliable and use arriving frames as the evidence that tracking is live.

4.5 Authentication

None. The interface is an in-process native call into a library shipped with the module. There is no credential, token, pairing step or peer identity check in either direction.

5. IF-CMHTCGAZE-02 Status and eye-data callback

5.1 Transport

The native library invokes three managed delegates registered through VS_SetCallbackFunction:

DelegateParametersHandled
VS_StatusUpdateCallbacktwo LPWStr stringsYes. The module treats the first as a status code and the second as its value.
VS_SettingChangeCallbackone LPWStr stringRegistered, body empty. Setting changes are ignored.
VS_LoggerCallbackone LPWStr stringRegistered, body empty. Native log lines are discarded, including the initialisation-failure detail the pre-VS_Init registration was meant to capture.

Callbacks arrive on the native library’s own thread. In native-rate mode the module builds and publishes the frame inside the status callback, so publication happens on that thread rather than on the SDK capture cycle.

5.2 Status codes acted on

CodeConstantHandling
2004HMD_NAMELogs VS_PC_SDK: HMD: <value> when the value is non-empty
2007VS_SERVER_VERSIONLogs Vive Streaming Server v<value> connected.
2105VS_SERVER_STATEConnection state machine, see below
5001EYE_DATAEye-data payload
2498noneAlso handled as an eye-data payload, in the same case as 5001
any othernoneIgnored silently

Server state values, as documented in the module source:

ValueMeaningModule action
0StreamingOn the first transition into this state: records the client connection, logs VS_PC_SDK: HMD connected., starts tracking, logs Starting face tracking. Repeat notifications are ignored.
1No frame received from SteamVRNone. Not logged.
2Standby, waiting for a VBS client to connectWhen a client connection was recorded: clears it, calls VS_StopFaceTracking directly, logs VS_PC_SDK: HMD disconnected.
3VBS driver not foundNone. Not logged.
4SteamVR not runningNone. Not logged.

States 1, 3 and 4 produce no log line and no state change. A workstation stuck in any of them presents as a module that never delivers data.

The standby handler calls the native stop function directly instead of the stop helper, so the tracker initialisation flag stays set. Because the start helper only starts a worker when that flag is clear, tracking is never restarted when the server returns to state 0, and gaze delivery ends for the remainder of the session. This is audit finding HTC-01 and it is a property of this revision, not of the Vive Streaming SDK. Recovery requires restarting the module.

5.3 Wire format

An eye-data value is a single string of comma-separated decimal numbers. The module splits it on ,, parses each element positionally, and stores the result in a fresh 37-element float array. Position defines meaning; there is no field name, length prefix or version marker in the payload.

IndexFieldPublished as
0-2COMBINE_EYE_ORIGIN_X/Y/Zgaze_origin
3-5COMBINE_EYE_DIRECTION_X/Y/Zgaze_direction
6-8LEFT_EYE_ORIGIN_X/Y/Zleft_gaze_origin
9-11LEFT_EYE_DIRECTION_X/Y/Zleft_gaze_direction
12-14RIGHT_EYE_ORIGIN_X/Y/Zright_gaze_origin
15-17RIGHT_EYE_DIRECTION_X/Y/Zright_gaze_direction
18LEFT_EYE_OPENNESSleft_eyelid_openness
19RIGHT_EYE_OPENNESSright_eyelid_openness
20LEFT_BLINKleft_eye_occluded, and blink when either blink flag is set
21LEFT_WIDEnot published
22RIGHT_BLINKright_eye_occluded, and blink when either blink flag is set
23RIGHT_WIDEnot published
24LEFT_SQUEEZEnot published
25RIGHT_SQUEEZEnot published
26LEFT_DOWNnot published
27RIGHT_DOWNnot published
28LEFT_OUTnot published
29RIGHT_INnot published
30LEFT_INnot published
31RIGHT_OUTnot published
32LEFT_UPnot published
33RIGHT_UPnot published
34TIMESTAMPnot published; marked unused in the module source
35LEFT_PUPIL_DIAMETERleft_eye_pupil_diameter, and half of pupil_diameter
36RIGHT_PUPIL_DIAMETERright_eye_pupil_diameter, and half of pupil_diameter

The twelve facial-expression fields at indices 21 and 23 to 33 are parsed into memory and never read out. They are not reachable through HEAT Capture at this revision.

5.4 Sample validity

RuleBehaviour
Empty value stringThe whole callback is ignored; no state change, no publication
First non-empty payload of the module instanceDiscarded unread. It only sets the first-payload flag. The tracker initialisation flag is not set, so the initialisation worker does not see this payload as success.
Null, empty or whitespace elementRejected; the element keeps the zero-initialised value of the fresh array
Non-numeric elementRejected on the same terms
NaN elementRejected on the same terms
Decimal separatorParsed with CultureInfo.InvariantCulture, so a comma-decimal workstation locale does not corrupt values
More than 37 elementsNot handled. The parse loop is bounded by the element count, not by the array length.

The rejection path is documented in the source as keeping the previous value, but the array is newly allocated for each payload, so a rejected element is published as 0 rather than as the last good value. A consumer cannot distinguish a rejected element from a genuine zero.

There is no per-field validity or confidence flag in the payload and the module does not synthesise one. Blink state is the only quality-adjacent signal, and it is a blink flag rather than a tracking-validity flag.

5.5 Timing and cadence

ModeCadence
SendAtNativeRate = true (default)One frame published per accepted eye-data callback, at whatever rate the native library delivers. The module neither defines nor caps this rate; the authority is HTC’s own documentation for the headset and the Vive Streaming SDK.
SendAtNativeRate = falseHandleCapture returns a frame built from the most recently retained payload, once per SDK Interval, 1000 ms by default. If no new payload arrived, the previous sample is republished with a new capture timestamp.

In native-rate mode HandleCapture returns null every cycle, which publishes nothing for that cycle, so frames come only from the callback. Intermediate payloads are never buffered in either mode: polled mode discards everything except the latest payload at the moment of the cycle.

6. IF-CMHTCGAZE-03 Frame publication and data model reference

Each publication is a SimulationFrame containing exactly one entity.

ItemValue
Entity nameHTC_Data
Entity idGuid.NewGuid().GetHashCode() masked to 31 bits, computed once per module instance. It is not derived from the entity name and is not stable across module restarts.
ComponentsGazeComponent and PupillometryDataComponent
Capture timeUTC Unix milliseconds taken when the frame is built. The payload’s own timestamp field is not used.

6.1 Gaze component

Published fieldSource
gaze_origin, gaze_directionCombined-eye origin and direction, indices 0-5
left_gaze_origin, left_gaze_directionIndices 6-11
right_gaze_origin, right_gaze_directionIndices 12-17
left_eye_occludedLEFT_BLINK compared against exactly 1
right_eye_occludedRIGHT_BLINK compared against exactly 1
blinkLogical or of the two blink comparisons

Occlusion is the blink flag, not an occlusion measurement. Because the comparison is against exactly 1, any other non-zero blink encoding the native library might use would read as not blinking.

IPD, combined_eye_position, the per-eye position fields, every gaze quality field, has_glasses, gaze_point and gaze_screen_point are left at their protobuf defaults.

6.2 Pupillometry component

Published fieldSource
left_eyelid_opennessIndex 18
right_eyelid_opennessIndex 19
left_eye_pupil_diameterIndex 35
right_eye_pupil_diameterIndex 36
pupil_diameterArithmetic mean of indices 35 and 36, computed by the module

The combined eyelid_openness field, every quality field and other_pupil_data are left at their protobuf defaults. The mean pupil diameter is unweighted and does not exclude an eye that is blinking or untracked, so during a one-eyed blink it is roughly half the open eye’s diameter.

6.3 Packet envelope

The SDK wraps the frame as a DataPacket with TimestampUnixMs set to UTC now and the frame packed into Payload. It is published with NodeId when OverrideNodeId is set, otherwise -1. No EntityEvent is published by this module.

7. Units and conventions

ConceptConvention
Gaze origin and directionThree-component float vectors, passed through unmodified. The module applies no normalisation, scaling, axis remap or handedness conversion, so the coordinate frame is exactly the one the Vive Streaming SDK emits.
Eyelid opennessNative scalar, passed through unmodified. No range is asserted or enforced.
Pupil diameterNative scalar, passed through unmodified. The unit is whatever the SDK reports; the module neither converts nor labels it.
BlinkBoolean, true only when the native field equals 1
Time baseHost wall clock, UTC Unix milliseconds, applied when the frame is built. No device timestamp is carried, and no latency compensation is applied for callback delivery or for the republication delay in polled mode.

No field is range-checked or clamped. The authority for the meaning, units and coordinate frame of every value is HTC’s own Vive Streaming SDK documentation.

8. Error behaviour

ConditionDetectionLogEffect
VS_Init returns non-zeroReturn codeInit ViveStreamingSDK failed(<code>)., error levelModule start fails
Native library or its dependencies missing.NET fails to resolve the DllImportHost-level exception, no module-specific messageModule start fails
Server state 1, 3 or 4Status callbackNoneSilent wait; no data and no diagnostic
Standby transition, server state 2Status callbackVS_PC_SDK: HMD disconnected.Tracking stops and, per HTC-01, does not restart for the session
Tracking does not produce data within 3000 msInitialisation worker pollFace tracking started or Face tracking failed to start, the outcome string being unreliable per 4.4Next capture cycle starts another worker until the 1000-attempt budget is exhausted
Retry budget exhaustedRetry counterNoneThe start helper returns silently for the rest of the module instance
Worker thread cannot be createdExceptionInitFaceTrackerWorker exception <message>, error levelThat attempt is lost; the retry counter is not incremented for it
Unparsable payload elementPer-element parseNoneElement published as 0
Payload with more than 37 elementsNot detectedNoneIndexOutOfRangeException from the parse loop, propagating on the native callback thread
Exception thrown from HandleCaptureSDKError in moduleThe capture loop continues

The frame builder contains a no-data guard that logs No Gaze data available and returns no frame. It is unreachable at this revision, because the sample buffer is allocated during initialisation and replaced, never cleared to null. An operator will not see that warning.

Loss of the eye tracker while the server stays in state 0 produces no log line and no state change. In native-rate mode, frames stop. In polled mode, the last payload is republished indefinitely with fresh capture timestamps, which a consumer cannot distinguish from a wearer holding a fixed gaze. Correlate with payload values changing rather than with frame arrival.

9. Configuration affecting the interface

SettingSectionDefaultInterface effect
SendAtNativeRateHEAT.HTCGazeModuletrueSelects callback-driven publication or per-cycle polled publication, as in 5.5
EnabledHEAT.HTCGazeModuletrueWhether the module runs at all
IntervalHEAT.HTCGazeModule1000 msPublication cadence in polled mode; in native-rate mode it only sets how often a tracking-start retry can occur
OverrideNodeId, NodeIdHEAT.HTCGazeModulefalse, 0Node attribution on the published packet
TimeoutHEAT.HTCGazeModule30 sNot read by this module

SendAtNativeRate is read from the settings monitor at each decision point, so it takes effect without restarting the module. The settings identity registered for localisation is HtcVive, with the label key module.HtcVive.sendAtNativeRate.label; the configuration section name is HEAT.HTCGazeModule. See Manage capture modules for the generic settings.

The retry budget, the 3000 ms initialisation timeout, the 100 ms poll interval and the status codes are compile-time constants and are not configurable.

10. Compatibility and versioning

There is no version negotiation on either side of the native interface. VS_Init is called without a requested version, and the value returned by VS_Version is only logged. The module therefore accepts whatever VS_PC_SDK.dll is present in its own directory, and the shipped copy is the only version the module has been built against.

The eye-data payload is positional, unversioned and unlength-checked. A Vive Streaming SDK release that inserts, removes or reorders a field silently misattributes every value after the change point; a release that appends fields beyond index 36 raises an index-out-of-range exception on the callback thread. Revalidate the index table in 5.3 against HTC’s documentation before adopting a new SDK build.

Prerequisites for the interface to come up at all: Windows x64, .NET 10, SteamVR running and streaming, and HTC’s Vive Business Streaming driver installed. The module connects to the VBS service, not to the headset directly.

The package manifest produced by the module’s own packaging script names the module HEAT.HTCGazeModule with display name HTC Vive SDK. The central module registry records the same package with a trailing space, audit finding REG-03. Where a deployment matches packages by exact name, treat HEAT.HTCGazeModule as the authoritative identity and trim whitespace before comparing, otherwise the registry entry and the installed package can be seen as two products.

11. Security considerations

The interface is entirely local: an in-process native call into a bundled DLL, which in turn talks to the Vive Business Streaming service on the same workstation. There is no network endpoint, credential or encryption at this boundary, and the module performs no peer identity check. Because the DLL is resolved by name from the module’s own directory, write access to that directory is equivalent to code execution inside the module process; treat the installation directory as integrity-protected.

The module declares process.unrestricted, which is an explicit opt-out from HEAT Capture’s default per-module process restriction and widens what the module process can do relative to other modules. Gaze, blink and pupil diameter are biometric personal data and are directly identifying in combination with session metadata: restrict access to the workstation and to captured sessions according to the deployment’s own data-handling policy.

12. Conformance

  1. With SteamVR streaming and the VBS driver installed, start a capture session. Confirm the log records Init ViveStreamingSDK <version> success., then Vive Streaming Server v<value> connected., then VS_PC_SDK: HMD connected..
  2. Confirm one entity named HTC_Data appears in the captured data with both a GazeComponent and a PupillometryDataComponent.
  3. With the headset worn, confirm gaze_direction and the per-eye directions change across successive frames, and that left_eyelid_openness and right_eyelid_openness are non-zero.
  4. Blink deliberately and confirm blink, left_eye_occluded and right_eye_occluded assert.
  5. Leave SendAtNativeRate at true and confirm frames arrive faster than the configured Interval. Set it to false and confirm the frame rate drops to one per Interval and that a static payload is republished with advancing capture timestamps.
  6. Verify HTC-01 before relying on the session: put the streaming server into standby, confirm VS_PC_SDK: HMD disconnected., then return to streaming and check whether any new frame arrives. Until HTC-01 is fixed, no frame will arrive and the capture session must be restarted. Do not run a session where a standby transition is expected.
  7. Confirm that a reading of 0 is not accepted as a measurement without corroboration, given the rejected-element behaviour in 5.4.

13. Revision history

RevDateProduct versionChange
12026-09-0232bbc15Initial issue.
Opens your browser's print dialog. Choose "Save as PDF" as the destination.