SCISSORS runtime and integration
In development. SCISSORS is not part of the current public Nexus releases. This page documents the runtime under development; it is not an installation guide for an available download.
SCISSORS owns active ragdoll, actor collision, contact-derived impulses/damage,
pose reconciliation and actor-physics lifecycle in the RPS stack. It consumes
ROCK provider state and physics/contact coordination. Current source exports SCISSORSAPI_GetApi, but it is a limited development contract and there is no SDK/SCISSORS module. Live actor-grab admission is disabled. Use ROCK Collision for supported contact observations; do not treat this table as an available actor-grabbing feature.
Current actor scope
Actor admission is restricted to loaded, enabled, non-deleted, non-player actors whose race is the authored HumanRace or HumanChildRace. Unknown/custom races and other rigs retain native ownership; body count or ActorTypeNPC alone is not an admission rule. This is a code eligibility policy, not evidence that every admitted actor has been runtime-qualified.
The native-human locomotion path additionally requires active-ragdoll ownership, alive/normal life and knock state, and no furniture authority. Its recent controller-support and pre-physics dispatch corrections belong to SCISSORS. They have not been promoted wholesale into RPS Framework's generic controller wrappers. Do not copy old movement-authoring plans as the current algorithm.
Controller registration/finalization, native movement handoff and distal-arm mapping now have targeted diagnostic paths. References to constraints, body IDs, world state and mapped poses remain lifecycle-bound. The runtime validates referenced bodies against engine pool capacity and coordinates ragdoll guards with ROCK's modular Core/Collision lifecycle.
Configuration and observation
The normal path is the user's Documents known folder plus
My Games/Fallout4VR/Mods_Config/SCISSORS/SCISSORS.ini. SCISSORS owns its settings and
configuration watcher; ROCK's configuration export does not govern this file.
The wheel's SCISSORS page edits that existing configuration through its separate
settings adapter.
[Diagnostics] bEnablePoseComparison defaults false. Explicitly enabling it can
observe human poses even when the main SCISSORS switch is disabled. Therefore a
diagnostic log in a disabled-control run is not proof of active-ragdoll ownership.
sPoseTraceActorFormId is a hexadecimal actor filter; zero leaves detailed actor
tracing unselected, and invalid input disables the detailed trace with a warning.
Validation boundary
Install the runtime as Data/F4SE/Plugins/SCISSORS.dll. Approved native changes
use custom-fast, Release and the relevant existing policy tests. The current
diagnostic work investigates movement/pose differences; source changes, logged
hook installation and successful builds do not prove those visual issues are
resolved. Correlate enabled/control observations with their exact test sessions.
Exported development API
The runtime header is src/api/SCISSORSApi.h; SCISSORSAPI_GetApi() returns the V1 table. This is separate from ROCK and has no RPS SDK CMake target.
| Function | Current implementation |
|---|---|
getVersion | Return the SCISSORS API version. |
getModVersion | Return a diagnostic version string. |
isReady | Report the runtime's external-API readiness gate. |
canGrabActor | Validate readiness/request shape, then reject valid live grabs with LiveGrabNotEnabled. |
beginActorGrab | Use the same rejection path; no actor-grab handle is issued. |
updateActorGrab | Check readiness, nonzero handle and request shape only; does not implement a live grab update. |
endActorGrab | No-op in current source. |
Existence/readiness of this table does not enable its unfinished operations. These limitations are source facts, not a runtime qualification claim.
Complete public declarations: SCISSORSApi.h
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef NOMMNOSOUND
#define NOMMNOSOUND
#endif
#include <cstdint>
#if !defined(_WINDOWS_) && !defined(_INC_WINDOWS)
struct HINSTANCE__;
using HMODULE = HINSTANCE__*;
using LPCSTR = const char*;
using FARPROC = std::intptr_t(__stdcall*)();
extern "C" __declspec(dllimport) HMODULE __stdcall GetModuleHandleA(LPCSTR lpModuleName);
extern "C" __declspec(dllimport) FARPROC __stdcall GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
#endif
namespace scissors::api
{
#if defined(SCISSORS_API_EXPORTS)
#define SCISSORS_PUBLIC_API extern "C" __declspec(dllexport)
#else
#define SCISSORS_PUBLIC_API extern "C" __declspec(dllimport)
#endif
#define SCISSORS_API_CALL __cdecl
inline constexpr std::uint32_t SCISSORS_API_VERSION = 1;
enum class ScissorsApiInitStatus : std::int32_t
{
Ready = 0,
MissingDll = 1,
MissingExport = 2,
NullApi = 3,
VersionTooOld = 4,
};
enum class ScissorsHand : std::uint32_t
{
None = 0,
Right = 1,
Left = 2,
};
enum class ScissorsActorGrabRejectReason : std::uint32_t
{
None = 0,
ApiNotReady = 1,
InvalidRequest = 2,
LiveGrabNotEnabled = 3,
ActorRejected = 4,
};
struct ScissorsActorGrabRequest
{
std::uint32_t size{ sizeof(ScissorsActorGrabRequest) };
std::uint32_t version{ SCISSORS_API_VERSION };
std::uintptr_t actor{ 0 };
std::uint32_t selectedBodyId{ 0x7FFF'FFFF };
ScissorsHand hand{ ScissorsHand::None };
float hitPointGame[3]{};
float grabPivotGame[3]{};
std::uint32_t flags{ 0 };
std::uint32_t reserved[3]{};
};
struct ScissorsActorGrabHandle
{
std::uint64_t value{ 0 };
std::uint32_t actorFormId{ 0 };
std::uint32_t generation{ 0 };
};
struct ScissorsActorGrabDecision
{
std::uint32_t size{ sizeof(ScissorsActorGrabDecision) };
std::uint32_t accepted{ 0 };
ScissorsActorGrabRejectReason reason{ ScissorsActorGrabRejectReason::None };
ScissorsActorGrabHandle handle{};
std::uint32_t reserved[4]{};
};
struct ScissorsApi
{
std::uint32_t size{ sizeof(ScissorsApi) };
std::uint32_t version{ SCISSORS_API_VERSION };
std::uint32_t(SCISSORS_API_CALL* getVersion)();
const char*(SCISSORS_API_CALL* getModVersion)();
bool(SCISSORS_API_CALL* isReady)();
bool(SCISSORS_API_CALL* canGrabActor)(const ScissorsActorGrabRequest* request, ScissorsActorGrabDecision* outDecision);
bool(SCISSORS_API_CALL* beginActorGrab)(const ScissorsActorGrabRequest* request, ScissorsActorGrabDecision* outDecision);
bool(SCISSORS_API_CALL* updateActorGrab)(const ScissorsActorGrabHandle* handle, const ScissorsActorGrabRequest* request);
void(SCISSORS_API_CALL* endActorGrab)(const ScissorsActorGrabHandle* handle);
static inline const ScissorsApi* inst{ nullptr };
static ScissorsApiInitStatus initialize(std::uint32_t minVersion = SCISSORS_API_VERSION)
{
auto* module = GetModuleHandleA("SCISSORS.dll");
if (!module) {
inst = nullptr;
return ScissorsApiInitStatus::MissingDll;
}
using GetApiFn = const ScissorsApi*(SCISSORS_API_CALL*)();
auto* proc = GetProcAddress(module, "SCISSORSAPI_GetApi");
if (!proc) {
inst = nullptr;
return ScissorsApiInitStatus::MissingExport;
}
const auto getApi = reinterpret_cast<GetApiFn>(proc);
const ScissorsApi* api = getApi();
if (!api) {
inst = nullptr;
return ScissorsApiInitStatus::NullApi;
}
if (api->version < minVersion || !api->getVersion || api->getVersion() < minVersion) {
inst = nullptr;
return ScissorsApiInitStatus::VersionTooOld;
}
inst = api;
return ScissorsApiInitStatus::Ready;
}
};
}
SCISSORS_PUBLIC_API const scissors::api::ScissorsApi* SCISSORS_API_CALL SCISSORSAPI_GetApi();