Skip to main content

Touch mechanisms and world tools

The final V1 table families turn ROCK's contact/ownership model into reusable physical mechanisms and bounded world tooling. They remain owner-scoped and game-thread-only; none of them exposes writable Havok or scene pointers.

Required extents and capabilities

FamilyMinimum table extentCapability
Touch targets/state/yieldROCK_PROVIDER_API_V1_TOUCH_GRAB_TARGETS_TABLE_BYTESTouchGrabTargets
Exact equipped handROCK_PROVIDER_API_V1_EQUIPPED_WEAPON_HAND_REQUEST_TABLE_BYTESEquippedWeaponHandlingAuthority
World raycastROCK_PROVIDER_API_V1_WORLD_RAYCASTS_TABLE_BYTESWorldRaycasts
Collider focusROCK_PROVIDER_API_V1_COLLIDER_VISUALIZATION_OVERRIDE_TABLE_BYTESColliderVisualizationOverride

Use supportsTouchGrabTargetsV1, supportsEquippedWeaponHandRequestV1, supportsWorldRaycastsV1, and supportsColliderVisualizationOverrideV1 before enabling each optional feature.

Touch-grab target kinds

KindUse
FixedAnchorAttach a contacting hand to an exact body or bounded wildcard surface without changing that body's motion type.
LimitedHingeExpose bounded angular motion around pivotWorldGame and axisWorldGame.
LimitedPrismaticExpose bounded linear motion along axisWorldGame.

minimumCoordinate, maximumCoordinate, and currentCoordinate describe the mechanism's authored range and current position. All positions are game units; hinge coordinates use the mechanism's documented angular convention in the header/source that authored the target.

Target identity and matching

Every RockProviderTouchGrabTargetV1 has a caller-stable targetId and a nonzero targetGeneration. Republishing the same ID/generation refreshes its lease and values. Increment the generation whenever the backing object, mechanism lifetime, or matching meaning changes.

An exact target supplies bodyId. A wildcard FixedAnchor supplies MatchAnyBody, an intentional allowedLayerMask, and one or more motion flags: MatchStaticMotion, MatchKeyframedMotion, or MatchDynamicMotion.

Exact bodies win before wildcard targets. One wildcard descriptor can own only one resolved body at a time. For two-hand climbing, publish separate right and left descriptors with distinct IDs so each hand can resolve a different wall.

Transactional scopes and rolling leases

setTouchGrabTargetsForScopeV1 replaces one scope atomically. A zero-count set or clearTouchGrabTargetsForScopeV1 removes it. Refresh a short lease inside the owner frame callback only while PhysicsWriteAllowed and your feature's own activation condition are true.

std::array<RockProviderTouchGrabTargetV1, 2> targets{};

targets[0].targetId = rightTargetId;
targets[0].targetGeneration = generation;
targets[0].kind = RockProviderTouchGrabKindV1::FixedAnchor;
targets[0].flags =
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::AllowRightHand) |
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::MatchAnyBody) |
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::MatchStaticMotion);

targets[1] = targets[0];
targets[1].targetId = leftTargetId;
targets[1].flags =
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::AllowLeftHand) |
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::MatchAnyBody) |
static_cast<std::uint32_t>(RockProviderTouchGrabTargetFlagV1::MatchStaticMotion);

for (auto& target : targets) {
target.allowedLayerMask = climbableLayerMask;
target.leaseFrames = 2;
target.worldGeneration = frame.worldGeneration;
target.skeletonGeneration = frame.skeletonGeneration;
target.providerGeneration = frame.providerGeneration;
}

const auto published = RockProviderApi::inst->setTouchGrabTargetsForScopeV1(
ownerToken, scopeToken, targets.data(),
static_cast<std::uint32_t>(targets.size()));

State and safe yield

copyTouchGrabStatesForScopeV1 reports Inactive, Armed, Held, Latched, Yielded, or Invalidated, plus the resolved body/reference, active hand mask, contact point/normal, mechanism coordinate/velocity, release reason, generations, and a transition sequence.

For a FixedAnchor target, surfaceGripMode has the same three-state contract as the aggregate hand snapshot:

  • CollisionAnchor — the Havok shell contact is authoritative;
  • MeshAnchorcontactPointGame is ROCK's visible-mesh anchor;
  • CollisionFallback — mesh acquisition was enabled but ROCK safely retained the Havok shell contact.

Check ContactPointValid before reading contactPointGame. The additional MeshSurfaceAnchor, MeshFingerPose, and MeshCollisionFallback state flags identify mesh authority, an actually active final mesh finger pose, and fallback respectively. A successful mesh anchor does not require a successful finger pose: consumers must not infer MeshFingerPose from MeshSurfaceAnchor.

The target descriptor still answers which target and body ROCK grabbed; surfaceGripMode and contactPointGame answer where and how that fixed surface is anchored. The owner-scoped state and getHandInteractionStateV1 expose the same mode, so a climbing or mechanism consumer can use either view without reconstructing a render-mesh point from Havok geometry.

If another system needs to animate or script a held mechanism, call requestTouchGrabYieldV1. It blocks new acquisition immediately, but release is asynchronous. Wait until the copied state is Yielded before moving the body. A yielded/invalidated descriptor remains non-acquirable until removed or republished with a new target generation.

Exact equipped-weapon hand request

requestEquippedWeaponHandV1 applies only to the weapon already equipped. The caller must own the current handling-authority lease; a left-hand request also requires AmbidextrousHandoff. Supply current weapon form/generation and world/skeleton/provider generations, call on the owner thread, then observe getEquippedWeaponHandlingStateV1 until the effective hand settles.

The request does not equip an inventory item and does not create a second weapon attachment path.

Bounded world raycasts

queryWorldRaycastV1 is callback-only. ROCK normalizes the direction, caps the distance at 8192 game units, applies its validated far-world filter, and limits each owner to the extended-limit budget (currently eight queries per frame).

RockProviderWorldRaycastRequestV1 request{};
request.startGame = start;
request.directionGame = direction;
request.maxDistanceGame = 2048.0f;
request.worldGeneration = frame.worldGeneration;
request.skeletonGeneration = frame.skeletonGeneration;
request.providerGeneration = frame.providerGeneration;

RockProviderWorldRaycastResultV1 hit{};
const auto result = RockProviderApi::inst->queryWorldRaycastV1(
ownerToken, &request, &hit);

A miss is still Ok: hit == 0, hitFraction == 1, and hitPointGame is the requested endpoint. NormalValid is set only when hitNormalGame is usable.

Exact collider visualization focus

setColliderVisualizationOverrideV1 temporarily replaces the complete config-driven collider overlay with one exact body from the current equipped weapon generation. The body may come from the complete evidence catalog, not only the compact eight-ID frame array. Refresh its short lease while focused; clear or let it expire to restore the unchanged normal overlay.

The optional partKind is a label. bodyId plus weaponGenerationKey is the authoritative selection identity.