Interface control document
1. Identification
| Item | Value |
|---|---|
| Document | ICD-HCPUNREAL-001 rev 3 |
| Product | HCP Unreal (hcp_unreal_plugin, VersionName 1.0 in its .uplugin) |
| Product versions covered | commit aec62a9 on develop (no tagged release exists in this repository) |
| Peer | HEAT Capture |
| Data model | HEAT ProtoLib C-API, bundled at Plugins/hcp_unreal_plugin/Source/ThirdParty/heat_protolib/capi/. No version marker is readable from this repository; the message and event shapes referenced here were verified directly against HEAT-ProtoLib at main. |
| Date | 2026-09-02 |
| Owner | VRAI Simulation |
2. Scope
HCP Unreal observes actors registered with it in a running Unreal Engine 5 project and publishes their state and events to HEAT Capture. It starts and stops a HEAT Capture session under Blueprint or C++ control, and keeps both the session and its publish connection up on its own once started - retrying indefinitely with backoff through any HEAT Capture outage - until explicitly stopped.
It does not control Unreal actors and consumes no gameplay input of its own: capture is observational only, driven entirely by what the host project’s own gameplay code does and by which actors and components it has been told to register and attach.
3. Interface inventory
| ID | Name | Direction | Transport | Peer |
|---|---|---|---|---|
| IF-HCPUNREAL-01 | Session control | Product to peer | HTTP REST, no TLS | HEAT Capture |
| IF-HCPUNREAL-02 | Frame and event publish | Product to peer | WebSocket, no TLS | HEAT Capture |
Both interfaces are driven by one supervisor state machine (FHEATCaptureTransport), advanced
once per subsystem tick, started and stopped together by StartSession()/StopSession():
Stopped --Start()--> EnsuringSession --> Connecting --> Running
^ |
|----------- Degraded <------------
(any failure, retried with
exponential backoff, forever)4. IF-HCPUNREAL-01 Session control
4.1 Transport and addressing
Plain HTTP. Base address is http:// + ServerHostname + : + ServerPort from Project
Settings (default http://localhost:5005, matching HEAT Capture’s own shipped default).
Endpoint paths are fixed in the plugin, not configurable.
4.2 Connection lifecycle
On Start(), the EnsuringSession state first GETs current-session: if a session is already
active, it is adopted directly rather than starting a redundant one. Only if none is active does
it POST start-session. A 409 response to start-session (another client won the race to
start one between the check and this call) is treated as recoverable: the state machine re-checks
current-session and adopts whichever session actually exists, rather than failing.
Any failure at this stage - unreachable host, non-2xx response, an unparsable response body - is
retried with exponential backoff (RetryInitialDelaySeconds doubling to RetryMaxDelaySeconds,
reset to the initial delay on the next success). This retry is indefinite: there is no attempt
limit and no permanent failure state short of StopSession().
StopSession() sends one POST to stop-session, not retried - see Section 4.7 and
Data produced for what happens to still-buffered frames first.
4.3 Authentication
None presented by the plugin. HEAT Capture requires a signed-in user for every /api/session/*
call; a 401 response is recognised specifically (logged as “no user is signed in”) rather than
treated as a generic failure, though the underlying retry behaviour is the same either way.
4.4 Messages
| Endpoint | Method | Purpose | Auth |
|---|---|---|---|
/api/session/current-session | GET | Adopt an already-active session before starting a new one | Signed-in user required by HEAT Capture |
/api/session/start-session | POST | Start a HEAT Capture session | Signed-in user required by HEAT Capture |
/api/session/stop-session | POST | Stop the active session | Signed-in user required by HEAT Capture |
start-session and stop-session carry an empty body.
4.5 Data elements
| Field | Type | Unit | Range | Optionality |
|---|---|---|---|---|
active (current-session response) | boolean | n/a | n/a | required |
session.userId (current-session response, when active) | string (GUID) | n/a | n/a | present when active is true |
userId (start-session response) | string (GUID) | n/a | n/a | required on a 2xx response |
Only the user id is read from either response; nothing else in the session object is parsed or re-published on IF-HCPUNREAL-02.
4.6 Timing
RequestTimeoutSeconds (default 5 s) bounds each individual call. Continuous while not yet
Running: a failed attempt is retried after the current backoff delay, not only once.
4.7 Error behaviour
| Condition | Observable | Product behaviour |
|---|---|---|
No user signed in, wrong host/port, or any other current-session/start-session failure | Logged with the specific reason (401, HTTP status, or “could not reach HEAT Capture”) | Retried indefinitely with exponential backoff; capture continues to buffer locally (see IF-HCPUNREAL-02) |
start-session returns 409 | Logged as a session-start conflict | Re-checks and adopts the session that won the race; not treated as a failure |
stop-session fails | Logged | Not retried - the level/session teardown proceeds regardless |
5. IF-HCPUNREAL-02 Frame and event publish
5.1 Transport and addressing
WebSocket over plain HTTP (ws://, no TLS). URL is
ws:// + ServerHostname + : + ServerPort + /ws/publish, built from the same Project
Settings as IF-HCPUNREAL-01.
5.2 Connection lifecycle
Connection is attempted only after IF-HCPUNREAL-01 has a valid session identity (the Connecting
state). Once connected (Running), a lost connection - OnConnectionError or an unexpected
OnClosed - re-enters Degraded and is retried through the same backoff and the same
adopt-or-start session sequence, indefinitely, for as long as StartSession() has been called
and StopSession() has not.
On Stop(), if still connected and frames are buffered, every buffered frame is sent immediately
and the transport waits up to StopDrainTimeoutSeconds before closing the socket and calling
stop-session - see Section 5.7 for why this is a best-effort window, not a confirmed delivery.
5.3 Authentication
None presented by the plugin. HEAT Capture requires a signed-in user for the WebSocket handshake to succeed.
5.4 Messages
One binary WebSocket frame per SimulationFrame, Any-wrapped. A frame is built only while a
session is active (see IF-HCPUNREAL-01) and the global UpdateRate gate has elapsed - see
Configure. If nothing changed and no event is pending, no frame is
built at all for that tick. A built frame is always accepted for transmission regardless of
whether the socket happens to be connected at that instant - see Section 5.6. HEAT Capture closes
the connection on any frame larger than 10 MiB and ignores text frames.
5.5 Data elements
See Data produced for the entity registration, timing and binding model, and ProtoLib’s message reference for the component and event field catalogue - the plugin’s 53 generated components and 79 generated event emitters are 1:1 wrappers over those same messages.
5.6 Timing
Entity-state frames: gated by the global UpdateRate Project Setting (default 1.0 s; 0
attempts every tick), then further throttled per entity and per component - see
Configure and Data produced. A built frame
is enqueued into a bounded rolling buffer (MaxBufferedFrames, MaxBufferedMegabytes) rather
than requiring an immediately-open socket; the buffer drains as soon as the connection is
Running, oldest frame first, and evicts its own oldest entry first if it fills faster than it
can drain. Events queue immediately when emitted but are only transmitted on the next frame that
actually gets built, so worst-case event latency is bounded by UpdateRate, not sent as an
independent, lower-latency message.
5.7 Error behaviour
| Condition | Observable | Product behaviour |
|---|---|---|
| Connection lost | WebSocket connection error / an unexpected close in the console log | Automatic reconnect through the full EnsuringSession -> Connecting sequence, retried indefinitely with exponential backoff; frames built in the meantime buffer instead of being dropped |
| Buffer fills faster than it can drain (sustained outage) | Buffered frame count | Oldest buffered frame is dropped to make room for the newest - a sustained outage loses the oldest data first, not the newest |
| Frame exceeds 10 MiB | HEAT Capture closes the connection | Same automatic-reconnect path as any other connection loss |
| Mission/level stops while frames are still buffered | - | Buffered frames are sent immediately if still connected; the transport waits up to StopDrainTimeoutSeconds for the OS socket buffer to have a chance to flush them before closing - this is a best-effort window, not a confirmed delivery, since Unreal’s IWebSocket::Send has no per-message delivery callback to wait on (unlike the equivalent HCP MACE transport, which can await each send) |
6. Data model reference
Entities carry SimulationFrame → Entity → Component messages, populated by whichever of
the 53 generated components (Generated/Components/) are attached and bound, plus a
plugin-synthesized TransformComponent and LinkedEntityComponent for parent/child
relationships. Events carry TrackingEvent with either a generic JsonEventData payload or a
typed payload from one of the 79 generated event emitters (Generated/Emitters/). See
Data produced for which fields this plugin never populates (for
example, no automatic transform on a plainly-registered entity) and
ProtoLib’s message reference for the schema itself.
7. Units, frames and conventions
| Concept | Convention |
|---|---|
| Linear units | Unreal world units, passed through to TransformComponent.position without conversion - confirm your project’s world-unit-to-metre scale matches what a consumer expects |
| Angular units | Orientation is always sent as a quaternion (Rotation.X/Y/Z/W from Unreal’s own FQuat), never as raw Euler angles |
| Coordinate frame | Unreal’s own left-handed world frame, as returned by USceneComponent::GetComponentTransform(). There is no conversion to a geodetic or ENU frame anywhere in this plugin |
| Geodetic datum | Not applicable - this plugin has no geodetic position source |
| Time base | SimulationFrame.capture_time is wall-clock UTC milliseconds; simulation_time_seconds is Unreal’s own World->GetTimeSeconds(), seconds since the level started, not wall clock |
| Endianness | Not applicable - payloads are protobuf, not a fixed binary layout |
8. Configuration affecting the interface
| Setting | Interface effect |
|---|---|
ServerHostname, ServerPort | Base address for both IF-HCPUNREAL-01 and IF-HCPUNREAL-02 |
UpdateRate | Global gate on IF-HCPUNREAL-02’s entity-state frame cadence |
RetryInitialDelaySeconds, RetryMaxDelaySeconds | Backoff schedule shared by session start, session adoption, and WebSocket connect |
RequestTimeoutSeconds | Timeout for each individual current-session/start-session/stop-session call |
MaxBufferedFrames, MaxBufferedMegabytes | Bounds on IF-HCPUNREAL-02’s rolling frame buffer - see Section 5.7 |
StopDrainTimeoutSeconds | How long Stop() waits for a best-effort flush before closing - see Section 5.7 |
Per-entity update frequency, per-component CaptureFrequencyHz | Further throttle IF-HCPUNREAL-02’s content, set in code/Blueprint rather than Project Settings - see Data produced |
See Configure for the full settings reference.
9. Compatibility and versioning
Documented against hcp-unreal commit aec62a9 on develop
(.uplugin VersionName 1.0) and Unreal Engine 5.7. There is no version negotiation on either
interface. A SimulationFrame carrying components a given HEAT Capture build does not recognise
is expected to be tolerated as unknown protobuf fields, consistent with how HEAT ProtoLib
messages evolve elsewhere in this catalogue.
10. Security considerations
Neither interface uses TLS. Both rely entirely on HEAT Capture’s own signed-in-user requirement; the plugin presents no credential of its own. Treat network isolation between the Unreal workstation and the HEAT Capture workstation as the security boundary, consistent with HEAT Capture’s own security guidance - see HEAT Capture’s web API.
11. Conformance
- Configure
ServerHostname/ServerPortto match a running HEAT Capture instance, per Configure. - Sign in to HEAT Capture, then press Play in the Unreal Editor and call
StartSession(). ConfirmStarted a new HEAT Capture session(orAdopted existing HEAT Capture session) andWebSocket connectedin the Output Log. - Register an entity with a
HEATTransformComponentattached. Confirm it appears in HEAT Capture within oneUpdateRateperiod. - Stop HEAT Capture (or block the connection) while the level keeps running. Confirm the Output Log shows retry attempts, and that entities keep being registered/updated locally without the plugin crashing or giving up.
- Restart HEAT Capture. Confirm the plugin reconnects on its own - without restarting the level
- and that buffered frames from the outage arrive.
- Call
StopSession(). Confirm the session stops on the HEAT Capture side.
12. Revision history
| Rev | Date | Product version | Change |
|---|---|---|---|
| 1 | 2026-09-02 | commit 18a97e3 | Initial issue. |
| 2 | 2026-09-02 | commit fd3b65f | bScanForNewEntities and bVerboseLogging removed upstream (were undocumented dead settings, never read by the plugin); Section 8 updated to match. |
| 3 | 2026-09-02 | commit aec62a9 (PR #5, merged to develop) | Connection lifecycle replaced: automatic reconnect with exponential backoff, adopt-or-start session handling, and a bounded rolling frame buffer, ported from HCP MACE’s transport design. Sections 2 through 9 rewritten; six new Project Settings documented. |