Skip to main content

Production integration checklist

Use this before shipping a plugin that statically links RPS Framework. Passing the framework tests proves the package contract; the consumer must still prove its own object discovery, lifecycle, policy, and runtime integration.

Build and package

  • Link the complete matching RPS::Runtime and RPS::Addresses package.
  • Compile the consumer as C++23 with an ABI-compatible MSVC toolset.
  • Match the packaged library's MSVC runtime (/MT for the staged Release package) or build RPS with the consumer's toolchain.
  • Do not vendor selected .cpp files, copied layouts, or raw RVAs.
  • Keep one RPS package version in the build; do not mix headers and libraries from different stage generations.
  • Build the final plugin configuration and run the framework package- consumer test in addition to the consumer's own tests.

F4SE and runtime gate

  • Keep F4SEVR query compatibility separate from executable file version.
  • Never compare QueryInterface::RuntimeVersion() with the VR executable constant.
  • Confirm VR identity and exact Fallout4VR.exe 1.2.72.0 before hooks or layout-dependent access.
  • Require RuntimeModule::detect() success before constructing an active RPS subsystem.
  • Validate exact entry bytes/targets before every consumer-owned hook or patch.
  • Disable only the dependent feature when the runtime or native boundary is unavailable; do not continue through a zero address.

Borrowed identity

  • Obtain actors, worlds, graph objects, scene objects, collision objects, descriptors, and body IDs from a lifecycle-safe current source.
  • Invalidate cached identity on world/load/unload/controller/skeleton/scene generation changes.
  • Never retain a raw pointer unless the returned RPS type explicitly owns a reference.
  • Treat addresses in snapshots/results as diagnostics only.
  • Pair body IDs with their world and owning object generation; do not treat an hknp slot as immortal.
  • Use resolveCollisionObjectBody() with both expected owner and world witnesses when crossing the collision-object boundary.

Threads and physics epochs

  • Acquire a guard for the exact world used by a body/constraint/shape-cast operation.
  • Keep read/write guard scope synchronous and narrow.
  • Do not move, cache, or share a guard between callbacks.
  • Keep game/frame-only actor, scene, audio, light, and pathing calls outside physics.
  • Do not wrap self-locking gravity, character-controller insertion, or Bethesda closest-ray calls in an external write guard.
  • Keep generator-output and root-pose views inside the callback that supplied their storage.
  • Keep hot callbacks allocation-free, bounded, non-blocking, and free of repeated formatted logging.

Inputs, units, and buffers

  • Value-initialize requests and preserve zero in reserved fields.
  • Reject or sanitize NaN/infinite vectors, transforms, scales, forces, angles, and time steps before publication.
  • Label game-unit and Havok-unit values in consumer names and types.
  • Do not apply game/Havok scale twice.
  • Use actual physics substep timing for drives instead of render delta.
  • Provide fixed-capacity output spans and inspect written, dropped, invalid, and truncated counts.
  • Treat a successful raycast miss as a valid query result.

Native ownership

  • Keep every source ShapeHandle alive through the create call; release the caller reference only after the successful native owner has retained it.
  • Keep GeneratedBodyService alive through body retirement and its eight completed post-solve grace steps.
  • Service generated-body cross-thread retirement on the constructing thread.
  • Keep ConstraintService alive and drain its pending queue under a matching write guard.
  • Detach every native user before resetting a PositionMotor.
  • Release graph-manager leases and current path requests on the engine- owning thread.
  • Fade/release every active NativeSoundHandle.
  • Reset every PointLight on its creation thread and inspect unknown unregister state rather than retrying blindly.

Behavior and restore paths

  • Define which subsystem owns each motion type, collision filter, gravity factor, actor authority bit, constraint, light, sound, and graph mutation.
  • Record the intended final state, not just the previous value, when other systems can legitimately change the same field.
  • Do not add a hidden second behavior path around a rejected RPS operation.
  • Treat OwnerConflict, generation change, wrong thread, and unsafe epoch as boundaries—not busy-retry conditions.
  • Keep direct ragdoll constraint mutation and live world-from-model compensation out of production integrations; they are not stable APIs.

Hooks and global state

  • Assign exactly one owner to each process-global hook or mutable global.
  • Preflight all callsites/slots as one group before applying a group patch.
  • Call the original collision predicate before suppression policy.
  • Keep hook readers lock-free/allocation-free where required.
  • Own callback/trampoline storage until all engine readers quiesce.
  • Roll back partial patch transactions and report rollback failure.
  • Do not treat HookPatch as a trampoline allocator or shutdown barrier; it supplies validation and transactional pointer/call rewriting only.

Shutdown

  • Stop new work before removing the objects/services it can target.
  • Quiesce hooks and callbacks before destroying their backing storage.
  • Release actor/path/graph references on the correct thread.
  • Release audio and lighting before scene/renderer managers disappear.
  • Retire constraints before generated bodies they reference.
  • Observe the generated-body physics grace period before releasing native object graphs.
  • Use world-loss shutdown only after no later world reader can run.
  • Invalidate borrowed identity and finally clear the runtime service.

Consumer validation matrix

CaseExpected behavior
Run outside Fallout4VR.exeRuntimeModule rejects the process; no native call.
Unsupported executable versionDependent feature remains disabled.
World changes between framesOld world/body generation is rejected and rebuilt.
Body slot is recycledOwner/world witness prevents accepting stale identity.
Physics TLS cannot be readGuard/game-thread wrapper fails closed.
Output span is too smallTruncation/dropped count is surfaced; no overrun.
Generator callback returnsNo track, palette, driver, or skeleton pointer survives.
Cross-thread body/constraint destructionWork enters bounded retirement queue and is serviced by owner.
Audio/light manager changesStatus exposes replacement/unavailability; no speculative double cleanup.
Native hit applies but cleanup failsApplied side effect remains reported and is not repeated.
One hook site mismatchesGroup preflight fails before partial installation.
DLL shutdown with an active detourShutdown waits for reader/callback quiescence before storage is destroyed.

In-game smoke test

After the first integration or any change to hooks, object discovery, or ownership lifecycle:

  1. Confirm F4SE reports the plugin loaded correctly.
  2. Confirm the plugin log reports successful executable detection.
  3. Exercise feature creation and normal cleanup.
  4. Exercise save/load, cell/world transition, actor unload, and menu/pause boundaries relevant to the feature.
  5. Confirm no stale bodies, constraints, sounds, lights, hooks, or authority state remain.
  6. Inspect failure-stage diagnostics without enabling retry spam.