Skip to main content

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.

FunctionCurrent implementation
getVersionReturn the SCISSORS API version.
getModVersionReturn a diagnostic version string.
isReadyReport the runtime's external-API readiness gate.
canGrabActorValidate readiness/request shape, then reject valid live grabs with LiveGrabNotEnabled.
beginActorGrabUse the same rejection path; no actor-grab handle is issued.
updateActorGrabCheck readiness, nonzero handle and request shape only; does not implement a live grab update.
endActorGrabNo-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
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();