Skip to main content

Animation, authored poses, and hand visuals

ROCK sits between native Fallout animation, the live VR body, equipped-weapon interaction, and final hand presentation. These APIs provide explicit phase boundaries and leased ownership so an animation addon can cooperate without detouring or overwriting another system blindly.

The intended separation is:

  • authority requests say which authored animation regions should survive;
  • runtime publication says whether the addon executing that authority is healthy;
  • phase callbacks provide stable capture and presentation boundaries;
  • pose readback exposes value snapshots;
  • hand visual authority publishes final hand/finger presentation through ROCK's FRIK bridge.

Native animation authority

setNativeAnimationAuthorityV1 · slot 39

RockProviderResultV1 setNativeAnimationAuthorityV1(
std::uint64_t ownerToken,
const RockProviderNativeAnimationAuthorityRequestV1* request);

Requires NativeAnimationAuthority. It publishes a rolling lease for one or more authored regions:

FlagRegion retained from authored native animation
ArmsBoth collarbone-to-hand chains.
HandsHand roots and finger/thumb descendants.
WeaponWeapon and WeaponLeft.
ReloadPoseConvenience combination of all three.

ROCK keeps the authored relationship between the two arms, hands, and weapon, then anchors those trees to the visible first-person weapon/controller world frame. Character root, center of mass, torso, head, and legs remain owned by the live VR body.

At least one implemented flag and a nonzero lease are required. Leases are clamped to 1,200 frames. Generation guards are optional when zero and exact when nonzero. Multiple registered owners can contribute authority flags; the state query reports their aggregate.

clearNativeAnimationAuthorityV1 · slot 40

RockProviderResultV1 clearNativeAnimationAuthorityV1(
std::uint64_t ownerToken);

Idempotently removes this owner's authority contribution. Authority also ends on lease expiry, generation change, callback fault, unregister, or provider loss.

getNativeAnimationAuthorityStateV1 · slot 41

bool getNativeAnimationAuthorityStateV1(
RockProviderNativeAnimationAuthorityStateV1* outState);

Returns the current aggregate flags, active owner count, capture count/sequence, and runtime status. The structure must carry the exact current size.

Status flags:

FlagMeaning
HookInstalledThe native-animation runtime installed its validated entry point.
RuntimeEnabledThe runtime executor is enabled.
CaptureValidIts most recent authored capture is usable.
LocalReloadTestLeaseActiveA local diagnostic/test lease is active.
HookInstallFailedThe executor failed to establish its hook.
ThreadMismatchCapture or application ran on the wrong thread.
CaptureFaultRuntime capture failed.
RuntimeProviderAvailableROCK currently has a live runtime-provider publication.

Authority being requested does not prove it is being executed. Require the positive runtime flags your feature needs and fail closed on fault flags.

Animation phase callbacks

registerAnimationPhaseCallbackV1 · slot 42

RockProviderResultV1 registerAnimationPhaseCallbackV1(
std::uint64_t ownerToken,
RockProviderAnimationPhaseCallbackV1 callback,
void* userData,
std::uint64_t* outCallbackToken);

Requires AnimationPhases. One callback is allowed per owner, with 16 slots globally. The same callback receives each phase through RockProviderAnimationPhaseContextV1::phase:

PhaseSafe purpose
NativeGraphOutputCapture the validated native player graph output before downstream native scene and hFRIK writers. Do not mutate the graph here.
BeforeRockRead inputs or publish values before ROCK mutates weapon/hand presentation.
AfterRockObserve the result after ROCK's interaction update.
CompleteFinal frame boundary after all visual writers.

The context also reports frame time, whether ROCK/provider/skeleton are ready, menu/config blocking, whether visual writes are allowed, active authority flags, and generation values.

Callbacks execute on ROCK's game-thread boundary. They must be bounded, must not throw across the ABI, and must not retain pointer-valued scene identities. A callback fault revokes every stateful resource and callback owned by that consumer.

unregisterAnimationPhaseCallbackV1 · slot 43

RockProviderResultV1 unregisterAnimationPhaseCallbackV1(
std::uint64_t ownerToken,
std::uint64_t callbackToken);

Prevents future dispatch copies. It is not a quiescence barrier for an invocation already copied for dispatch, so callback state and userData must remain alive until that invocation returns.

Equipped weapon grip readback

getEquippedWeaponGripStateV1 · slot 44

bool getEquippedWeaponGripStateV1(
std::uint64_t ownerToken,
RockProviderEquippedWeaponGripStateV1* outState);

Requires EquippedWeaponGripState and succeeds only from an owner frame or animation callback. It exposes ROCK's current value-only grip solution:

  • weapon form ID and generation;
  • weapon world transform;
  • exact right/left hand targets in weapon-local space;
  • firing-hand/two-hand/ownership validity flags.

weaponNode is a current-frame, non-owning identity witness. Never dereference or retain it. Use the form ID, generation key, and copied transforms for normal consumer logic.

Hand visual authority

setHandVisualAuthorityV1 · slot 45

RockProviderResultV1 setHandVisualAuthorityV1(
std::uint64_t ownerToken,
const RockProviderHandVisualAuthorityRequestV1* request);

Requires HandVisualAuthority and is callback-only. Publish a right or left hand with either or both flags:

  • WorldTransform supplies an exact game-space hand transform;
  • FingerLocalTransforms supplies selected transforms from the fixed 15-bone finger-local array.

Rules:

  • priority must be between -10,000 and 10,000;
  • the lease must be nonzero and is clamped to 120 frames;
  • selected transforms must be finite;
  • finger publication requires a nonzero 15-bit mask and only masked rows are consumed;
  • optional generation guards prevent writes after world/skeleton/provider replacement;
  • the hFRIK visual-authority bridge and skeleton must be ready.

