Skip to main content

Your first integration

Create one durable framework service after your plugin reaches a safe runtime lifecycle point. Detect the executable once, retain the value-only RuntimeModule, and keep world/object discovery separate from the checked operation that uses those borrowed objects.

Minimal service

RpsPhysicsService.h
#pragma once

#include <RPS/Runtime/PhysicsApi.h>
#include <RPS/Runtime/RuntimeModule.h>
#include <RPS/Runtime/WorldAccess.h>

class RpsPhysicsService final
{
public:
[[nodiscard]] bool start() noexcept;
void stop() noexcept;

[[nodiscard]] bool setBodyVelocity(
void* hknpWorld,
RPS::Runtime::Physics::BodyId body,
const RPS::Runtime::Physics::Vector4& linear,
const RPS::Runtime::Physics::Vector4& angular) const noexcept;

private:
RPS::Runtime::RuntimeModule module_{};
bool ready_{};
};
RpsPhysicsService.cpp
#include "RpsPhysicsService.h"

using namespace RPS::Runtime;
using namespace RPS::Runtime::Physics;

bool RpsPhysicsService::start() noexcept
{
module_ = RuntimeModule::detect();
ready_ = static_cast<bool>(module_);
return ready_;
}

void RpsPhysicsService::stop() noexcept
{
// Retire owned constraints, bodies, sounds, and scene objects before this.
ready_ = false;
module_ = {};
}

bool RpsPhysicsService::setBodyVelocity(
void* hknpWorld,
BodyId body,
const Vector4& linear,
const Vector4& angular) const noexcept
{
if (!ready_ || !hknpWorld || !body.valid() ||
!linear.finite() || !angular.finite()) {
return false;
}

WorldWriteGuard write{module_, hknpWorld};
if (!write.active()) {
return false;
}

Api physics{module_, hknpWorld};
return physics.setVelocityDeferred(write, body, linear, angular);
}

This example does four important things:

  1. It rejects every unsupported executable before resolving native calls.
  2. It does not store the borrowed hknpWorld or body ID as immortal state.
  3. It acquires an explicit write epoch for the exact world being mutated.
  4. It treats a failed wrapper result as no authority to continue.

Call it only with current engine identity

RPS does not invent a world, actor, scene node, graph driver, collision object, or body ID. Obtain those from a lifecycle-safe source in your plugin and pass them for one synchronous operation. Re-resolve or invalidate them when the game loads, unloads, changes world, rebuilds a skeleton, replaces a controller, or destroys the owning scene object.

Do not cache BodyId alone as permanent identity. hknp slots can be recycled. Pair it with the world/object generation already owned by your plugin, or re-resolve it through resolveCollisionObjectBody() using both expected scene owner and expected world witnesses.

Keep the F4SE gate separate

RuntimeModule::detect() verifies the actual PE image and executable file version. It does not replace your F4SE Query/Load contract.

In F4SEVR 0.6.21, QueryInterface::RuntimeVersion() is a flat-compatibility value, not the Fallout4VR.exe file version. Do not compare that query value with RUNTIME_VR_1_2_72. Confirm VR identity and exact executable version in the runtime/module domain before installing hooks or using layout-dependent features. RPS performs its own exact runtime check again at its boundary.

Read before you mutate

When an operation depends on current body state, acquire a read guard and copy a snapshot:

WorldReadGuard read{module_, hknpWorld};
if (!read.active()) {
return;
}

Api physics{module_, hknpWorld};
const BodySnapshot snapshot = physics.snapshot(read, bodyId);
if (!snapshot.valid) {
return;
}

const auto motionIndex = snapshot.body.motionIndex;
const auto filterInfo = snapshot.body.collisionFilterInfo;

The result contains copied values. Do not derive a long-lived pointer from an address field in a snapshot.

Result handling rule

Boolean wrappers report only complete success. Structured results preserve more truth: native invocation, accepted/rejected status, postcondition, rollback, cleanup, or ownership uncertainty. Log the status once and stop the operation. Do not blindly retry a native call whose result says ownership or registration state is unknown.

Before adding more subsystems, read the runtime and safety contract and ownership and epoch rules.