Add animation sync docs and descriptor infra

Add detailed design and implementation scaffolding for TiltedEvolution-style animation synchronization. New documentation: animation-architecture-alignment.md, animation-sync-analysis.md, f4-animation-descriptor.md, phase1-3-completion-report.md and updates to dev-log.md describing Phase 1-3 progress. Plugin: introduce F4AnimationDescriptor (plugin/include/F4AnimationDescriptor.h, plugin/src/F4AnimationDescriptor.cpp) and extend remote state handling (plugin/include/F4TRemotePlayerState.h). Integrate protocol/state extensions and refactor points: plugin/src/F4TNetworking.cpp now parses optional actorStateFlags and actionEvents; proxy controller and animation sync files (F4TProxyActorController.cpp, F4TProxyAnimationSync.cpp) updated to support descriptor-based bulk variable snapshots and actor state replication. Server/tools: update server/dev_server_app.py and server/fake_client.py to handle and display the new optional fields. These changes are additive and backward-compatible and set up phases for action capture, dynamic proxy spawn, and action-replay integration.
This commit is contained in:
2026-06-03 16:52:47 +12:00
parent 9d05344e4c
commit a6b2505242
14 changed files with 2453 additions and 105 deletions
+130
View File
@@ -0,0 +1,130 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include "RE/I/IAnimationGraphManagerHolder.h"
namespace F4T::AnimationDescriptor
{
// Forward declarations
struct AnimationVariableSnapshot;
class F4AnimationDescriptor;
// Variable type enumeration
enum class VariableType : std::uint8_t
{
Float,
Bool,
Int,
};
// Snapshot of animation graph variables (used for action replay)
struct AnimationVariableSnapshot
{
std::vector<float> floats;
std::vector<bool> bools;
std::vector<std::uint32_t> ints;
};
// Animation descriptor for a specific graph type (humanoid, creature, etc.)
// Uses indexed variable cache read/write instead of string-based lookups
// This matches TiltedEvolution's descriptor model for optimal performance
class F4AnimationDescriptor
{
public:
// Get the humanoid master behavior graph descriptor (most common)
static const F4AnimationDescriptor& GetHumanoidDescriptor();
// Prevent copies (singleton pattern)
F4AnimationDescriptor(const F4AnimationDescriptor&) = delete;
F4AnimationDescriptor& operator=(const F4AnimationDescriptor&) = delete;
// Get variable names by index
const std::string& GetFloatVariableName(std::size_t a_index) const;
const std::string& GetBoolVariableName(std::size_t a_index) const;
const std::string& GetIntVariableName(std::size_t a_index) const;
// Get index by variable name (for reverse lookup)
std::size_t GetFloatVariableIndex(const std::string& a_name) const;
std::size_t GetBoolVariableIndex(const std::string& a_name) const;
std::size_t GetIntVariableIndex(const std::string& a_name) const;
// Check if variable exists
bool HasFloatVariable(const std::string& a_name) const;
bool HasBoolVariable(const std::string& a_name) const;
bool HasIntVariable(const std::string& a_name) const;
// Bulk read animation variables from actor's graph cache
// Locks the graph manager during read
bool SaveAnimationVariablesFromCache(
RE::IAnimationGraphManagerHolder& a_holder,
AnimationVariableSnapshot& a_snapshot) const;
// Bulk write animation variables to actor's graph cache
// Locks the graph manager during write
bool LoadAnimationVariablesToCache(
RE::IAnimationGraphManagerHolder& a_holder,
const AnimationVariableSnapshot& a_snapshot) const;
// Get counts of each variable type
std::size_t GetFloatVariableCount() const { return m_floatVariables.size(); }
std::size_t GetBoolVariableCount() const { return m_boolVariables.size(); }
std::size_t GetIntVariableCount() const { return m_intVariables.size(); }
private:
// Private constructor (singleton)
F4AnimationDescriptor() = default;
// Variable name tables (indices match animation system's variable cache)
std::vector<std::string> m_floatVariables;
std::vector<std::string> m_boolVariables;
std::vector<std::string> m_intVariables;
// Reverse lookup tables (name → index) for faster search
std::unordered_map<std::string, std::size_t> m_floatVariableIndexMap;
std::unordered_map<std::string, std::size_t> m_boolVariableIndexMap;
std::unordered_map<std::string, std::size_t> m_intVariableIndexMap;
// Initialize humanoid descriptor tables
// Called once at startup; contains FO4-specific graph variable indices
void InitializeHumanoidVariables();
// Helper: Try to read a variable by index from graph cache
bool TryReadFloatVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
float& a_outValue) const;
bool TryReadBoolVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
bool& a_outValue) const;
bool TryReadIntVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
std::uint32_t& a_outValue) const;
// Helper: Try to write a variable by index to graph cache
bool TryWriteFloatVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
float a_value) const;
bool TryWriteBoolVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
bool a_value) const;
bool TryWriteIntVariable(
RE::IAnimationGraphManagerHolder& a_holder,
std::size_t a_index,
std::uint32_t a_value) const;
// Friend for logging
friend class F4TProxyAnimationSync;
};
}
+19
View File
@@ -8,6 +8,20 @@
namespace F4T::RemotePlayerState
{
// Represents a remote action event captured from another player
// Used for action replay on proxy actors (combat idles, equipping, etc.)
struct RemoteActionEvent
{
std::uint32_t type{}; // Action type (see ActorMediator::ActorAction enum)
std::string eventName; // Action event name (e.g., "ActorMovementStart")
std::vector<float> animationVariablesFloat; // Float variables snapshot (Speed, Direction, etc.)
std::vector<bool> animationVariablesBool; // Bool variables snapshot (IsSprinting, IsSneaking, etc.)
std::vector<std::uint32_t> animationVariablesInt; // Int variables snapshot (weapon types, etc.)
std::uint32_t actorStateFlags1{}; // ActorState.flags1 at action time
std::uint32_t actorStateFlags2{}; // ActorState.flags2 at action time
std::chrono::steady_clock::time_point captureTime{};
};
struct RemotePlayerState
{
std::uint32_t playerId{};
@@ -27,6 +41,11 @@ namespace F4T::RemotePlayerState
bool weaponDrawn{ false };
float movementSpeed{ 0.0F };
std::chrono::steady_clock::time_point lastReceivedLocalTime{};
// Phase 2: Actor state and action events (TiltedEvolution alignment)
std::uint32_t actorStateFlags1{}; // ActorState.flags1 (combat, sneaking, etc.)
std::uint32_t actorStateFlags2{}; // ActorState.flags2 (additional state flags)
std::vector<RemoteActionEvent> actionEvents; // Queue of pending action events
};
void SetAssignedPlayerId(std::uint32_t a_playerId);