Skip to main content

ROCK

RPS SDK · SDK/ROCK · ABI V1

Build on physical interaction, not around it.

ROCK gives other F4SE plugins a shared view of VR hands, equipped weapons, generated collision, contacts, input, animation phases, and bounded interaction authority. Your addon consumes stable values and narrow commands while ROCK keeps ownership of the live interaction runtime.

ABIV1 pre-launch
Table90 callables
ModelPOD / value ABI
RuntimeWindows x64 · FO4VR

How you use it

The SDK is one header. There is nothing to link, and nothing sits between your code and ROCK. You include ROCKProviderApi.h, ask it to find ROCK.dll at runtime, and receive a struct of function pointers. Calling one of those pointers calls straight into ROCK.

YOUR PLUGIN DLL ROCK.dll
┌──────────────────────────────────┐ ┌─────────────────────┐
│ #include "ROCKProviderApi.h" │ │ │
│ │ │ Live runtime: │
│ RockProviderApi::initialize(…) │ │ hands, weapons, │
│ └─ inline, runs HERE: │ │ collision, input │
│ GetModuleHandleA("ROCK.dll")│────▶│ │
│ GetProcAddress(…) │ │ ROCKAPI_GetDescriptorV1
│ fills ::inst │◀────│ └─ hands back the table
│ │ │ │
│ RockProviderApi::inst │ │ │
│ ->registerConsumerV1(…) ──────┼────▶│ the work runs │
│ ->getFrameSnapshot(…) ──────┼────▶│ inside ROCK │
└──────────────────────────────────┘ └─────────────────────┘

The whole integration shape, taken from MinimalProviderConsumer.cpp:

#include "ROCKProviderApi.h" // one header, nothing linked

RockProviderApi::initialize( // inline: locate ROCK.dll and
ROCK_PROVIDER_API_VERSION, // negotiate the table extent
ROCK_PROVIDER_API_V1_EXTERNAL_BODY_SCOPES_TABLE_BYTES);

RockProviderApi::inst->registerConsumerV1(...); // call ROCK directly,
RockProviderApi::inst->getProviderLimitsV1(...); // through its table

RockProviderApi::inst is ROCK's function table.

Two things this implies, both easy to get wrong:

  • The helpers are not a layer. initialize, hasFeatureBitV1, hasLifecycleFlag, and the supports…V1 family are inline functions that compile into your DLL. They are bit tests, size checks, and module lookup — convenience, not API surface. Only the 90 table slots execute inside ROCK.
  • The examples are templates, not a dependency. Copy an example, rename its plugin definition, and make it yours. Nothing you ship should link or include the example scaffolding; it exists so seventeen samples do not each rewrite the same F4SE bootstrap.

Because the table is resolved at runtime, the compiler cannot tell you that a slot is missing from the ROCK build a user actually has installed. That is what minProviderApiByteSize and the feature-bit checks replace — they are the runtime substitute for a link-time error.

What ROCK does for an addon

ROCK is the physical-interaction provider. It already knows which physical hand is primary, what each hand is touching or holding, how the equipped weapon is composed, which weapon parts are interactive, what collision is available, and when the world or skeleton is safe to use. The SDK turns that knowledge into a bounded C ABI that another plugin can consume without compiling against ROCK's internals.

The relationship is intentionally narrow:

  1. Your plugin finds ROCK.dll and negotiates the table extent it needs.
  2. It registers a named consumer and asks only for the capabilities it uses.
  3. ROCK returns an owner token and the exact capabilities granted.
  4. Your plugin reads snapshots or publishes short-lived requests through the table.
  5. ROCK validates ownership, generation, capacity, and thread rules before it accepts work.
  6. Expiry, provider loss, callback faults, or consumer shutdown revoke owned state deterministically.

What you can rely on

ROCK's source is public and worth reading. What this module gives you is the part that is stable: the header, the versioned contract, and examples built on it. Code that reaches past that into ROCK's internals is not covered by the API version and can break on any release.

Within that contract, consuming the SDK does not make your plugin part of ROCK. There is no static link to ROCK and no authority to mutate arbitrary scene or Havok state. Your DLL keeps its own F4SE bootstrap, lifecycle, and shutdown ordering.

The surfaces you can use

Choose a path

If this is your first ROCK consumer, follow these in order:

  1. Install the header and configure your build.
  2. Create a registered owner callback.
  3. Read the runtime contract, especially table negotiation, generation guards, leases, and callback lifetime.
  4. Find the feature family in the complete API index.
  5. Start from a minimal example mod or a focused recipe instead of assembling stateful calls in isolation.

If you are deciding what to make, start with What to build with ROCK.

:::warning Pre-launch V1

The numeric API version remains 1 while the pre-launch table grows by appending functions. Never treat getVersion() == 1 as sufficient discovery. Require the table-byte constant for your last function and check its feature bit before calling it. The SDK helpers make this straightforward.

:::

Consumer promise

The public contract is ROCKProviderApi.h. The API uses fixed-size, standard-layout values; bounded arrays, registries, and cursor streams; explicit capability grants; and fail-closed lifecycle rules. Expensive data is produced or copied only when requested. Merely registering a capability does not enable all of its possible work.

Where a page says callback-only, call that surface from a ROCK owner frame or animation-phase callback on the game thread. Where a structure contains a pointer-sized field, treat it as a temporary identity witness—never as ownership or permission to dereference engine memory.