Interface control document
1. Identification
| Item | Value |
|---|---|
| Document | ICD-CMOPENXR-001 rev 1 |
| Product | OpenXR module (HEAT.OpenXR in the module registry) |
| Product versions covered | v2.0.4 |
| Peer | HEAT OpenXR API layer (CaptureApp-OpenXR, a separate installable OpenXR API layer) |
| Host | HEAT Capture, HEAT.Capture.ModuleSDK 2.0.5 |
| Data model | HEAT ProtoLib (bundled with HEAT.Capture.ModuleSDK 2.0.5); SimulationFrame, SpatialComponent, StreamComponent |
| Date | 2026-09-01 |
| Owner | VRAI Simulation |
Peer-side facts in this document (Sections 4.5, 4.6, 8) are drawn from the peer’s own design
documentation in its repository - CaptureApp-OpenXR, docs/superpowers/specs/ - at the commit
this document was written against, not from this module’s own source. Verify against that
repository directly if the peer has since changed.
2. Scope
This document defines the local named-pipe transport and push protocol between the OpenXR
module and the HEAT OpenXR API layer, and the SimulationFrame shape the layer pushes. It does
not define the OpenXR API itself, which is the peer’s own concern as an OpenXR API layer - see
the Khronos OpenXR specification .
This module is peer-to-product only: it never sends anything to the peer. The peer pushes continuously; this module only receives and republishes.
3. Interface inventory
| ID | Name | Direction | Transport | Peer |
|---|---|---|---|---|
| IF-CMOPENXR-01 | OpenXR telemetry and video push | Peer to product | Windows named pipe, local machine only | HEAT OpenXR API layer |
4. IF-CMOPENXR-01 OpenXR telemetry and video push
4.1 Transport and addressing
| Item | Value |
|---|---|
| Protocol | Windows named pipe (\\.\pipe\heat_openxr_data_pipe by default), this module as client, the layer as server |
| Pipe name | HeatOpenXRDataPipe, default heat_openxr_data_pipe. It is a hidden module setting, unavailable in the standard settings UI. |
| Direction | Inbound only. The layer’s own IPC server is documented on its side as “an inbound-only endpoint” - it never reads from this pipe, only writes |
| Wire framing | Newline-delimited UTF-8 text lines |
| Payload encoding | Each line is a base64-encoded SimulationFrame protobuf message |
4.2 Connection lifecycle
This module connects when a capture session starts and disconnects when it ends - a fresh connection attempt is made for each. The named-pipe client waits up to 5 seconds to connect; a timeout is logged as a warning and treated as a normal retry condition, not a fatal error.
If the connection drops for any reason (peer closed, connect timeout, an internal fault), this
module logs HEAT OpenXR data pipe disconnected, reconnecting and retries after 1 second,
indefinitely, for as long as the capture session remains active. There is no reconnect limit.
4.3 Authentication
None. A named pipe of this name on the local machine is trusted implicitly; any local process able to open it as a server would be accepted as the peer.
4.4 Message set
There is exactly one message shape: a serialized SimulationFrame, pushed continuously at
native rate with no request or acknowledgement in either direction. The command pipe this
module previously used to request a capture mode and poll for a reply has been removed; the peer
no longer serves it.
4.5 Data elements
Every pushed frame carries one entity, fixed by the peer: id 0x4F505852 (ASCII “OPXR” packed
into a uint64), name OpenXR_Data. This module uses that id and name directly; it does not
generate its own.
| Field | Component | Notes |
|---|---|---|
pose | SpatialComponent | Head pose |
left_controller_pose, right_controller_pose | SpatialComponent | |
left_hand, right_hand | SpatialComponent | |
left_image, right_image | SpatialComponent | Not populated by this pipeline - the peer’s CPU pixel-readback path was removed when video moved to StreamComponent |
sources (up to two VideoSource, named eye.left/eye.right) | StreamComponent | H264 access units, one per eye when both are healthy for that tick |
See Data produced for the full mapping and the video encoding parameters.
4.6 Timing
Pushed at the peer’s native XR tick rate (typically 90Hz for video-capable ticks, subject to a peer-side submission throttle that may push video less often than every tick; pose and hand data follow the same tick). Both eyes’ video for the same tick are correlated and sent together when possible; a straggler eye beyond a bounded wait does not hold up the tick - that frame is sent with only the healthy eye’s video. There is no guaranteed maximum end-to-end latency published by either side.
4.7 Error behaviour
| Condition | Observable | Module behaviour |
|---|---|---|
| Pipe connect times out (5s) | Warning logged, HEAT OpenXR data pipe reader failed, reconnecting or a connect-timeout warning | Retries after 1s |
| Peer closes the pipe | HEAT OpenXR data pipe disconnected, reconnecting | Retries after 1s |
A line fails to base64-decode or parse as SimulationFrame | Warning logged: Failed to parse SimulationFrame from OpenXR layer payload | That line is dropped; the reader continues with the next line |
| No line available | Nothing - this is the normal steady state between pushes | The module’s own per-tick poll (HandleCapture) is a deliberate no-op; publishing happens only from the background reader, never from polling |
5. Data model reference
Output is HEAT ProtoLib’s SimulationFrame, populating SpatialComponent and StreamComponent
as described in 4.5. See HEAT ProtoLib’s message reference for every
field on each. This module never populates any other component.
6. Units, frames and conventions
| Concept | Convention |
|---|---|
| Poses | XrPosef - orientation plus position, in the peer’s OpenXR tracking space; this module does not convert or reinterpret it |
| Video | H264-encoded bytes, opaque to this module; pixel/coordinate conventions are the peer’s and the encoder’s |
| Time base | The peer’s own frame construction time as carried in the pushed SimulationFrame; this module does not add or override a timestamp |
7. Configuration affecting the interface
| Setting | Interface effect |
|---|---|
HeatOpenXRDataPipe | Selects the local named-pipe name. This hidden setting defaults to heat_openxr_data_pipe and is not exposed in the standard module settings UI. |
Enabled | Determines whether the module runs at all. The host enforces it, not code inside this module. |
See Manage capture modules for the generic setting.
8. Compatibility and versioning
This module and the HEAT OpenXR API layer have no protocol version field or version-negotiation step. Deploy matching module and API-layer versions. A version mismatch does not have a defined observable signature: it can produce parse failures, an incompatible payload that parses partially, or no connection at all.
9. Security considerations
There is no authentication and no transport encryption; a named pipe of a known, fixed name is implicitly trusted. Because the pipe is local-machine-only (Windows named pipes are not reachable over the network without an explicit remote-pipe configuration, which neither side sets up), the practical exposure is limited to other processes already running on the same workstation.
This module declares no ModuleCapability at all - local named-pipe IPC to a peer on the same
machine is not classified as a mediated or declarative capability in the HEAT Capture module
model the way network sockets or device access are.
10. Conformance
- Install and enable the HEAT OpenXR API layer, then start a VR application through it. Start a HEAT Capture session with this module enabled.
- Confirm the log records
HEAT OpenXR data pipe connected, publishing at native rate. - Confirm an entity named
OpenXR_Datawith id0x4F505852appears in the captured data within one native tick, carryingSpatialComponentpose data. - Move the headset and confirm
poseupdates accordingly; move a controller and confirmleft_controller_pose/right_controller_poseupdate. - Confirm
StreamComponent.sourcescarries at least oneVideoSourcewithcodec: "H264"and non-emptyvideo_data. - Stop the layer process while the session is still running. Confirm the log records
HEAT OpenXR data pipe disconnected, reconnecting, and that restarting the layer resumes publishing without restarting the capture session.
11. Revision history
| Rev | Date | Product version | Change |
|---|---|---|---|
| 1 | 2026-09-01 | v2.0.4 | Initial issue. |