ROCK derives an owner-unique tag and routes the publication through the shared FRIK priority system. Refresh a short lease each frame while ownership is intended.

clearHandVisualAuthorityV1 · slot 46

RockProviderResultV1 clearHandVisualAuthorityV1(
std::uint64_t ownerToken,
RockProviderHand hand);

Also callback-only. Pass Right or Left to clear one hand, or None to clear both. TargetUnavailable means the owner had no matching active publication.

Native animation runtime publication

publishNativeAnimationRuntimeV1 · slot 47

RockProviderResultV1 publishNativeAnimationRuntimeV1(
std::uint64_t ownerToken,
const RockProviderNativeAnimationRuntimePublicationV1* publication);

This is for the addon that actually executes native animation authority, not for every animation consumer. It requires NativeAnimationRuntimeProvider and there can be only one runtime-provider owner at a time.

Publish the executor's status flags, captured transform count, monotonically meaningful capture sequence, generation guards, and a nonzero lease. Runtime leases are clamped to 120 frames. ROCK adds RuntimeProviderAvailable itself; do not publish that derived flag.

clearNativeAnimationRuntimeV1 · slot 81

RockProviderResultV1 clearNativeAnimationRuntimeV1(
std::uint64_t ownerToken);

Explicitly withdraws the runtime health publication. It returns OwnerConflict if another consumer currently owns that singleton role.

Authored and presented pose readback

getSelectedAuthoredGripPoseV1 · slot 70

RockProviderResultV1 getSelectedAuthoredGripPoseV1(
std::uint64_t ownerToken,
RockProviderAuthoredGripPoseV1* outPose);

Requires PoseReadback and is callback-only. It returns the currently selected authored grip variant:

  • weapon identity and generation;
  • source (LiveEquippedGraph, NativeIdlePreharvest, or RuntimeCanonical);
  • variant and capture sequence;
  • right/left hand-in-weapon transforms;
  • per-hand 15-bone finger-local arrays and validity masks;
  • world/skeleton/provider generations.

Honor the individual validity flags and masks. A valid pose may contain one hand without the other, or hand roots without finger data.

getPresentedHandPoseV1 · slot 71

RockProviderResultV1 getPresentedHandPoseV1(
std::uint64_t ownerToken,
RockProviderHand hand,
RockProviderPresentedHandPoseV1* outPose);

Requires PoseReadback and is callback-only. This is the final presented hand after visual-authority writers, including:

  • optional hand-world transform;
  • selected finger-local transforms;
  • frame and presentation sequences;
  • root-flattened readback indication;
  • generation values.

Use authored pose data to understand intended weapon-relative placement. Use presented pose data to understand what the player actually sees.

Complete pattern: preserve animation, then present the hand

using namespace rock::provider;

void ROCK_PROVIDER_CALL onAnimationPhase(
const RockProviderAnimationPhaseContextV1* context,
void* userData)
{
auto& state = *static_cast<AnimationClient*>(userData);
if (!context || context->phase != RockProviderAnimationPhaseV1::AfterRock) {
return;
}

const auto writesAllowed =
(context->flags & static_cast<std::uint32_t>(
RockProviderAnimationPhaseContextFlagV1::VisualWritesAllowed)) != 0;
if (!writesAllowed) {
return;
}

RockProviderAuthoredGripPoseV1 authored{};
if (RockProviderApi::inst->getSelectedAuthoredGripPoseV1(
state.ownerToken, &authored) != RockProviderResultV1::Ok) {
return;
}

const auto rightValid =
(authored.flags & static_cast<std::uint32_t>(
RockProviderAuthoredGripPoseFlagV1::RightHandValid)) != 0;
if (!rightValid) {
return;
}

RockProviderHandVisualAuthorityRequestV1 visual{};
visual.hand = RockProviderHand::Right;
visual.flags = static_cast<std::uint32_t>(
RockProviderHandVisualAuthorityFlagV1::WorldTransform);
visual.priority = 200;
visual.worldTransform = composeWeaponWorldWith(
state.weaponWorld, authored.rightHandInWeapon);
visual.leaseFrames = 2;
visual.worldGeneration = context->worldGeneration;
visual.skeletonGeneration = context->skeletonGeneration;
visual.providerGeneration = context->providerGeneration;

const auto result = RockProviderApi::inst->setHandVisualAuthorityV1(
state.ownerToken, &visual);
if (result != RockProviderResultV1::Ok) {
state.stopRefreshingVisuals();
}
}

The authority request itself can be refreshed outside this callback:

RockProviderNativeAnimationAuthorityRequestV1 authority{};
authority.flags = static_cast<std::uint32_t>(
RockProviderNativeAnimationAuthorityFlagV1::ReloadPose);
authority.leaseFrames = 3;
authority.worldGeneration = snapshot.worldGeneration;
authority.skeletonGeneration = snapshot.skeletonGeneration;
authority.providerGeneration = snapshot.providerGeneration;

RockProviderApi::inst->setNativeAnimationAuthorityV1(ownerToken, &authority);

Failure and cleanup rules

  • Never write visuals during NativeGraphOutput; it is a capture boundary.
  • Never call callback-only pose or visual APIs from a worker thread.
  • Keep callback/user data alive through an already-dispatched invocation.
  • Treat runtime health separately from requested authority.
  • Stop publication on generation change and rediscover current pose state.
  • Clear hand visuals, runtime publication, and native authority explicitly on shutdown; lease expiry is the safety net, not the normal lifecycle.