Add data-only proxy animation debug logging
Track and log per-slot animation/movement diagnostics for runtime proxy actors without applying any visual animation. Adds a ProxyAnimationSpeedBucket enum and GetProxyAnimationSpeedBucket/GetProxyAnimationSpeedBucketLabel helpers, a new UpdateProxyAnimationStateDebug() routine, and fields on ProxyActorSlot to remember last-observed moving, sprinting, sneaking, jumping, weapon-drawn, movement type, and coarse speed-bucket state. Resets observation state when slots are created/reused and emits initial/transition-only logs including playerId and proxy FormID. Documentation and dev-log entries updated; visual animation, animation graph changes, and AI/movement behavior remain intentionally unmodified.
This commit is contained in:
@@ -141,6 +141,15 @@ namespace
|
||||
kHolding
|
||||
};
|
||||
|
||||
enum class ProxyAnimationSpeedBucket
|
||||
{
|
||||
kIdle,
|
||||
kSlow,
|
||||
kWalk,
|
||||
kRun,
|
||||
kSprint
|
||||
};
|
||||
|
||||
struct RemotePlayerSelection
|
||||
{
|
||||
std::optional<RemotePlayer> selectedPlayer;
|
||||
@@ -218,7 +227,14 @@ namespace
|
||||
bool remoteStateMovedLogged{ false };
|
||||
bool reusableLogged{ false };
|
||||
bool directRemoteSpawnLogged{ false };
|
||||
std::optional<bool> lastObservedSneakState;
|
||||
bool hasLoggedInitialAnimationState{ false };
|
||||
std::optional<bool> lastObservedMovingState;
|
||||
std::optional<bool> lastObservedSprintingState;
|
||||
std::optional<bool> lastObservedSneakingState;
|
||||
std::optional<bool> lastObservedJumpingState;
|
||||
std::optional<bool> lastObservedWeaponDrawnState;
|
||||
std::optional<std::string> lastObservedMovementType;
|
||||
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
||||
};
|
||||
|
||||
constexpr auto kProxyMovementMode = ProxyMovementMode::kRemotePlayerStateTest;
|
||||
@@ -361,6 +377,10 @@ namespace
|
||||
}
|
||||
|
||||
void ResetRemoteMovementStateTracking();
|
||||
void UpdateProxyAnimationStateDebug(
|
||||
ProxyActorSlot& a_slot,
|
||||
const RemotePlayer& a_remotePlayer,
|
||||
const RE::Actor& a_proxy);
|
||||
|
||||
const char* GetProxyNeutralizationReasonLabel(ProxyNeutralizationReason a_reason)
|
||||
{
|
||||
@@ -378,6 +398,47 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetProxyAnimationSpeedBucketLabel(ProxyAnimationSpeedBucket a_bucket)
|
||||
{
|
||||
switch (a_bucket) {
|
||||
case ProxyAnimationSpeedBucket::kIdle:
|
||||
return "idle";
|
||||
case ProxyAnimationSpeedBucket::kSlow:
|
||||
return "slow";
|
||||
case ProxyAnimationSpeedBucket::kWalk:
|
||||
return "walk";
|
||||
case ProxyAnimationSpeedBucket::kRun:
|
||||
return "run";
|
||||
case ProxyAnimationSpeedBucket::kSprint:
|
||||
return "sprint";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ProxyAnimationSpeedBucket GetProxyAnimationSpeedBucket(const RemotePlayer& a_remotePlayer)
|
||||
{
|
||||
if (!a_remotePlayer.isMoving || a_remotePlayer.movementSpeed < 1.0F) {
|
||||
return ProxyAnimationSpeedBucket::kIdle;
|
||||
}
|
||||
if (a_remotePlayer.isSprinting) {
|
||||
return ProxyAnimationSpeedBucket::kSprint;
|
||||
}
|
||||
|
||||
// TODO: Tune these diagnostic thresholds against real Fallout 4 movement units
|
||||
// before using them for visual locomotion animation decisions.
|
||||
if (a_remotePlayer.movementSpeed < 75.0F) {
|
||||
return ProxyAnimationSpeedBucket::kSlow;
|
||||
}
|
||||
if (a_remotePlayer.movementSpeed < 150.0F) {
|
||||
return ProxyAnimationSpeedBucket::kWalk;
|
||||
}
|
||||
if (a_remotePlayer.movementSpeed < 300.0F) {
|
||||
return ProxyAnimationSpeedBucket::kRun;
|
||||
}
|
||||
return ProxyAnimationSpeedBucket::kSprint;
|
||||
}
|
||||
|
||||
std::string FormatPosition(const RE::NiPoint3& a_position)
|
||||
{
|
||||
return std::format("({:.2f}, {:.2f}, {:.2f})", a_position.x, a_position.y, a_position.z);
|
||||
@@ -4001,7 +4062,14 @@ namespace
|
||||
|
||||
void ResetSlotObservationState(ProxyActorSlot& a_slot)
|
||||
{
|
||||
a_slot.lastObservedSneakState.reset();
|
||||
a_slot.hasLoggedInitialAnimationState = false;
|
||||
a_slot.lastObservedMovingState.reset();
|
||||
a_slot.lastObservedSprintingState.reset();
|
||||
a_slot.lastObservedSneakingState.reset();
|
||||
a_slot.lastObservedJumpingState.reset();
|
||||
a_slot.lastObservedWeaponDrawnState.reset();
|
||||
a_slot.lastObservedMovementType.reset();
|
||||
a_slot.lastObservedMovementSpeedBucket.reset();
|
||||
}
|
||||
|
||||
bool HasSlotHeldSinceTime(const ProxyActorSlot& a_slot)
|
||||
@@ -4161,7 +4229,6 @@ namespace
|
||||
a_slot.restoredForCurrentState = true;
|
||||
a_slot.firstSmoothedMovementLogged = false;
|
||||
a_slot.remoteStateMovedLogged = false;
|
||||
a_slot.lastObservedSneakState = a_remotePlayer.isSneaking;
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy actor {:08X} restored/reused for remote player {} at X={:.2f}, Y={:.2f}, Z={:.2f}.",
|
||||
@@ -4170,6 +4237,7 @@ namespace
|
||||
a_remotePlayer.x,
|
||||
a_remotePlayer.y,
|
||||
a_remotePlayer.z));
|
||||
UpdateProxyAnimationStateDebug(a_slot, a_remotePlayer, a_proxy);
|
||||
LogProxyMovementDiagnostic(
|
||||
a_proxy,
|
||||
a_remotePlayer,
|
||||
@@ -4179,22 +4247,52 @@ namespace
|
||||
ProxyMovementDiagnosticMode::kRestore);
|
||||
}
|
||||
|
||||
void ApplyRemoteMovementStateToSlot(ProxyActorSlot& a_slot, const RemotePlayer& a_remotePlayer)
|
||||
void UpdateProxyAnimationStateDebug(
|
||||
ProxyActorSlot& a_slot,
|
||||
const RemotePlayer& a_remotePlayer,
|
||||
const RE::Actor& a_proxy)
|
||||
{
|
||||
if (!a_slot.lastObservedSneakState) {
|
||||
a_slot.lastObservedSneakState = a_remotePlayer.isSneaking;
|
||||
return;
|
||||
const auto speedBucket = GetProxyAnimationSpeedBucket(a_remotePlayer);
|
||||
const auto shouldLogInitial = !a_slot.hasLoggedInitialAnimationState;
|
||||
const auto changed =
|
||||
!a_slot.lastObservedMovingState || *a_slot.lastObservedMovingState != a_remotePlayer.isMoving ||
|
||||
!a_slot.lastObservedSprintingState || *a_slot.lastObservedSprintingState != a_remotePlayer.isSprinting ||
|
||||
!a_slot.lastObservedSneakingState || *a_slot.lastObservedSneakingState != a_remotePlayer.isSneaking ||
|
||||
!a_slot.lastObservedJumpingState || *a_slot.lastObservedJumpingState != a_remotePlayer.isJumping ||
|
||||
!a_slot.lastObservedWeaponDrawnState || *a_slot.lastObservedWeaponDrawnState != a_remotePlayer.weaponDrawn ||
|
||||
!a_slot.lastObservedMovementType || *a_slot.lastObservedMovementType != a_remotePlayer.movementType ||
|
||||
!a_slot.lastObservedMovementSpeedBucket || *a_slot.lastObservedMovementSpeedBucket != speedBucket;
|
||||
|
||||
if (shouldLogInitial || changed) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy animation state {} for remote player {}: actor={:08X}, holdingIndex={}, moving={}, speed={:.1f}, speedBucket={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementType={}.",
|
||||
shouldLogInitial ? "initial" : "changed",
|
||||
a_remotePlayer.playerId,
|
||||
a_proxy.GetFormID(),
|
||||
a_slot.holdingPositionIndex,
|
||||
a_remotePlayer.isMoving,
|
||||
a_remotePlayer.movementSpeed,
|
||||
GetProxyAnimationSpeedBucketLabel(speedBucket),
|
||||
a_remotePlayer.isSprinting,
|
||||
a_remotePlayer.isSneaking,
|
||||
a_remotePlayer.isJumping,
|
||||
a_remotePlayer.weaponDrawn,
|
||||
a_remotePlayer.movementType));
|
||||
}
|
||||
|
||||
if (*a_slot.lastObservedSneakState == a_remotePlayer.isSneaking) {
|
||||
return;
|
||||
}
|
||||
a_slot.hasLoggedInitialAnimationState = true;
|
||||
a_slot.lastObservedMovingState = a_remotePlayer.isMoving;
|
||||
a_slot.lastObservedSprintingState = a_remotePlayer.isSprinting;
|
||||
a_slot.lastObservedSneakingState = a_remotePlayer.isSneaking;
|
||||
a_slot.lastObservedJumpingState = a_remotePlayer.isJumping;
|
||||
a_slot.lastObservedWeaponDrawnState = a_remotePlayer.weaponDrawn;
|
||||
a_slot.lastObservedMovementType = a_remotePlayer.movementType;
|
||||
a_slot.lastObservedMovementSpeedBucket = speedBucket;
|
||||
|
||||
a_slot.lastObservedSneakState = a_remotePlayer.isSneaking;
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Remote player {} sneak state changed: isSneaking={}",
|
||||
a_remotePlayer.playerId,
|
||||
a_remotePlayer.isSneaking));
|
||||
// TODO: Future visual milestones should investigate safe sneak/crouch
|
||||
// application, weapon drawn visuals, locomotion graph behavior, jump
|
||||
// animation behavior, and read-only research for animation graph
|
||||
// variables/events before any visual animation state is applied.
|
||||
}
|
||||
|
||||
void MoveSlotProxyToRemotePlayer(
|
||||
@@ -4218,6 +4316,7 @@ namespace
|
||||
a_slot.holdReason.reset();
|
||||
a_slot.heldSinceTime = {};
|
||||
a_slot.reusableLogged = false;
|
||||
UpdateProxyAnimationStateDebug(a_slot, a_remotePlayer, a_proxy);
|
||||
|
||||
if (shouldSnap) {
|
||||
LogThrottledInfo(
|
||||
@@ -4362,6 +4461,7 @@ namespace
|
||||
CaptureSlotInitialRemoteSpawnTransform(slot->second, a_remotePlayer);
|
||||
if (inserted) {
|
||||
slot->second.holdingPositionIndex = g_nextProxyHoldingPositionIndex++;
|
||||
ResetSlotObservationState(slot->second);
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy slot created for remote player {} with holding index {}.",
|
||||
a_remotePlayer.playerId,
|
||||
@@ -4416,7 +4516,6 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
ApplyRemoteMovementStateToSlot(a_slot, a_remotePlayer);
|
||||
MoveSlotProxyToRemotePlayer(a_slot, *proxy, a_remotePlayer, a_player, a_sameCellPassed);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user