Skeletal Assembly Architecture

July 15, 2026 ยท View on GitHub

SOT for the modular skeletal assembly framework: lifecycle, registry, types, naming.


Purpose

Move skeletal-actor orchestration, lifecycle, and assembly into C++ while keeping content assets data-driven. Characters are wrappers that consume a definition + capability specs. The framework supports player heroes, NPCs, and future non-character skeletal actors.

This is NOT a rewrite of Blueprint content into C++. It is a move of orchestration and lifecycle into C++.


Plugin Placement

PluginTierOwns
ProjectSkeletalAssemblySystemsAssembly lifecycle, state machine, debug capture
ProjectSkeletalCapabilitiesGameplayAdapter capabilities (Mutable, MotionMatching, LocalFirstPerson)
ProjectObjectCapabilitiesGameplayGeneric capabilities (Lockable, Pickup, Hinged) + FCapabilityRegistry
ProjectObjectResourcesObjectDefinition, meshes, spawn utility
ProjectCharacterGameplayProduction ADefinitionCharacter pawn and character-owned runtime wiring

ProjectSkeletalAssembly must NOT depend on: GAS, Vitals, Mutable, PoseSearch, specific skeletons or bone names.


Ownership Model

Assets choose configuration. C++ owns runtime policy.

The framework owns:

  • Component graph assembly from definition
  • Attachment and visibility policy
  • Lifecycle state machine (Idle -> Assembling -> Ready -> TearingDown)
  • Capability registration and activation ordering
  • Debug capture

Assets and Blueprints keep:

  • Motion Matching AnimBP graphs
  • Retarget AnimBPs
  • Mutable content graphs (CO/COI authoring)
  • Skeleton-specific asset choices

One Registry Model

All capabilities -- object and skeletal -- use the same FCapabilityRegistry.

A door capability and a skeletal capability are the same concept. JSON declares what the thing has, the registry finds the class, the framework attaches it. The difference is runtime lifecycle: some actors need orchestration (hero with assembly phases), most don't (door, simple NPC).

There is NO separate skeletal registry.

Registration:

  • Core always-enabled plugins listed in registry scan config by FName: ProjectObjectCapabilities, ProjectMotionSystem, ProjectSkeletalAssembly
  • External plugins call FCapabilityRegistry::RegisterCapabilityModule() in StartupModule()

Capability IDs (FPrimaryAssetId("CapabilityComponent", "...")):

IDPluginPurpose
Lockable, Pickup, Hinged, Sliding, LootContainer, Audio, ActorWatcherProjectObjectCapabilitiesGeneric object capabilities
SkeletalAssemblyProjectSkeletalAssemblyAssembly lifecycle coordinator
DebugCaptureProjectSkeletalAssemblyRuntime debug capture
MutableCustomizationProjectSkeletalCapabilitiesMutable body customization
MotionMatchingProjectSkeletalCapabilitiesMotion Matching driver (v1 stub)
LocalFirstPersonProjectSkeletalCapabilitiesFirst-person body handling

Anti-patterns:

  • Separate registry per domain
  • Hardcoding external module names in registry
  • Making the registry a UObject (unnecessary GC)
  • Forcing all skeletal actors through assembly orchestration

Assembly Component

USkeletalAssemblyComponent is an opt-in lifecycle coordinator (flat peer on the actor, not a wrapper).

State machine: Idle -> Assembling -> Ready -> TearingDown

When present (SkeletalAssembly capability in definition):

  1. ObjectSpawnUtility processes it first (regardless of JSON array order)
  2. Other skeletal capabilities targeting assembly-managed meshes are deferred
  3. Assembly activates deferred capabilities when state reaches Ready
  4. Assembly deactivates them on TearingDown

When absent: all capabilities fire-and-forget at spawn (simple objects).

This matches the Lyra init state coordinator pattern: peer component manages sibling initialization ordering.


Core Types

TypeLocationPurpose
USkeletalAssemblyComponentProjectSkeletalAssemblyLifecycle state machine, capability management
IAssemblyCapabilityProjectCoreInterface for assembly lifecycle (DIP boundary)
IAssemblyViewConfigSourceProjectCoreInterface for view config data
FAssemblyViewConfigProjectCoreCamera offset data struct
UCharacterDebugCaptureComponentProjectSkeletalAssemblyDebug overlay + JSON capture

Acceptance Criteria

Static architecture:

  • ProjectSkeletalAssembly in Plugins/Systems/, no third-party deps
  • FCapabilityRegistry is the single registry for ALL capabilities
  • SkeletalAssembly is a capability (flat peer coordinator pattern)
  • No separate skeletal registry

Runtime:

  • Hero.json spawns DefinitionCharacter through ProjectObjectSpawn
  • assembly reaches Ready with the configured capabilities active
  • Capture command writes screenshot + JSON sidecar

Reuse:

  • Non-player NPC can use the same assembly core without local-player capabilities

Non-Goals (V1)

  • Rewrite Motion Matching AnimBP graphs into C++
  • Rewrite Mutable content graphs into C++
  • Redesign traversal, foley, or smart-object behavior
  • Force all skeletal actors through assembly orchestration

Naming Standard

Component Layout IDs

Flat PascalCase: DriverBody, WorldBody, LocalBody, Head, BodyCustomization, HeadCustomization, LocalBodyCustomization

Capability IDs

Flat PascalCase FName via FPrimaryAssetId("CapabilityComponent", "..."): MotionMatching, MutableCustomization, LocalFirstPerson, DebugCapture

Section Categories

Flat PascalCase: Animation, Customization, View, Debug

Definition IDs

FPrimaryAssetId with type ObjectDefinition: ObjectDefinition:Hero, ObjectDefinition:Door_GrandPa

Rules

  1. PascalCase everywhere
  2. No underscores in IDs
  3. Singular nouns
  4. No prefixes on IDs (no SK_, SA_, Comp_)
  5. No legacy abbreviations (BodyCustomization not Body_CSK)

Cross-References