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::RuntimeandRPS::Addressespackage. - Compile the consumer as C++23 with an ABI-compatible MSVC toolset.
- Match the packaged library's MSVC runtime (
/MTfor the staged Release package) or build RPS with the consumer's toolchain. - Do not vendor selected
.cppfiles, 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.exe1.2.72.0before 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
ShapeHandlealive through the create call; release the caller reference only after the successful native owner has retained it. - Keep
GeneratedBodyServicealive through body retirement and its eight completed post-solve grace steps. - Service generated-body cross-thread retirement on the constructing thread.
- Keep
ConstraintServicealive 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
PointLighton 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
HookPatchas 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
| Case | Expected behavior |
|---|---|
Run outside Fallout4VR.exe | RuntimeModule rejects the process; no native call. |
| Unsupported executable version | Dependent feature remains disabled. |
| World changes between frames | Old world/body generation is rejected and rebuilt. |
| Body slot is recycled | Owner/world witness prevents accepting stale identity. |
| Physics TLS cannot be read | Guard/game-thread wrapper fails closed. |
| Output span is too small | Truncation/dropped count is surfaced; no overrun. |
| Generator callback returns | No track, palette, driver, or skeleton pointer survives. |
| Cross-thread body/constraint destruction | Work enters bounded retirement queue and is serviced by owner. |
| Audio/light manager changes | Status exposes replacement/unavailability; no speculative double cleanup. |
| Native hit applies but cleanup fails | Applied side effect remains reported and is not repeated. |
| One hook site mismatches | Group preflight fails before partial installation. |
| DLL shutdown with an active detour | Shutdown 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:
- Confirm F4SE reports the plugin loaded correctly.
- Confirm the plugin log reports successful executable detection.
- Exercise feature creation and normal cleanup.
- Exercise save/load, cell/world transition, actor unload, and menu/pause boundaries relevant to the feature.
- Confirm no stale bodies, constraints, sounds, lights, hooks, or authority state remain.
- Inspect failure-stage diagnostics without enabling retry spam.