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:
| Flag | Region retained from authored native animation |
|---|---|
Arms | Both collarbone-to-hand chains. |
Hands | Hand roots and finger/thumb descendants. |
Weapon | Weapon and WeaponLeft. |
ReloadPose | Convenience 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:
| Flag | Meaning |
|---|---|
HookInstalled | The native-animation runtime installed its validated entry point. |
RuntimeEnabled | The runtime executor is enabled. |
CaptureValid | Its most recent authored capture is usable. |
LocalReloadTestLeaseActive | A local diagnostic/test lease is active. |
HookInstallFailed | The executor failed to establish its hook. |
ThreadMismatch | Capture or application ran on the wrong thread. |
CaptureFault | Runtime capture failed. |
RuntimeProviderAvailable | ROCK 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:
| Phase | Safe purpose |
|---|---|
NativeGraphOutput | Capture the validated native player graph output before downstream native scene and hFRIK writers. Do not mutate the graph here. |
BeforeRock | Read inputs or publish values before ROCK mutates weapon/hand presentation. |
AfterRock | Observe the result after ROCK's interaction update. |
Complete | Final 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:
WorldTransformsupplies an exact game-space hand transform;FingerLocalTransformssupplies selected transforms from the fixed 15-bone finger-local array.
Rules:
prioritymust 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, orRuntimeCanonical); - 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.