Use authoritative FO4 animation variable names

Replace previously guessed graph variable names/types with the real FO4 humanoid movement behavior names (from MTBehavior.hkx) and update syncing logic accordingly. F4AnimationDescriptor now lists only the variables F4T drives (Speed, SpeedSmoothed, IsSprinting, bInJumpState, iIsInSneak) to avoid clobbering graph state; Direction is intentionally not written. Enable proxy sneak sync using the INT32 iIsInSneak, change SpeedSampled -> SpeedSmoothed, and update all Set/Get calls and diagnostic logging to the new names. Fix jump detection by relying on the actor IsJumping() API (removing the noisy vertical-speed heuristic that produced false positives and caused proxies to remain in jump state). Changes touch descriptor, proxy sync, puppet, main logic, and related docs to explain the rationale and testing notes.
This commit is contained in:
2026-06-05 15:29:31 +12:00
parent 5ba440ab5d
commit 6a38e7226c
7 changed files with 220 additions and 144 deletions
+13
View File
@@ -1,5 +1,18 @@
# Animation Sync Analysis: Fallout 4 Together vs TiltedEvolution
> **Resolution update (2026-06-05):** The "variable names may be wrong" hypothesis below is
> now confirmed and fixed. The real FO4 humanoid graph variable names + types were extracted
> from `MTBehavior.hkx` (unpacked in the `F4-Animation-Research` repo) and applied to the code.
> Authoritative set actually driven by F4T:
> - `Speed`, `SpeedSmoothed` (float / `VARIABLE_TYPE_REAL`)
> - `IsSprinting`, `bInJumpState` (bool / `VARIABLE_TYPE_BOOL`)
> - `iIsInSneak` (int / `VARIABLE_TYPE_INT32`, 0 = standing, 1 = sneaking)
>
> Earlier guesses (`direction`, `isMoving`, `isSneaking`, `isCrouching`, `speedSampled`,
> `speedDamped`, `SpeedSampled`) do **not** exist on this graph and silently failed. `Direction`
> exists but is movement-relative-to-facing (not world heading), so it is intentionally left
> unwritten to avoid strafing artifacts. See `docs/dev-log.md` (2026-06-05) for the change set.
## Problem Statement
Your Fallout 4 Together animations aren't working on proxy actors. You have animation sync code in `F4TProxyAnimationSync.cpp` attempting to set graph variables by name (`Speed`, `IsSprinting`, etc.), but the variables aren't being applied or synced correctly.
+102
View File
@@ -7,6 +7,108 @@ failed experiments, successful tests, and next steps.
---
## 2026-06-05 - Fix False Jump State Masking Locomotion Animation
### Summary
After grounding the graph variable names (see entry below), an in-game log
(`Fallout4Together.log`) showed locomotion variables were now correct
(`graphSpeedReadBack=true(373.0)` while running), but proxies still never showed walk/run
animation. Root cause: the local player was reporting `isJumping=true` almost continuously
while merely running, so proxies were perpetually forced into the jump state, which masks
ground locomotion.
### Files Changed
- `plugin/src/main.cpp`
### Details
- The log proved the graph layer is healthy: when moving, `recvMovementSpeed` ~545,
`computedGraphSpeed=373`, and `Speed`/`SpeedSmoothed` read back as 373 (engine not
clobbering them). `moveStart` is accepted on idle->move transitions.
- But `jumping=true` stuck on for 2.5s+ stretches during plain running. Source diagnostic:
`Local jump detection: apiJumping=false, verticalSpeed=2724.7, heldJumping=true`.
- `GetVerticalSpeed()` is `(z - prevZ) / dt`, which spikes to thousands of units/sec on flat
ground (tiny dt + z micro-jitter) - far above the 80 u/s `kJumpVerticalSpeedThreshold`,
so `derivedJumping` fired constantly. The real `IsJumping()` API was accurate (true only
on actual jumps).
- Fix: jump detection now relies solely on `a_player.IsJumping()`; removed the
vertical-speed-derived jump trigger. `verticalSpeed` is still computed and logged for
diagnostics. (`kJumpVerticalSpeedThreshold` is now unused.)
### Testing
- `xmake build` succeeds with no new warnings.
- In-game verification pending: with false jumps gone, proxies should hold the locomotion
state and play walk/run while `Speed` drives the blend; `jumping=true` should now appear
only during real jumps.
### Known Issues
- `kJumpVerticalSpeedThreshold` constant is now unused (left in place; harmless).
- Locomotion transition event names still unverified against behavior-graph `eventNames`.
### Next Steps
- Confirm walk/run now animates in-game and capture a fresh log.
- If a real jump is occasionally missed by `IsJumping()`, consider a *sane* vertical-speed
fallback (much higher threshold + clamp on dt) rather than the old raw heuristic.
---
## 2026-06-05 - Animation Graph Variables Grounded in Real Behavior Data
### Summary
Replaced the guessed FO4 animation-graph variable names with the authoritative names and
types extracted from the real humanoid movement behavior graph
(`Meshes\Actors\Character\Behaviors\MTBehavior.hkx`), unpacked to XML in the companion
`F4-Animation-Research` repo. Almost every previous graph-variable name was wrong, so
`SetGraphVariable*` calls were silently failing and proxy animation state was never applied.
### Files Changed
- `plugin/include/F4TProxyAnimationSync.h`
- `plugin/src/F4TProxyAnimationSync.cpp`
- `plugin/src/F4AnimationDescriptor.cpp`
- `plugin/src/F4TProxyPuppet.cpp`
- `docs/animation-sync-analysis.md`
### Details
- Ground truth comes from `MTBehavior.hkx` -> `hkbBehaviorGraphStringData::variableNames`
(67 entries) paired with the parallel `hkbBehaviorGraphData::variableInfos` types.
- Corrected names / types now used:
- `Speed`, `SpeedSmoothed` -> `VARIABLE_TYPE_REAL` (float). Previous code wrote
`speed`/`speedSampled`/`speedDamped`, none of which exist on this graph.
- `IsSprinting`, `bInJumpState` -> `VARIABLE_TYPE_BOOL` (bool).
- `iIsInSneak` -> `VARIABLE_TYPE_INT32` (int, 0 = standing, 1 = sneaking). Sneak was
previously modelled (incorrectly) as a bool `isSneaking`, so it never worked.
- `F4AnimationDescriptor` now lists ONLY the variables we actually drive. This matters
because `LoadAnimationVariablesToCache()` bulk-writes every descriptor variable from the
snapshot; listing extra variables would clobber graph state we do not intend to touch
(e.g. `IsPlayer`, `bAllowRotation`, `LookAt*`).
- Re-enabled `kEnableProxySneakSync` now that the correct int variable name is known.
- `Direction` is deliberately NOT written: the graph `Direction` is movement direction
relative to facing (radians), not the absolute world heading (`angleZ`) we carry. Writing
`angleZ` would make proxies strafe/moonwalk. Left at the graph default (forward) until a
relative movement direction is added to the protocol.
- Did NOT touch the network/JSON field names (`isMoving`, `isSprinting`, ...) — those are
protocol fields and stay as-is.
### Testing
- `xmake build` succeeds (only pre-existing C4702 unreachable-code warnings in
`F4TProxyActorController.cpp`, unrelated to this change).
- In-game verification still required: confirm walk/run/sprint blend, sneak pose on proxies,
and watch the `Proxy anim diag` log line for `graphSpeedReadBack`/`speedSmoothedReadBack`
now reporting `true` (variables found) instead of failing silently.
### Known Issues
- Locomotion transition EVENT names (`moveStart`, `SprintStart`, jump events, sneak events)
are unchanged and still empirically chosen; they are a separate axis from variables and
should be cross-checked against the behavior graph `eventNames` next.
- Weapon-drawn sync remains disabled; that state lives in the weapon wrapping behavior, not
`MTBehavior`, and needs its own research pass.
### Next Steps
- Verify in-game and capture a fresh `Fallout4Together.log` diagnostic.
- Cross-check the transition event names against the behavior graph `eventNames` arrays.
- If sneak works, investigate the weapon wrapping behavior for the weapon-drawn variable.
---
## 2026-06-05 - Jump Animation Fix (Correct Event Names)
### Issues Resolved
+11 -7
View File
@@ -14,18 +14,22 @@ namespace RE
namespace F4T::ProxyAnimationSync
{
// Confirmed via local read-by-name graph debug (Fallout4Together.log):
// - float "Speed" reads/writes while in-game (walk ~47-105)
// - bool "IsSprinting" reads while in-game
// - bIsMoving, bIsSneaking, bSprint, bInJumpState, bWeaponDrawn, Direction: unavailable by name
// Authoritative FO4 humanoid movement-behavior graph variable names, taken from
// MTBehavior.hkx (hkbBehaviorGraphStringData::variableNames, with types from the
// parallel variableInfos array) via the F4-Animation-Research unpack:
// Speed, SpeedSmoothed, Direction -> VARIABLE_TYPE_REAL (float)
// IsSprinting, bInJumpState -> VARIABLE_TYPE_BOOL (bool)
// iIsInSneak -> VARIABLE_TYPE_INT32 (int)
// Earlier code guessed names like "speed"/"speedSampled"/"direction" that do not exist
// on this graph, so every SetGraphVariable* call silently failed.
namespace GraphVar
{
inline constexpr const char* kSpeed = "Speed";
inline constexpr const char* kSpeedLower = "speed";
inline constexpr const char* kSpeedSampled = "speedSampled";
inline constexpr const char* kSpeedDamped = "speedDamped";
inline constexpr const char* kSpeedSmoothed = "SpeedSmoothed";
inline constexpr const char* kDirection = "Direction";
inline constexpr const char* kIsSprinting = "IsSprinting";
inline constexpr const char* kInJumpState = "bInJumpState";
inline constexpr const char* kIsInSneak = "iIsInSneak";
}
// Maps network movementSpeed (units/sec) to graph Speed (~0-105). Used when the sender
+25 -43
View File
@@ -43,35 +43,31 @@ namespace F4T::AnimationDescriptor
// Humanoid Master Behavior Graph Variables (FO4)
// ============================================================================
//
// These indices are based on comparison with TiltedEvolution's Skyrim SE model
// and FO4's shared Creation Engine foundation. Variable names and indices should
// match the humanoid master behavior graph structure in FO4.
// These names and types are taken from the authoritative FO4 humanoid movement
// behavior graph (Meshes\Actors\Character\Behaviors\MTBehavior.hkx), unpacked to
// XML in the F4-Animation-Research repo. They are the real
// hkbBehaviorGraphStringData::variableNames, paired with the parallel
// variableInfos types - NOT guesses.
//
// Note: Exact indices need verification via debug logging (Phase 1.1).
// For now, using names that match common Creation Engine patterns.
// Speed, SpeedSmoothed -> VARIABLE_TYPE_REAL (float)
// IsSprinting, bInJumpState -> VARIABLE_TYPE_BOOL (bool)
// iIsInSneak -> VARIABLE_TYPE_INT32 (int, 0 = standing, 1 = sneaking)
//
// IMPORTANT: only list variables F4T actually drives here. LoadAnimationVariablesToCache()
// bulk-writes every variable in the descriptor from the snapshot, so any variable listed
// but not explicitly set would be written as 0/false each update and clobber graph state
// we do not intend to touch (e.g. IsPlayer, bAllowRotation, LookAt* settings).
//
// The previous table guessed names ("direction", "isMoving", "isSneaking", "speedSampled",
// "speedDamped", ...) that do not exist on this graph, so every SetGraphVariable* call
// silently failed and no animation state was ever applied.
//
void F4AnimationDescriptor::InitializeHumanoidVariables()
{
// ===== FLOAT VARIABLES (Locomotion & Direction) =====
// Index these in order; the index becomes the cache access offset
// ===== FLOAT VARIABLES (VARIABLE_TYPE_REAL) =====
m_floatVariables = {
// Locomotion core
"Speed", // 0 - Primary locomotion speed (0-105)
"direction", // 1 - Facing direction relative to movement
"speedDamped", // 2 - Damped speed for smoothing
"speedSampled", // 3 - Sampled speed
// Aiming & weapon
"pitchGunAim", // 4 - Pitch angle when aiming
"weaponAdjust", // 5 - Weapon positioning offset
// Physics
"velocityZ", // 6 - Vertical velocity (Havok-driven)
// Thresholds & parameters
"speedWalk", // 7 - Walk speed threshold
"speedRun", // 8 - Run speed threshold
"Speed", // 0 - primary locomotion speed (world units/sec)
"SpeedSmoothed", // 1 - engine-damped speed; mirror Speed so it tracks the target
};
// Build reverse lookup map for float variables
@@ -79,19 +75,10 @@ namespace F4T::AnimationDescriptor
m_floatVariableIndexMap[m_floatVariables[i]] = i;
}
// ===== BOOL VARIABLES (State Flags) =====
// ===== BOOL VARIABLES (VARIABLE_TYPE_BOOL) =====
m_boolVariables = {
// Movement state
"isSprinting", // 0 - Sprint mode active
"isSneaking", // 1 - Sneak/crouch mode
"isMoving", // 2 - Any movement active
"isJumping", // 3 - Jump in progress
"isCrouching", // 4 - Crouch mode active
// Engine state
"bMotionDriven", // 5 - Motion-driven locomotion
"bInMoveState", // 6 - In locomotion state machine
"bSprintOK", // 7 - Sprint available (game state dependent)
"IsSprinting", // 0 - sprint sub-state
"bInJumpState", // 1 - jump / takeoff state
};
// Build reverse lookup map for bool variables
@@ -99,14 +86,9 @@ namespace F4T::AnimationDescriptor
m_boolVariableIndexMap[m_boolVariables[i]] = i;
}
// ===== INT VARIABLES (Equipment & State) =====
// ===== INT VARIABLES (VARIABLE_TYPE_INT32) =====
m_intVariables = {
// Equipment
"iLeftHandType", // 0 - Left hand weapon/item type
"iRightHandEquipped", // 1 - Right hand equipped (0/1)
// State
"iIsInSneak", // 2 - Sneak state (0/1/2)
"iIsInSneak", // 0 - sneak/crouch state (0 = standing, 1 = sneaking)
};
// Build reverse lookup map for int variables
+47 -78
View File
@@ -30,10 +30,14 @@ namespace
// The old reason for disabling (suppressed AI) no longer applies.
constexpr bool kEnableProxyVisualAnimationSync = true;
constexpr bool kEnableProxyLocomotionSync = true;
// Disabled until read-by-name debug finds working bool variable names on this graph.
constexpr bool kEnableProxySneakSync = false;
// Enabled now that the authoritative FO4 graph name is known: sneak is the INT32
// variable "iIsInSneak" (0 = standing, 1 = sneaking), not a bool. See MTBehavior.hkx
// in the F4-Animation-Research unpack.
constexpr bool kEnableProxySneakSync = true;
constexpr bool kEnableProxyJumpSync = true;
// Crouch is not a separate FO4 graph state; sneaking (iIsInSneak) covers it.
constexpr bool kEnableProxyCrouchSync = false;
// Weapon-drawn lives in the weapon wrapping behavior, not MTBehavior; needs more research.
constexpr bool kEnableProxyWeaponDrawnSync = false;
constexpr float kIdleSpeedThreshold = 1.0F;
@@ -181,14 +185,9 @@ namespace
a_snapshot.floats[speedIdx] = graphSpeed;
}
const auto speedSampledIdx = a_descriptor.GetFloatVariableIndex(F4T::ProxyAnimationSync::GraphVar::kSpeedSampled);
if (speedSampledIdx != static_cast<std::size_t>(-1) && speedSampledIdx < a_snapshot.floats.size()) {
a_snapshot.floats[speedSampledIdx] = graphSpeed;
}
const auto speedDampedIdx = a_descriptor.GetFloatVariableIndex(F4T::ProxyAnimationSync::GraphVar::kSpeedDamped);
if (speedDampedIdx != static_cast<std::size_t>(-1) && speedDampedIdx < a_snapshot.floats.size()) {
a_snapshot.floats[speedDampedIdx] = graphSpeed;
const auto speedSmoothedIdx = a_descriptor.GetFloatVariableIndex(F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed);
if (speedSmoothedIdx != static_cast<std::size_t>(-1) && speedSmoothedIdx < a_snapshot.floats.size()) {
a_snapshot.floats[speedSmoothedIdx] = graphSpeed;
}
}
@@ -316,18 +315,11 @@ namespace
if constexpr (kEnableProxyLocomotionSync) {
const auto graphSpeed = ClampGraphSpeed(a_desired.graphSpeed);
const auto speedWritten = TrySetGraphFloat(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kSpeed, graphSpeed);
const auto speedLowerWritten = TrySetGraphFloat(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kSpeedLower, graphSpeed);
const auto speedSampledWritten =
TrySetGraphFloat(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kSpeedSampled, graphSpeed);
const auto speedDampedWritten =
TrySetGraphFloat(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kSpeedDamped, graphSpeed);
bool sprintWritten = false;
if (a_desired.isSprinting) {
sprintWritten = TrySetGraphBool(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kIsSprinting, true);
} else {
sprintWritten = TrySetGraphBool(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kIsSprinting, false);
}
success &= speedWritten && speedLowerWritten && speedSampledWritten && speedDampedWritten && sprintWritten;
const auto speedSmoothedWritten =
TrySetGraphFloat(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed, graphSpeed);
const auto sprintWritten =
TrySetGraphBool(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kIsSprinting, a_desired.isSprinting);
success &= speedWritten && speedSmoothedWritten && sprintWritten;
if (a_desired.isMoving) {
float readBackSpeed = 0.0F;
@@ -382,48 +374,33 @@ namespace
PopulateLocomotionSpeedSnapshot(snapshot, descriptor, a_desired.graphSpeed);
const auto directionIdx = descriptor.GetFloatVariableIndex("direction");
if (directionIdx != static_cast<std::size_t>(-1)) {
snapshot.floats[directionIdx] = a_desired.direction;
}
// NOTE: the FO4 graph "Direction" variable is the movement direction relative to
// facing (radians), not the world heading we carry in angleZ. Writing angleZ here
// makes the proxy strafe/moonwalk, so Direction is intentionally left at its graph
// default (forward) until a relative movement direction is added to the protocol.
// Populate bool variables
const auto isSprintingIdx = descriptor.GetBoolVariableIndex("isSprinting");
const auto isSprintingIdx = descriptor.GetBoolVariableIndex(F4T::ProxyAnimationSync::GraphVar::kIsSprinting);
if (isSprintingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isSprintingIdx] = a_desired.isSprinting;
}
const auto isSneakingIdx = descriptor.GetBoolVariableIndex("isSneaking");
if (isSneakingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isSneakingIdx] = a_desired.isSneaking;
}
const auto isMovingIdx = descriptor.GetBoolVariableIndex("isMoving");
if (isMovingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isMovingIdx] = a_desired.isMoving;
}
if constexpr (kEnableProxyJumpSync) {
const auto isJumpingIdx = descriptor.GetBoolVariableIndex("isJumping");
if (isJumpingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isJumpingIdx] = a_desired.isJumping;
const auto inJumpIdx = descriptor.GetBoolVariableIndex(F4T::ProxyAnimationSync::GraphVar::kInJumpState);
if (inJumpIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[inJumpIdx] = a_desired.isJumping;
}
}
if constexpr (kEnableProxyCrouchSync) {
const auto isCrouchingIdx = descriptor.GetBoolVariableIndex("isCrouching");
if (isCrouchingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isCrouchingIdx] = a_desired.isCrouching;
if constexpr (kEnableProxySneakSync) {
const auto sneakIdx = descriptor.GetIntVariableIndex(F4T::ProxyAnimationSync::GraphVar::kIsInSneak);
if (sneakIdx != static_cast<std::size_t>(-1)) {
snapshot.ints[sneakIdx] = a_desired.isSneaking ? 1U : 0U;
}
}
// Single bulk write operation (major performance win)
const auto success = descriptor.LoadAnimationVariablesToCache(*graphHolder, snapshot);
if constexpr (kEnableProxyJumpSync) {
TrySetGraphBool(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kInJumpState, a_desired.isJumping);
}
if (!success) {
static std::unordered_set<std::uint32_t> loggedWriteFailures;
if (loggedWriteFailures.insert(a_remotePlayerId).second) {
@@ -578,13 +555,13 @@ namespace F4T::ProxyAnimationSync
last = now;
auto* diagHolder = static_cast<RE::IAnimationGraphManagerHolder*>(&a_proxy);
float readSpeed = -1.0F;
float readSpeedSampled = -1.0F;
float readSpeedSmoothed = -1.0F;
const auto haveSpeed =
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ "Speed" }, readSpeed);
const auto haveSpeedSampled =
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ "speedSampled" }, readSpeedSampled);
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ F4T::ProxyAnimationSync::GraphVar::kSpeed }, readSpeed);
const auto haveSpeedSmoothed =
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed }, readSpeedSmoothed);
LogInfoWithLocalPlayerPrefix(std::format(
"Proxy anim diag p{} actor={:08X}: recvMoving={}, recvSprint={}, recvMovementSpeed={:.1f}, recvAnimGraphSpeed={:.1f}, computedGraphSpeed={:.1f}, graphSpeedReadBack={}({:.1f}), speedSampledReadBack={}({:.1f}).",
"Proxy anim diag p{} actor={:08X}: recvMoving={}, recvSprint={}, recvMovementSpeed={:.1f}, recvAnimGraphSpeed={:.1f}, computedGraphSpeed={:.1f}, graphSpeedReadBack={}({:.1f}), speedSmoothedReadBack={}({:.1f}).",
a_remotePlayer.playerId,
actorId,
a_remotePlayer.isMoving,
@@ -594,8 +571,8 @@ namespace F4T::ProxyAnimationSync
desired.graphSpeed,
haveSpeed,
readSpeed,
haveSpeedSampled,
readSpeedSampled));
haveSpeedSmoothed,
readSpeedSmoothed));
}
}
@@ -689,37 +666,29 @@ namespace F4T::ProxyAnimationSync
const auto desired = BuildDesiredState(a_remotePlayer);
PopulateLocomotionSpeedSnapshot(snapshot, descriptor, desired.graphSpeed);
// Direction: from remote player's heading
if (descriptor.GetFloatVariableIndex("direction") != static_cast<std::size_t>(-1)) {
snapshot.floats[descriptor.GetFloatVariableIndex("direction")] = desired.direction;
}
// Direction intentionally not written; see ApplyDesiredStateToGraphDescriptorBased.
// Prepare bool variables (isSprinting, isSneaking, isMoving, isJumping, isCrouching)
// Prepare bool variables (IsSprinting, bInJumpState)
snapshot.bools.resize(descriptor.GetBoolVariableCount(), false);
if (descriptor.GetBoolVariableIndex("isSprinting") != static_cast<std::size_t>(-1)) {
snapshot.bools[descriptor.GetBoolVariableIndex("isSprinting")] = desired.isSprinting;
const auto isSprintingIdx = descriptor.GetBoolVariableIndex(GraphVar::kIsSprinting);
if (isSprintingIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[isSprintingIdx] = desired.isSprinting;
}
if (descriptor.GetBoolVariableIndex("isSneaking") != static_cast<std::size_t>(-1)) {
snapshot.bools[descriptor.GetBoolVariableIndex("isSneaking")] = desired.isSneaking;
const auto inJumpIdx = descriptor.GetBoolVariableIndex(GraphVar::kInJumpState);
if (inJumpIdx != static_cast<std::size_t>(-1)) {
snapshot.bools[inJumpIdx] = desired.isJumping;
}
if (descriptor.GetBoolVariableIndex("isMoving") != static_cast<std::size_t>(-1)) {
snapshot.bools[descriptor.GetBoolVariableIndex("isMoving")] = desired.isMoving;
}
if (descriptor.GetBoolVariableIndex("isJumping") != static_cast<std::size_t>(-1)) {
snapshot.bools[descriptor.GetBoolVariableIndex("isJumping")] = desired.isJumping;
}
if (descriptor.GetBoolVariableIndex("isCrouching") != static_cast<std::size_t>(-1)) {
snapshot.bools[descriptor.GetBoolVariableIndex("isCrouching")] = desired.isCrouching;
}
// Prepare int variables (equipment, etc.)
// Prepare int variables (iIsInSneak)
snapshot.ints.resize(descriptor.GetIntVariableCount(), 0);
const auto sneakIdx = descriptor.GetIntVariableIndex(GraphVar::kIsInSneak);
if (sneakIdx != static_cast<std::size_t>(-1)) {
snapshot.ints[sneakIdx] = desired.isSneaking ? 1U : 0U;
}
// Apply all variables at once via descriptor bulk write
if (!descriptor.LoadAnimationVariablesToCache(
*static_cast<RE::IAnimationGraphManagerHolder*>(&a_proxy),
+3 -1
View File
@@ -66,8 +66,10 @@ namespace F4T::ProxyPuppet
return;
}
auto* graphHolder = static_cast<RE::IAnimationGraphManagerHolder*>(a_this);
// "Speed" and "SpeedSmoothed" are the real FO4 humanoid graph float variables
// (MTBehavior.hkx). The previous "SpeedSampled" name does not exist on this graph.
graphHolder->SetGraphVariableFloat(RE::BSFixedString{ "Speed" }, a_speed);
graphHolder->SetGraphVariableFloat(RE::BSFixedString{ "SpeedSampled" }, a_speed);
graphHolder->SetGraphVariableFloat(RE::BSFixedString{ "SpeedSmoothed" }, a_speed);
}
void SyncControllerMotionFields(RE::Actor& a_actor, const RE::NiPoint3& a_velocity, float a_speed)
+6 -2
View File
@@ -296,9 +296,13 @@ namespace
const auto derivedMoving = a_movementSpeed >= kMovingSpeedThreshold;
const auto isMoving = ResolveMovementHoldState(derivedMoving, a_currentTime, a_movingHoldUntil);
const auto sneakDetection = GetSneakDetectionState(a_player);
// Jump detection relies solely on the IsJumping() API. The previous
// vertical-speed heuristic (a_verticalSpeed >= kJumpVerticalSpeedThreshold) produced
// constant false positives: (z - prevZ)/dt spikes to thousands of units/sec on flat
// ground due to tiny dt and z micro-jitter, so proxies were stuck in a perpetual jump
// state that masked the run/walk locomotion animation (see Fallout4Together.log).
const auto apiJumping = a_player.IsJumping();
const auto derivedJumping = a_verticalSpeed >= kJumpVerticalSpeedThreshold;
if (apiJumping || derivedJumping) {
if (apiJumping) {
jumpHoldUntil = a_currentTime + kJumpStateHoldDuration;
}