Skip to main content

Shared debug overlay

The debug-overlay API gives addon developers one cooperative OpenVR/D3D diagnostic surface. Consumers publish copied value data; ROCK owns rendering, capacity, expiry, generation cleanup, and coexistence between publishers.

Use it for probes, contact normals, grip targets, collider outlines, labels, and short-lived runtime diagnostics. It is not a general user-interface API.

Capability and limits

Request DebugOverlayPublication. Publication and clear are game-thread-only and should be called from an owner frame or animation callback.

Exact collider focus is separately gated by ColliderVisualizationOverride; request it only for tools that use slots 88–89.

Current public limits:

LimitValue
Publishers8
Lines per publisher1,024
Text rows per publisher16
Combined rendered lines2,048
Combined rendered text rows64
Text storage per row128 bytes, including the null terminator
Maximum publication lease120 frames

The per-publisher limits are admission limits. Combined renderer limits mean a crowded overlay may display only a bounded subset; diagnostics must not depend on every primitive being rendered.

publishDebugOverlayV1 · slot 51

RockProviderResultV1 publishDebugOverlayV1(
std::uint64_t ownerToken,
const RockProviderDebugOverlayPublicationV1* publication);

Transactionally replaces this owner's complete retained publication. ROCK copies line and text arrays during the call and never retains the consumer's buffers.

The publication requires:

  • exact structure size and supported version;
  • a nonzero lease, clamped to 120 frames;
  • matching optional generation guards;
  • non-null arrays whenever their corresponding count is nonzero;
  • counts within the per-publisher limits.

Publishing zero lines and zero text rows clears this owner's content and returns Ok.

Lines

Each RockProviderDebugOverlayLineV1 contains game-space start/end points and RGBA color. Every coordinate must be finite and every color component must be between 0.0 and 1.0.

RockProviderDebugOverlayLineV1 normal{};
normal.startGame[0] = contact.x;
normal.startGame[1] = contact.y;
normal.startGame[2] = contact.z;
normal.endGame[0] = contact.x + surfaceNormal.x * 4.0f;
normal.endGame[1] = contact.y + surfaceNormal.y * 4.0f;
normal.endGame[2] = contact.z + surfaceNormal.z * 4.0f;
normal.color[0] = 0.25f;
normal.color[1] = 0.90f;
normal.color[2] = 0.70f;
normal.color[3] = 1.0f;

Text

Each RockProviderDebugOverlayTextV1 has null-terminated text, RGBA color, and textSize between 0.5 and 8.0.

  • With no flags, x and y place the text in overlay coordinates.
  • With WorldAnchored, worldAnchorGame must be finite and anchors the label in game space.

Use snprintf or another bounded writer so the 128-byte text buffer is always null-terminated.

RockProviderDebugOverlayTextV1 label{};
label.flags = static_cast<std::uint32_t>(
RockProviderDebugOverlayTextFlagV1::WorldAnchored);
std::snprintf(label.text, sizeof(label.text), "magazine · body %u", bodyId);
label.textSize = 1.5f;
label.color[0] = 0.95f;
label.color[1] = 0.82f;
label.color[2] = 0.35f;
label.color[3] = 1.0f;
label.worldAnchorGame[0] = partPoint.x;
label.worldAnchorGame[1] = partPoint.y;
label.worldAnchorGame[2] = partPoint.z;

Complete publication

using namespace rock::provider;

std::array<RockProviderDebugOverlayLineV1, 1> lines{ normal };
std::array<RockProviderDebugOverlayTextV1, 1> text{ label };

RockProviderDebugOverlayPublicationV1 publication{};
publication.lineCount = static_cast<std::uint32_t>(lines.size());
publication.textCount = static_cast<std::uint32_t>(text.size());
publication.lines = lines.data();
publication.textEntries = text.data();
publication.worldGeneration = frame.worldGeneration;
publication.skeletonGeneration = frame.skeletonGeneration;
publication.providerGeneration = frame.providerGeneration;
publication.leaseFrames = 2;

const auto result = RockProviderApi::inst->publishDebugOverlayV1(
ownerToken, &publication);

The local arrays may go out of scope after the call returns. Refresh only when content changes or before the short lease expires; do not allocate or format large diagnostic batches every frame without need.

clearDebugOverlayV1 · slot 52

RockProviderResultV1 clearDebugOverlayV1(std::uint64_t ownerToken);

Removes this owner's retained geometry and text. The call is callback-only and requires the same capability. It is safe to clear explicitly before consumer unregister; generation change, lease expiry, callback fault, and provider loss are automatic cleanup paths.

Exact collider focus · slots 88–89

setColliderVisualizationOverrideV1 is a separate diagnostic authority. It temporarily replaces the complete config-driven collider presentation with one exact body from the current equipped-weapon generation.

RockProviderColliderVisualizationRequestV1 focus{};
focus.weaponGenerationKey = frame.weaponGenerationKey;
focus.bodyId = selectedBodyId;
focus.partKind = selectedPartKind; // descriptive only
focus.leaseFrames = 2;
focus.worldGeneration = frame.worldGeneration;
focus.skeletonGeneration = frame.skeletonGeneration;
focus.providerGeneration = frame.providerGeneration;

const auto focused =
RockProviderApi::inst->setColliderVisualizationOverrideV1(
ownerToken, &focus);

The body must exist in ROCK's complete generation-bound weapon evidence catalog. It is not limited to the compact weaponBodyIds[8] frame prefix. partKind is only a label; body ID plus weapon generation is authoritative.

Refresh from an owner callback while focus is desired. Call clearColliderVisualizationOverrideV1(ownerToken) or let the rolling lease expire to restore the unchanged normal overlay. Request the separate ColliderVisualizationOverride capability and check supportsColliderVisualizationOverrideV1().

Result handling

ResultTypical cause
OkPublication copied or owner content cleared.
WrongThreadCalled outside ROCK's owner callback boundary.
InvalidSizePublication structure is not the exact current size.
UnsupportedVersionPublication version is zero or newer than the provider.
InvalidArgumentBad count/pointer, zero lease, bad row, non-finite coordinate, out-of-range color/text size, or unterminated text.
WorldNotReady / NotReadyA nonzero generation guard no longer matches or provider state is unavailable.
NotReadyROCK cannot currently render the publication.
PermissionDeniedCapability was not granted.
CapacityFullAll eight publisher slots are occupied.
TargetUnavailableCollider focus names an old weapon generation.
TargetInvalidCollider focus names a body outside the complete current weapon catalog.

Good overlay behavior

  • Give every subsystem a stable color and keep alpha readable in VR.
  • Prefer short lines and a few labels over dense walls of text.
  • Publish only diagnostics the user explicitly enabled.
  • Keep generation guards populated so stale world geometry disappears.
  • Avoid per-frame heap churn and string formatting in callbacks.
  • Never use overlay visibility as gameplay state; it is diagnostic and bounded.