Interaction commands
Interaction commands ask ROCK to use its normal interaction paths. The API call validates and queues work; it does not mutate a hand immediately. Success means the command was accepted into the queue, then committed and applied at a ROCK-owned update point.
Require the InteractionCommands capability. Check the named support helper for
each command, and supportsCommandCancellationV1 if you cancel work.
Shared command lifecycle
Request calls return RequestQueued and a non-zero command ID when accepted.
Poll the command or consume InteractionCommandTerminal provider events.
Accepted → Queued → Committed → Applied → Terminal
The public state enum is Queued, Succeeded, Rejected, or Cancelled.
RockProviderCommandStageV1 provides the finer milestone. Acceptance,
commitment, and application frame numbers remain available in the result.
The provider holds up to 32 active commands and 64 retained completed results.
Poll or consume events promptly; an old result can eventually age out and return
RequestNotFound.
requestForceGrabV1 · slot 23
RockProviderResultV1 requestForceGrabV1(
std::uint64_t ownerToken,
const RockProviderForceGrabRequestV1* request,
std::uint64_t* outCommandId);
Queues a near grab through ROCK's normal dynamic grab path. On application, settling, finger posing, collision ownership, haptics, and later release behavior match a normal ROCK grab.
Required request fields:
- exact structure size and V1 version;
- explicit
LeftorRighthand; - non-zero
targetFormId; - optional current
targetBodyIdto narrow identity; - optional generation guards;
- finite
maxDistanceGame.
targetRefr is ignored. The initial public scope is a live loose object or loose
weapon near the requested hand. maxDistanceGame == 0 uses ROCK's own near-grab
range; a positive value sets an additional caller limit.
Set UsePreferredGrabPointGame only when
preferredGrabPointGame[3] contains a finite world-space game-unit point.
Only one queued force grab may reserve a given hand. A second one returns
HandBusy before queueing.
requestForceReleaseV1 · slot 25
RockProviderResultV1 requestForceReleaseV1(
std::uint64_t ownerToken,
const RockProviderForceReleaseRequestV1* request,
std::uint64_t* outCommandId);
Queues release of the object held by an explicit hand. With no velocity flag, this is a gentle physical drop: ROCK uses its normal cleanup path without reusing captured controller throw history.
Flags:
| Flag | Effect |
|---|---|
ImmediateCollisionRestore | Requests immediate collision restoration where the normal release policy allows it. |
RequireMatchingTarget | Reject unless the held object matches supplied form ID and/or body ID. At least one identity must be supplied. |
UseVelocityHavok | Apply caller-provided linear Havok velocity and angular radians/second after detach. |
Explicit velocity vectors are finite-checked but intentionally not clamped. They are trusted physical inputs; validate and bound them in your addon.
requestThrownDropV1 · slot 26
RockProviderResultV1 requestThrownDropV1(
std::uint64_t ownerToken,
const RockProviderThrownDropRequestV1* request,
std::uint64_t* outCommandId);
Queues a release intended to preserve or author throw motion. Without
UseVelocityHavok, ROCK captures the current held release motion before
detaching. With the flag, it uses the supplied linear Havok and angular
radians/second vectors.
ImmediateCollisionRestore and RequireMatchingTarget have the same meaning
as force release. Explicit velocities are finite-checked and not clamped.
Request return values
Before queueing, all three calls can return:
InvalidArgument— null/zero inputs, unsupported flags, non-finite values, missing required identity, or malformed flag-dependent data;InvalidSizeorUnsupportedVersion;HandUnavailableforNoneor an invalid hand;NotReadywhen the provider is not initialized;OwnerNotRegisteredorPermissionDenied;HandBusyfor a conflicting force-grab reservation;CapacityFullwhen the command queue is full;RequestQueuedwith a non-zero ID on acceptance.
World/skeleton/provider guard mismatch may also return WorldNotReady or
NotReady.
getInteractionCommandResultV1 · slot 24
RockProviderResultV1 getInteractionCommandResultV1(
std::uint64_t ownerToken,
std::uint64_t commandId,
RockProviderInteractionCommandResultV1* outResult);
Reads an active or retained result belonging to the caller. The output size must
be at least ROCK_PROVIDER_INTERACTION_COMMAND_RESULT_V1_PREFIX_SIZE (112
bytes). ROCK prefix-copies up to the current 120-byte structure.
Key fields:
kind,state, andfailure— high-level outcome;stageandfailureStage— the lifecycle point reached and where failure was assigned;acceptedFrame,committedFrame,appliedFrame— milestone timing;frameIndex— latest result update frame;- hand, form/body identity, and generation fields.
The legacy targetRefr field is always zero.
Returns RequestNotFound if the ID does not belong to the owner, has aged out,
or never existed.
cancelInteractionCommandV1 · slot 75
RockProviderResultV1 cancelInteractionCommandV1(
std::uint64_t ownerToken,
std::uint64_t commandId);
Cancels a command only while it is still in the provider queue. A successful
cancel stores a terminal Cancelled result and releases a force-grab hand
reservation.
Returns:
Ok— still queued and now cancelled;AlreadyCommitted— provider execution already owns the work;RequestNotFound— unknown, already terminal, or no longer retained;- standard owner/capability/argument errors.
Cancellation is race-aware. AlreadyCommitted means the consumer must observe
the eventual terminal outcome rather than assuming the action did not happen.
Terminal failure reasons
RockProviderInteractionFailureV1 distinguishes:
| Failure | Meaning |
|---|---|
ProviderNotReady | Provider disappeared or was not ready at execution. |
PhysicsWritesBlocked | Lifecycle did not permit the physical operation. |
OwnerNotRegistered | Owner was removed before execution. |
InvalidRequest | Request failed execution-stage validation. |
StaleWorldGeneration | World guard no longer matched. |
StaleSkeletonGeneration | Skeleton guard no longer matched. |
StaleProviderGeneration | Provider guard no longer matched. |
TargetMissing | Requested form/reference could not be resolved. |
TargetUnavailable | Target exists but cannot currently participate. |
TargetBodyMissing | Required body is absent. |
TargetAlreadyOwned | Another interaction owns the target. |
HandInvalid | Hand request is invalid at execution. |
HandDisabled | ROCK collision/interaction has disabled the hand. |
HandBusy | Hand has incompatible active work. |
HandNotHolding | Release/drop hand owns no held object. |
HeldObjectMismatch | RequireMatchingTarget did not match. |
An accepted command can still end rejected for any execution-time condition.
Example: generation-safe force grab
std::uint64_t queueGrab(
std::uint64_t ownerToken,
const RockProviderFrameSnapshot& frame,
RockProviderHand hand,
std::uint32_t targetFormId,
std::uint32_t targetBodyId)
{
if (!hasLifecycleFlag(
frame.lifecycleFlags,
RockProviderLifecycleFlag::PhysicsWriteAllowed)) {
return 0;
}
RockProviderForceGrabRequestV1 request{};
request.hand = hand;
request.targetFormId = targetFormId;
request.targetBodyId = targetBodyId;
request.worldGeneration = frame.worldGeneration;
request.skeletonGeneration = frame.skeletonGeneration;
request.providerGeneration = frame.providerGeneration;
request.maxDistanceGame = 18.0f;
std::uint64_t commandId = 0;
const auto result = RockProviderApi::inst->requestForceGrabV1(
ownerToken, &request, &commandId);
return result == RockProviderResultV1::RequestQueued ? commandId : 0;
}
Example: poll without losing acceptance history
bool commandFinished(
std::uint64_t ownerToken,
std::uint64_t commandId,
bool& succeeded)
{
RockProviderInteractionCommandResultV1 result{};
const auto query = RockProviderApi::inst->getInteractionCommandResultV1(
ownerToken, commandId, &result);
if (query != RockProviderResultV1::Ok) {
return false;
}
switch (result.state) {
case RockProviderInteractionCommandStateV1::Succeeded:
succeeded = true;
return true;
case RockProviderInteractionCommandStateV1::Rejected:
case RockProviderInteractionCommandStateV1::Cancelled:
succeeded = false;
return true;
default:
return false;
}
}
For many concurrent commands, the provider event cursor is more efficient than polling every ID every frame. Keep result polling for targeted status UI or when the event stream reports a gap.