Add local animation graph read-only debug
Introduce a game-thread-only, read-only diagnostic helper to probe local player animation graph variables and (optionally) numeric cache IDs. Adds F4TLocalAnimationGraphDebug.h/cpp with UpdateLocalPlayerAnimationGraphDebug(...) and wires a call into plugin/src/main.cpp immediately after GetPlayerMovementState. The helper probes curated variable names via CommonLibF4 read-only APIs, logs availability/changes, and includes a disabled-by-default numeric cache ID scan (kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = false) due to a reported crash during testing. Update also adds extensive dev-log documentation (docs/dev-log.md) describing behavior, testing notes, and rollback instructions. Build succeeded; manual F4SE in-game verification remains pending.
This commit is contained in:
+219
@@ -2489,6 +2489,225 @@ Next investigation should focus on read-only animation graph variable discovery
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-03 - Proxy Animation Graph Read-Only Investigation
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
Added a local-player-only, read-only animation graph diagnostic helper to probe
|
||||||
|
known candidate graph variable names and compare them with existing local
|
||||||
|
movement, sneak, jump, and weapon state signals.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
|
||||||
|
- `plugin/include/F4TLocalAnimationGraphDebug.h`
|
||||||
|
- `plugin/src/F4TLocalAnimationGraphDebug.cpp`
|
||||||
|
- `plugin/src/main.cpp`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
- Added `UpdateLocalPlayerAnimationGraphDebug(...)` on the existing game-thread
|
||||||
|
local polling path, immediately after `GetPlayerMovementState(...)` and
|
||||||
|
before any transform send decision.
|
||||||
|
- Added explicit read-only gates:
|
||||||
|
`kEnableLocalPlayerAnimationGraphReadOnlyDebug = true` and
|
||||||
|
`kEnableProxyAnimationGraphReadOnlyDebug = false`.
|
||||||
|
- The helper only inspects the local player. It does not inspect runtime
|
||||||
|
proxies by default and does not change proxy movement, lifecycle,
|
||||||
|
neutralization, placed fallback behavior, networking, protocol, server code,
|
||||||
|
or fake-client tooling.
|
||||||
|
- Graph access is limited to read-only CommonLibF4 APIs:
|
||||||
|
`GetAnimationGraphManagerImpl`, `GetGraphVariableCacheSize`,
|
||||||
|
`GetGraphVariableImplBool`, `GetGraphVariableImplInt`,
|
||||||
|
`GetGraphVariableImplFloat`, and
|
||||||
|
`BGSAnimationSystemUtils::IsActiveGraphInTransition`.
|
||||||
|
- Candidate variables are probed by curated name only. There is no raw cache
|
||||||
|
walking, no fake enumeration, no graph variable writes, no graph events, no
|
||||||
|
`NotifyAnimationGraphImpl`, no `PerformAction`, no `InitializeActorInstant`,
|
||||||
|
and no `ActorState` writes.
|
||||||
|
- Unavailable candidate reads are logged once per variable/type. Successful
|
||||||
|
reads are logged when their value changes or when local state / graph metadata
|
||||||
|
changes.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Ran `xmake build` from `plugin`; it succeeded and linked
|
||||||
|
`Fallout4Together.dll`.
|
||||||
|
- Manual Fallout 4 / F4SE testing is still pending:
|
||||||
|
- Launch through F4SE.
|
||||||
|
- `coc F4TTestCell01`.
|
||||||
|
- Toggle local sneak on/off.
|
||||||
|
- Move/stop and sprint if safe.
|
||||||
|
- Jump.
|
||||||
|
- Draw/holster weapon if safe.
|
||||||
|
- Check `Fallout4Together.log` for `Local animation graph debug` lines.
|
||||||
|
- Optionally add one fake client to confirm runtime proxies still spawn and
|
||||||
|
move while proxy graph diagnostics remain disabled.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
- Useful graph variables have not yet been confirmed in-game; log review from
|
||||||
|
the manual test sequence is required.
|
||||||
|
- Candidate variable failures will only prove that those names/types were
|
||||||
|
unavailable through the current read-by-name API, not that the underlying
|
||||||
|
graph has no equivalent state.
|
||||||
|
- CommonLibF4 does not expose a safe graph variable name enumeration API here,
|
||||||
|
so this pass intentionally uses curated read-by-name probes only.
|
||||||
|
|
||||||
|
### Rollback Note
|
||||||
|
|
||||||
|
- If the helper causes a crash, severe log spam, local control issues,
|
||||||
|
performance problems, proxy movement/lifecycle regressions, or unexpected
|
||||||
|
network send behavior, set `kEnableLocalPlayerAnimationGraphReadOnlyDebug`
|
||||||
|
back to `false` or remove the single helper call from `plugin/src/main.cpp`.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
- Run the manual F4SE test sequence and record which graph candidates are
|
||||||
|
available, unavailable, or transition with sneak/movement/jump/weapon state.
|
||||||
|
- If no safe graph reads produce useful values, investigate another read-only
|
||||||
|
route such as animation graph event observation research or known Fallout 4
|
||||||
|
graph variable references before planning any write/event milestone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-03 - Animation Graph Cache ID Read-Only Investigation
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
Added a local-player-only numeric animation graph cache ID diagnostic pass that
|
||||||
|
uses CommonLibF4 read-only virtual getter overloads to probe cache IDs from
|
||||||
|
`0` through `GetGraphVariableCacheSize() - 1`.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
|
||||||
|
- `plugin/src/F4TLocalAnimationGraphDebug.cpp`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
- Added `kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = true` as a
|
||||||
|
separate gate from the existing local-player read-only debug gate.
|
||||||
|
- Kept the existing read-by-name candidate probing intact.
|
||||||
|
- Numeric probing only uses:
|
||||||
|
`GetGraphVariableImpl(std::uint32_t, bool&)`,
|
||||||
|
`GetGraphVariableImpl(std::uint32_t, std::int32_t&)`, and
|
||||||
|
`GetGraphVariableImpl(std::uint32_t, float&)`.
|
||||||
|
- `GetGraphVariableCacheSize()` is the strict upper bound, so a cache size of
|
||||||
|
`64` probes only IDs `0` through `63`.
|
||||||
|
- Tracks per-ID/per-type availability and last value, logs initial readable
|
||||||
|
values once, and logs later values only when they change.
|
||||||
|
- Change logs include local movement, sprint, sneak, jump, weapon, speed,
|
||||||
|
actor stance, and force-sneak context.
|
||||||
|
- Added compact candidate buckets for sneak bool/int IDs, movement float IDs,
|
||||||
|
weapon bool/int IDs, jump bool/int IDs, and sprint bool/int IDs.
|
||||||
|
- Candidate logs intentionally use cautious wording such as candidate IDs and
|
||||||
|
movement-speed-related hints. No permanent graph variable names are claimed.
|
||||||
|
- This remains local-player-only. No proxy actor graph reads were added.
|
||||||
|
- No graph writes, graph events, `NotifyAnimationGraphImpl`, `PerformAction`,
|
||||||
|
raw memory reads, cache walking, guessed offsets, `ActorState` writes,
|
||||||
|
networking changes, protocol changes, Python server changes, fake-client
|
||||||
|
changes, proxy movement changes, proxy lifecycle changes, proxy
|
||||||
|
neutralization changes, or placed fallback behavior changes were added.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Ran `xmake build` from `plugin`; it succeeded and linked
|
||||||
|
`Fallout4Together.dll`.
|
||||||
|
- Linter diagnostics for `plugin/src/F4TLocalAnimationGraphDebug.cpp` reported
|
||||||
|
no errors.
|
||||||
|
- Manual Fallout 4 / F4SE testing is still pending:
|
||||||
|
- Launch through F4SE.
|
||||||
|
- `coc F4TTestCell01`.
|
||||||
|
- Confirm startup logs show graph manager availability, cache size, and
|
||||||
|
readable cache ID summary.
|
||||||
|
- Toggle local sneak on/off and check for transition logs plus related
|
||||||
|
numeric cache ID changes.
|
||||||
|
- Move/stop, sprint if safe, jump, and draw/holster weapon if safe.
|
||||||
|
- Optionally add one fake client and confirm runtime proxy spawning/movement
|
||||||
|
still works while proxy graph cache probing remains absent.
|
||||||
|
|
||||||
|
### Observations To Record After Manual Testing
|
||||||
|
|
||||||
|
- Cache size observed: not yet observed in-game for this milestone.
|
||||||
|
- Readable bool ID count: not yet observed in-game.
|
||||||
|
- Readable int ID count: not yet observed in-game.
|
||||||
|
- Readable float ID count: not yet observed in-game.
|
||||||
|
- Candidate IDs discovered: none recorded yet; requires manual log review.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
- Runtime usefulness is unknown until the F4SE manual test pass records actual
|
||||||
|
cache size, readable counts, and candidate IDs.
|
||||||
|
- If all numeric cache ID reads return unavailable, this path will only confirm
|
||||||
|
that the safe numeric read API is not useful for the local player in that
|
||||||
|
state; it does not justify raw memory probing or graph writes.
|
||||||
|
|
||||||
|
### Rollback Note
|
||||||
|
|
||||||
|
- If the cache ID diagnostic causes a crash, severe log spam, performance
|
||||||
|
drops, local control issues, proxy movement regressions, network cadence
|
||||||
|
changes, or unexpected outgoing transform JSON changes, set
|
||||||
|
`kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug` to `false` or revert
|
||||||
|
this milestone.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
- Run the manual F4SE test sequence and record cache size, readable ID counts,
|
||||||
|
and any candidate sneak/movement/weapon/jump/sprint IDs from
|
||||||
|
`Fallout4Together.log`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-03 - Animation Graph Cache ID Investigation Rollback
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
Disabled numeric animation graph cache ID probing by default after a Fallout 4
|
||||||
|
crash during read-only testing. Safer local animation graph diagnostics remain
|
||||||
|
enabled.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
|
||||||
|
- `plugin/src/F4TLocalAnimationGraphDebug.cpp`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
- Set `kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = false` with a
|
||||||
|
crash-safety comment above the gate.
|
||||||
|
- Left numeric cache ID investigation code in place behind the disabled gate for
|
||||||
|
future manual single-ID experiments.
|
||||||
|
- No networking, protocol, Python server, fake-client, proxy, or
|
||||||
|
`main.cpp` changes.
|
||||||
|
|
||||||
|
### Animation Graph Cache ID Investigation Result
|
||||||
|
|
||||||
|
Numeric cache ID probing through `GetGraphVariableImpl(id, bool/int/float&)` was tested as a read-only investigation path. Fallout 4 crashed during/after the test, while the server and fake client continued running. The numeric cache ID path has been disabled by default.
|
||||||
|
|
||||||
|
The existing local animation graph debug helper remains active for safer diagnostics: local state transitions, graph manager availability, graph cache size, graph transition state, and read-by-name candidate probing.
|
||||||
|
|
||||||
|
Future graph investigation should avoid full cache scans and instead test one known/suspected variable or ID at a time behind a stricter manual gate.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Ran `xmake build` from `plugin`; build succeeded.
|
||||||
|
- Manual F4SE retest pending: `coc F4TTestCell01`, toggle sneak, move/jump/weapon,
|
||||||
|
add one fake client. Expect `Local animation graph debug:` logs without
|
||||||
|
`Local animation graph cache debug:` lines and no crash.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
- Full-cache numeric ID probing is treated as unsafe until single-ID manual
|
||||||
|
gates exist.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
- Run the manual F4SE test sequence to confirm no crash and that safer local
|
||||||
|
animation debug logs still appear.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Entry Template
|
## Entry Template
|
||||||
|
|
||||||
Use this format for future updates:
|
Use this format for future updates:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace RE
|
||||||
|
{
|
||||||
|
class PlayerCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace F4T::LocalAnimationGraphDebug
|
||||||
|
{
|
||||||
|
// Game-thread-only, read-only diagnostics for discovering local player
|
||||||
|
// animation graph variables before any proxy visual animation writes.
|
||||||
|
void UpdateLocalPlayerAnimationGraphDebug(
|
||||||
|
RE::PlayerCharacter& a_player,
|
||||||
|
bool a_isMoving,
|
||||||
|
bool a_isSprinting,
|
||||||
|
bool a_isSneaking,
|
||||||
|
bool a_isJumping,
|
||||||
|
bool a_weaponDrawn,
|
||||||
|
float a_movementSpeed,
|
||||||
|
std::chrono::steady_clock::time_point a_currentTime);
|
||||||
|
}
|
||||||
@@ -0,0 +1,804 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "F4TLocalAnimationGraphDebug.h"
|
||||||
|
|
||||||
|
#include "F4TNetworking.h"
|
||||||
|
|
||||||
|
#include "RE/A/ACTOR_STANCE.h"
|
||||||
|
#include "RE/B/BGSAnimationSystemUtils.h"
|
||||||
|
#include "RE/B/BSFixedString.h"
|
||||||
|
#include "RE/I/IAnimationGraphManagerHolder.h"
|
||||||
|
#include "RE/P/PlayerCharacter.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <format>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr bool kEnableLocalPlayerAnimationGraphReadOnlyDebug = true;
|
||||||
|
// Numeric graph cache ID probing caused a Fallout 4 crash during testing. Keep disabled by default.
|
||||||
|
// Future investigation must isolate single IDs/types manually instead of scanning the full cache.
|
||||||
|
constexpr bool kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = false;
|
||||||
|
constexpr bool kEnableProxyAnimationGraphReadOnlyDebug = false;
|
||||||
|
|
||||||
|
constexpr auto kCandidateGraphVariables = std::to_array<std::string_view>({
|
||||||
|
"IsSneaking",
|
||||||
|
"bIsSneaking",
|
||||||
|
"IsCrouching",
|
||||||
|
"bIsCrouching",
|
||||||
|
"Sneaking",
|
||||||
|
"bSneaking",
|
||||||
|
"IsMoving",
|
||||||
|
"bIsMoving",
|
||||||
|
"Speed",
|
||||||
|
"speed",
|
||||||
|
"Direction",
|
||||||
|
"fSpeed",
|
||||||
|
"bSprint",
|
||||||
|
"IsSprinting",
|
||||||
|
"IsJumping",
|
||||||
|
"bInJumpState",
|
||||||
|
"WeaponDrawn",
|
||||||
|
"bWeaponDrawn",
|
||||||
|
});
|
||||||
|
|
||||||
|
struct LocalAnimationGraphState
|
||||||
|
{
|
||||||
|
bool isMoving{ false };
|
||||||
|
bool isSprinting{ false };
|
||||||
|
bool isSneaking{ false };
|
||||||
|
bool isJumping{ false };
|
||||||
|
bool weaponDrawn{ false };
|
||||||
|
bool playerCharacterIsSneaking{ false };
|
||||||
|
bool actorIsSneaking{ false };
|
||||||
|
bool apiJumping{ false };
|
||||||
|
bool apiSprinting{ false };
|
||||||
|
bool apiWeaponDrawn{ false };
|
||||||
|
bool actorStateForceSneak{ false };
|
||||||
|
std::uint32_t actorStateStance{ 0 };
|
||||||
|
std::int32_t weaponState{ 0 };
|
||||||
|
std::int32_t gunState{ 0 };
|
||||||
|
float movementSpeed{ 0.0F };
|
||||||
|
bool graphManagerAvailable{ false };
|
||||||
|
bool graphInTransition{ false };
|
||||||
|
std::uint32_t graphVariableCacheSize{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CandidateReadState
|
||||||
|
{
|
||||||
|
bool boolUnavailableLogged{ false };
|
||||||
|
bool intUnavailableLogged{ false };
|
||||||
|
bool floatUnavailableLogged{ false };
|
||||||
|
|
||||||
|
bool hasBoolValue{ false };
|
||||||
|
bool lastBoolValue{ false };
|
||||||
|
|
||||||
|
bool hasIntValue{ false };
|
||||||
|
std::int32_t lastIntValue{ 0 };
|
||||||
|
|
||||||
|
bool hasFloatValue{ false };
|
||||||
|
float lastFloatValue{ 0.0F };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CacheIdReadState
|
||||||
|
{
|
||||||
|
bool boolUnavailableObserved{ false };
|
||||||
|
bool hasBoolValue{ false };
|
||||||
|
bool lastBoolValue{ false };
|
||||||
|
|
||||||
|
bool intUnavailableObserved{ false };
|
||||||
|
bool hasIntValue{ false };
|
||||||
|
std::int32_t lastIntValue{ 0 };
|
||||||
|
|
||||||
|
bool floatUnavailableObserved{ false };
|
||||||
|
bool hasFloatValue{ false };
|
||||||
|
float lastFloatValue{ 0.0F };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CacheIdDebugState
|
||||||
|
{
|
||||||
|
bool hasCacheSize{ false };
|
||||||
|
std::uint32_t cacheSize{ 0 };
|
||||||
|
std::vector<CacheIdReadState> idStates{};
|
||||||
|
std::vector<std::uint32_t> candidateSneakBoolIds{};
|
||||||
|
std::vector<std::uint32_t> candidateSneakIntIds{};
|
||||||
|
std::vector<std::uint32_t> candidateMovementFloatIds{};
|
||||||
|
std::vector<std::uint32_t> candidateWeaponBoolIds{};
|
||||||
|
std::vector<std::uint32_t> candidateWeaponIntIds{};
|
||||||
|
std::vector<std::uint32_t> candidateJumpBoolIds{};
|
||||||
|
std::vector<std::uint32_t> candidateJumpIntIds{};
|
||||||
|
std::vector<std::uint32_t> candidateSprintBoolIds{};
|
||||||
|
std::vector<std::uint32_t> candidateSprintIntIds{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CacheIdProbeUpdate
|
||||||
|
{
|
||||||
|
std::vector<std::uint32_t> changedBoolIds{};
|
||||||
|
std::vector<std::uint32_t> changedIntIds{};
|
||||||
|
std::vector<std::uint32_t> changedFloatIds{};
|
||||||
|
std::uint32_t readableBoolIds{ 0 };
|
||||||
|
std::uint32_t readableIntIds{ 0 };
|
||||||
|
std::uint32_t readableFloatIds{ 0 };
|
||||||
|
bool candidateSummaryChanged{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string GetLocalPlayerLogMessage(std::string_view a_message)
|
||||||
|
{
|
||||||
|
return F4T::Networking::GetLocalPlayerLogPrefix() + " " + std::string(a_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogInfoWithLocalPlayerPrefix(std::string_view a_message)
|
||||||
|
{
|
||||||
|
const auto message = GetLocalPlayerLogMessage(a_message);
|
||||||
|
REX::INFO(std::string_view{ message });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreFloatsDifferent(float a_lhs, float a_rhs)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(a_lhs) || !std::isfinite(a_rhs)) {
|
||||||
|
return a_lhs != a_rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::fabs(a_lhs - a_rhs) > 0.001F;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatIdList(const std::vector<std::uint32_t>& a_ids)
|
||||||
|
{
|
||||||
|
std::string result{ "[" };
|
||||||
|
for (std::size_t index = 0; index < a_ids.size(); ++index) {
|
||||||
|
if (index != 0) {
|
||||||
|
result += ", ";
|
||||||
|
}
|
||||||
|
result += std::format("{}", a_ids[index]);
|
||||||
|
}
|
||||||
|
result += "]";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatLocalStateContext(const LocalAnimationGraphState& a_state)
|
||||||
|
{
|
||||||
|
return std::format(
|
||||||
|
"moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementSpeed={:.1f}, actorStateStance={}, actorStateForceSneak={}",
|
||||||
|
a_state.isMoving,
|
||||||
|
a_state.isSprinting,
|
||||||
|
a_state.isSneaking,
|
||||||
|
a_state.isJumping,
|
||||||
|
a_state.weaponDrawn,
|
||||||
|
a_state.movementSpeed,
|
||||||
|
a_state.actorStateStance,
|
||||||
|
a_state.actorStateForceSneak);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddUniqueId(std::vector<std::uint32_t>& a_ids, std::uint32_t a_id)
|
||||||
|
{
|
||||||
|
for (const auto existingId : a_ids) {
|
||||||
|
if (existingId == a_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a_ids.push_back(a_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MatchesStateBool(bool a_value, bool a_stateValue)
|
||||||
|
{
|
||||||
|
return a_value == a_stateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MatchesStateInt(std::int32_t a_value, bool a_stateValue)
|
||||||
|
{
|
||||||
|
if (a_value != 0 && a_value != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a_value != 0) == a_stateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasRelevantStateChange(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
if (!a_lastState) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& lastState = *a_lastState;
|
||||||
|
return lastState.isMoving != a_currentState.isMoving ||
|
||||||
|
lastState.isSprinting != a_currentState.isSprinting ||
|
||||||
|
lastState.isSneaking != a_currentState.isSneaking ||
|
||||||
|
lastState.isJumping != a_currentState.isJumping ||
|
||||||
|
lastState.weaponDrawn != a_currentState.weaponDrawn ||
|
||||||
|
lastState.playerCharacterIsSneaking != a_currentState.playerCharacterIsSneaking ||
|
||||||
|
lastState.actorIsSneaking != a_currentState.actorIsSneaking ||
|
||||||
|
lastState.apiJumping != a_currentState.apiJumping ||
|
||||||
|
lastState.apiSprinting != a_currentState.apiSprinting ||
|
||||||
|
lastState.apiWeaponDrawn != a_currentState.apiWeaponDrawn ||
|
||||||
|
lastState.actorStateForceSneak != a_currentState.actorStateForceSneak ||
|
||||||
|
lastState.actorStateStance != a_currentState.actorStateStance ||
|
||||||
|
lastState.weaponState != a_currentState.weaponState ||
|
||||||
|
lastState.gunState != a_currentState.gunState;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasGraphMetadataChange(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
if (!a_lastState) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& lastState = *a_lastState;
|
||||||
|
return lastState.graphManagerAvailable != a_currentState.graphManagerAvailable ||
|
||||||
|
lastState.graphInTransition != a_currentState.graphInTransition ||
|
||||||
|
lastState.graphVariableCacheSize != a_currentState.graphVariableCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasSneakTransition(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
return a_lastState && a_lastState->isSneaking != a_currentState.isSneaking;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasMovementTransition(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
if (!a_lastState) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a_lastState->isMoving != a_currentState.isMoving ||
|
||||||
|
AreFloatsDifferent(a_lastState->movementSpeed, a_currentState.movementSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasJumpTransition(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
return a_lastState && a_lastState->isJumping != a_currentState.isJumping;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasWeaponTransition(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
return a_lastState && a_lastState->weaponDrawn != a_currentState.weaponDrawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasSprintTransition(
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
return a_lastState && a_lastState->isSprinting != a_currentState.isSprinting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogStateSnapshot(
|
||||||
|
std::string_view a_reason,
|
||||||
|
const LocalAnimationGraphState& a_state)
|
||||||
|
{
|
||||||
|
const auto actorStateSneaking =
|
||||||
|
a_state.actorStateStance == static_cast<std::uint32_t>(RE::ACTOR_STANCE::kSneaking);
|
||||||
|
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: {} moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementSpeed={:.1f}, playerCharacterIsSneaking={}, actorIsSneaking={}, actorStateSneaking={}, actorStateStance={}, actorStateForceSneak={}, apiJumping={}, apiSprinting={}, apiWeaponDrawn={}, weaponState={}, gunState={}, graphManagerAvailable={}, graphVariableCacheSize={}, graphInTransition={}.",
|
||||||
|
a_reason,
|
||||||
|
a_state.isMoving,
|
||||||
|
a_state.isSprinting,
|
||||||
|
a_state.isSneaking,
|
||||||
|
a_state.isJumping,
|
||||||
|
a_state.weaponDrawn,
|
||||||
|
a_state.movementSpeed,
|
||||||
|
a_state.playerCharacterIsSneaking,
|
||||||
|
a_state.actorIsSneaking,
|
||||||
|
actorStateSneaking,
|
||||||
|
a_state.actorStateStance,
|
||||||
|
a_state.actorStateForceSneak,
|
||||||
|
a_state.apiJumping,
|
||||||
|
a_state.apiSprinting,
|
||||||
|
a_state.apiWeaponDrawn,
|
||||||
|
a_state.weaponState,
|
||||||
|
a_state.gunState,
|
||||||
|
a_state.graphManagerAvailable,
|
||||||
|
a_state.graphVariableCacheSize,
|
||||||
|
a_state.graphInTransition));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeBoolCandidate(
|
||||||
|
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||||
|
std::string_view a_candidate,
|
||||||
|
CandidateReadState& a_state,
|
||||||
|
bool a_shouldLogSuccessfulValues)
|
||||||
|
{
|
||||||
|
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||||
|
bool value = false;
|
||||||
|
if (!a_holder.GetGraphVariableImplBool(variableName, value)) {
|
||||||
|
if (!a_state.boolUnavailableLogged) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate bool variable \"{}\" unavailable.",
|
||||||
|
a_candidate));
|
||||||
|
a_state.boolUnavailableLogged = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto changed = !a_state.hasBoolValue || a_state.lastBoolValue != value;
|
||||||
|
if (changed || a_shouldLogSuccessfulValues) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate bool variable \"{}\"={}.",
|
||||||
|
a_candidate,
|
||||||
|
value));
|
||||||
|
}
|
||||||
|
|
||||||
|
a_state.hasBoolValue = true;
|
||||||
|
a_state.lastBoolValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeIntCandidate(
|
||||||
|
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||||
|
std::string_view a_candidate,
|
||||||
|
CandidateReadState& a_state,
|
||||||
|
bool a_shouldLogSuccessfulValues)
|
||||||
|
{
|
||||||
|
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||||
|
std::int32_t value = 0;
|
||||||
|
if (!a_holder.GetGraphVariableImplInt(variableName, value)) {
|
||||||
|
if (!a_state.intUnavailableLogged) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate int variable \"{}\" unavailable.",
|
||||||
|
a_candidate));
|
||||||
|
a_state.intUnavailableLogged = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto changed = !a_state.hasIntValue || a_state.lastIntValue != value;
|
||||||
|
if (changed || a_shouldLogSuccessfulValues) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate int variable \"{}\"={}.",
|
||||||
|
a_candidate,
|
||||||
|
value));
|
||||||
|
}
|
||||||
|
|
||||||
|
a_state.hasIntValue = true;
|
||||||
|
a_state.lastIntValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeFloatCandidate(
|
||||||
|
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||||
|
std::string_view a_candidate,
|
||||||
|
CandidateReadState& a_state,
|
||||||
|
bool a_shouldLogSuccessfulValues)
|
||||||
|
{
|
||||||
|
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||||
|
float value = 0.0F;
|
||||||
|
if (!a_holder.GetGraphVariableImplFloat(variableName, value)) {
|
||||||
|
if (!a_state.floatUnavailableLogged) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate float variable \"{}\" unavailable.",
|
||||||
|
a_candidate));
|
||||||
|
a_state.floatUnavailableLogged = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto changed = !a_state.hasFloatValue || AreFloatsDifferent(a_state.lastFloatValue, value);
|
||||||
|
if (changed || a_shouldLogSuccessfulValues) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph debug: candidate float variable \"{}\"={:.3f}.",
|
||||||
|
a_candidate,
|
||||||
|
value));
|
||||||
|
}
|
||||||
|
|
||||||
|
a_state.hasFloatValue = true;
|
||||||
|
a_state.lastFloatValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeCandidateGraphVariables(
|
||||||
|
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||||
|
bool a_shouldLogSuccessfulValues)
|
||||||
|
{
|
||||||
|
static std::array<CandidateReadState, kCandidateGraphVariables.size()> candidateStates{};
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < kCandidateGraphVariables.size(); ++index) {
|
||||||
|
const auto candidate = kCandidateGraphVariables[index];
|
||||||
|
auto& state = candidateStates[index];
|
||||||
|
|
||||||
|
ProbeBoolCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||||
|
ProbeIntCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||||
|
ProbeFloatCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetCacheIdDebugState(CacheIdDebugState& a_debugState, std::uint32_t a_cacheSize)
|
||||||
|
{
|
||||||
|
a_debugState.hasCacheSize = true;
|
||||||
|
a_debugState.cacheSize = a_cacheSize;
|
||||||
|
a_debugState.idStates.clear();
|
||||||
|
a_debugState.idStates.resize(a_cacheSize);
|
||||||
|
a_debugState.candidateSneakBoolIds.clear();
|
||||||
|
a_debugState.candidateSneakIntIds.clear();
|
||||||
|
a_debugState.candidateMovementFloatIds.clear();
|
||||||
|
a_debugState.candidateWeaponBoolIds.clear();
|
||||||
|
a_debugState.candidateWeaponIntIds.clear();
|
||||||
|
a_debugState.candidateJumpBoolIds.clear();
|
||||||
|
a_debugState.candidateJumpIntIds.clear();
|
||||||
|
a_debugState.candidateSprintBoolIds.clear();
|
||||||
|
a_debugState.candidateSprintIntIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeLogBoolCandidate(
|
||||||
|
CacheIdDebugState& a_debugState,
|
||||||
|
CacheIdProbeUpdate& a_update,
|
||||||
|
std::uint32_t a_id,
|
||||||
|
bool a_value,
|
||||||
|
const LocalAnimationGraphState& a_currentState,
|
||||||
|
bool a_sneakTransition,
|
||||||
|
bool a_weaponTransition,
|
||||||
|
bool a_jumpTransition,
|
||||||
|
bool a_sprintTransition)
|
||||||
|
{
|
||||||
|
if (a_sneakTransition && MatchesStateBool(a_value, a_currentState.isSneaking) &&
|
||||||
|
AddUniqueId(a_debugState.candidateSneakBoolIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate sneak bool id {} mirrors local sneaking transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_weaponTransition && MatchesStateBool(a_value, a_currentState.weaponDrawn) &&
|
||||||
|
AddUniqueId(a_debugState.candidateWeaponBoolIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate weapon bool id {} mirrors local weapon drawn transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_jumpTransition && MatchesStateBool(a_value, a_currentState.isJumping) &&
|
||||||
|
AddUniqueId(a_debugState.candidateJumpBoolIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate jump bool id {} mirrors local jump transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_sprintTransition && MatchesStateBool(a_value, a_currentState.isSprinting) &&
|
||||||
|
AddUniqueId(a_debugState.candidateSprintBoolIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate sprint bool id {} mirrors local sprint transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeLogIntCandidate(
|
||||||
|
CacheIdDebugState& a_debugState,
|
||||||
|
CacheIdProbeUpdate& a_update,
|
||||||
|
std::uint32_t a_id,
|
||||||
|
std::int32_t a_value,
|
||||||
|
const LocalAnimationGraphState& a_currentState,
|
||||||
|
bool a_sneakTransition,
|
||||||
|
bool a_weaponTransition,
|
||||||
|
bool a_jumpTransition,
|
||||||
|
bool a_sprintTransition)
|
||||||
|
{
|
||||||
|
if (a_sneakTransition && MatchesStateInt(a_value, a_currentState.isSneaking) &&
|
||||||
|
AddUniqueId(a_debugState.candidateSneakIntIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate sneak int id {} mirrors local sneaking transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_weaponTransition && MatchesStateInt(a_value, a_currentState.weaponDrawn) &&
|
||||||
|
AddUniqueId(a_debugState.candidateWeaponIntIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate weapon int id {} mirrors local weapon drawn transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_jumpTransition && MatchesStateInt(a_value, a_currentState.isJumping) &&
|
||||||
|
AddUniqueId(a_debugState.candidateJumpIntIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate jump int id {} mirrors local jump transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_sprintTransition && MatchesStateInt(a_value, a_currentState.isSprinting) &&
|
||||||
|
AddUniqueId(a_debugState.candidateSprintIntIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: candidate sprint int id {} mirrors local sprint transition.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeLogFloatCandidate(
|
||||||
|
CacheIdDebugState& a_debugState,
|
||||||
|
CacheIdProbeUpdate& a_update,
|
||||||
|
std::uint32_t a_id,
|
||||||
|
bool a_movementTransition)
|
||||||
|
{
|
||||||
|
if (a_movementTransition && AddUniqueId(a_debugState.candidateMovementFloatIds, a_id)) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: float id {} appears movement-speed-related.",
|
||||||
|
a_id));
|
||||||
|
a_update.candidateSummaryChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogTransitionChangedIds(
|
||||||
|
std::string_view a_transition,
|
||||||
|
const CacheIdProbeUpdate& a_update,
|
||||||
|
const LocalAnimationGraphState& a_currentState)
|
||||||
|
{
|
||||||
|
if (a_update.changedBoolIds.empty() && a_update.changedIntIds.empty() && a_update.changedFloatIds.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: {} transition changed boolIds={}, intIds={}, floatIds={} with {}.",
|
||||||
|
a_transition,
|
||||||
|
FormatIdList(a_update.changedBoolIds),
|
||||||
|
FormatIdList(a_update.changedIntIds),
|
||||||
|
FormatIdList(a_update.changedFloatIds),
|
||||||
|
FormatLocalStateContext(a_currentState)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogCandidateSummary(const CacheIdDebugState& a_debugState)
|
||||||
|
{
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug summary: candidateSneakBoolIds={}, candidateSneakIntIds={}, candidateMovementFloatIds={}, candidateWeaponBoolIds={}, candidateWeaponIntIds={}, candidateJumpBoolIds={}, candidateJumpIntIds={}, candidateSprintBoolIds={}, candidateSprintIntIds={}.",
|
||||||
|
FormatIdList(a_debugState.candidateSneakBoolIds),
|
||||||
|
FormatIdList(a_debugState.candidateSneakIntIds),
|
||||||
|
FormatIdList(a_debugState.candidateMovementFloatIds),
|
||||||
|
FormatIdList(a_debugState.candidateWeaponBoolIds),
|
||||||
|
FormatIdList(a_debugState.candidateWeaponIntIds),
|
||||||
|
FormatIdList(a_debugState.candidateJumpBoolIds),
|
||||||
|
FormatIdList(a_debugState.candidateJumpIntIds),
|
||||||
|
FormatIdList(a_debugState.candidateSprintBoolIds),
|
||||||
|
FormatIdList(a_debugState.candidateSprintIntIds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeCacheIdGraphVariables(
|
||||||
|
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||||
|
const LocalAnimationGraphState& a_currentState,
|
||||||
|
const std::optional<LocalAnimationGraphState>& a_lastState)
|
||||||
|
{
|
||||||
|
if constexpr (!kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CacheIdDebugState debugState{};
|
||||||
|
|
||||||
|
const auto cacheSize = a_currentState.graphVariableCacheSize;
|
||||||
|
const auto cacheSizeChanged = !debugState.hasCacheSize || debugState.cacheSize != cacheSize;
|
||||||
|
if (cacheSizeChanged) {
|
||||||
|
ResetCacheIdDebugState(debugState, cacheSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheIdProbeUpdate update{};
|
||||||
|
const auto stateContext = FormatLocalStateContext(a_currentState);
|
||||||
|
const auto sneakTransition = HasSneakTransition(a_lastState, a_currentState);
|
||||||
|
const auto movementTransition = HasMovementTransition(a_lastState, a_currentState);
|
||||||
|
const auto jumpTransition = HasJumpTransition(a_lastState, a_currentState);
|
||||||
|
const auto weaponTransition = HasWeaponTransition(a_lastState, a_currentState);
|
||||||
|
const auto sprintTransition = HasSprintTransition(a_lastState, a_currentState);
|
||||||
|
|
||||||
|
for (std::uint32_t id = 0; id < cacheSize; ++id) {
|
||||||
|
auto& idState = debugState.idStates[id];
|
||||||
|
|
||||||
|
bool boolValue = false;
|
||||||
|
if (a_holder.GetGraphVariableImpl(id, boolValue)) {
|
||||||
|
++update.readableBoolIds;
|
||||||
|
const auto changed = !idState.hasBoolValue || idState.lastBoolValue != boolValue;
|
||||||
|
if (changed) {
|
||||||
|
if (idState.hasBoolValue) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: bool id {} changed {} -> {} with {}.",
|
||||||
|
id,
|
||||||
|
idState.lastBoolValue,
|
||||||
|
boolValue,
|
||||||
|
stateContext));
|
||||||
|
} else {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: bool id {} initial {} with {}.",
|
||||||
|
id,
|
||||||
|
boolValue,
|
||||||
|
stateContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
update.changedBoolIds.push_back(id);
|
||||||
|
MaybeLogBoolCandidate(
|
||||||
|
debugState,
|
||||||
|
update,
|
||||||
|
id,
|
||||||
|
boolValue,
|
||||||
|
a_currentState,
|
||||||
|
sneakTransition,
|
||||||
|
weaponTransition,
|
||||||
|
jumpTransition,
|
||||||
|
sprintTransition);
|
||||||
|
}
|
||||||
|
idState.hasBoolValue = true;
|
||||||
|
idState.lastBoolValue = boolValue;
|
||||||
|
} else {
|
||||||
|
idState.boolUnavailableObserved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t intValue = 0;
|
||||||
|
if (a_holder.GetGraphVariableImpl(id, intValue)) {
|
||||||
|
++update.readableIntIds;
|
||||||
|
const auto changed = !idState.hasIntValue || idState.lastIntValue != intValue;
|
||||||
|
if (changed) {
|
||||||
|
if (idState.hasIntValue) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: int id {} changed {} -> {} with {}.",
|
||||||
|
id,
|
||||||
|
idState.lastIntValue,
|
||||||
|
intValue,
|
||||||
|
stateContext));
|
||||||
|
} else {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: int id {} initial {} with {}.",
|
||||||
|
id,
|
||||||
|
intValue,
|
||||||
|
stateContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
update.changedIntIds.push_back(id);
|
||||||
|
MaybeLogIntCandidate(
|
||||||
|
debugState,
|
||||||
|
update,
|
||||||
|
id,
|
||||||
|
intValue,
|
||||||
|
a_currentState,
|
||||||
|
sneakTransition,
|
||||||
|
weaponTransition,
|
||||||
|
jumpTransition,
|
||||||
|
sprintTransition);
|
||||||
|
}
|
||||||
|
idState.hasIntValue = true;
|
||||||
|
idState.lastIntValue = intValue;
|
||||||
|
} else {
|
||||||
|
idState.intUnavailableObserved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float floatValue = 0.0F;
|
||||||
|
if (a_holder.GetGraphVariableImpl(id, floatValue)) {
|
||||||
|
++update.readableFloatIds;
|
||||||
|
const auto changed = !idState.hasFloatValue || AreFloatsDifferent(idState.lastFloatValue, floatValue);
|
||||||
|
if (changed) {
|
||||||
|
if (idState.hasFloatValue) {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: float id {} changed {:.3f} -> {:.3f} with {}.",
|
||||||
|
id,
|
||||||
|
idState.lastFloatValue,
|
||||||
|
floatValue,
|
||||||
|
stateContext));
|
||||||
|
} else {
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: float id {} initial {:.3f} with {}.",
|
||||||
|
id,
|
||||||
|
floatValue,
|
||||||
|
stateContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
update.changedFloatIds.push_back(id);
|
||||||
|
MaybeLogFloatCandidate(debugState, update, id, movementTransition);
|
||||||
|
}
|
||||||
|
idState.hasFloatValue = true;
|
||||||
|
idState.lastFloatValue = floatValue;
|
||||||
|
} else {
|
||||||
|
idState.floatUnavailableObserved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheSizeChanged) {
|
||||||
|
const auto allUnavailable =
|
||||||
|
update.readableBoolIds == 0 && update.readableIntIds == 0 && update.readableFloatIds == 0;
|
||||||
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
|
"Local animation graph cache debug: cacheSize={}, readableBoolIds={}, readableIntIds={}, readableFloatIds={}{}.",
|
||||||
|
cacheSize,
|
||||||
|
update.readableBoolIds,
|
||||||
|
update.readableIntIds,
|
||||||
|
update.readableFloatIds,
|
||||||
|
allUnavailable ? ", all numeric cache ID reads unavailable" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sneakTransition) {
|
||||||
|
LogTransitionChangedIds("sneak", update, a_currentState);
|
||||||
|
}
|
||||||
|
if (movementTransition) {
|
||||||
|
LogTransitionChangedIds("movement", update, a_currentState);
|
||||||
|
}
|
||||||
|
if (jumpTransition) {
|
||||||
|
LogTransitionChangedIds("jump", update, a_currentState);
|
||||||
|
}
|
||||||
|
if (weaponTransition) {
|
||||||
|
LogTransitionChangedIds("weapon", update, a_currentState);
|
||||||
|
}
|
||||||
|
if (sprintTransition) {
|
||||||
|
LogTransitionChangedIds("sprint", update, a_currentState);
|
||||||
|
}
|
||||||
|
if (update.candidateSummaryChanged) {
|
||||||
|
LogCandidateSummary(debugState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace F4T::LocalAnimationGraphDebug
|
||||||
|
{
|
||||||
|
void UpdateLocalPlayerAnimationGraphDebug(
|
||||||
|
RE::PlayerCharacter& a_player,
|
||||||
|
bool a_isMoving,
|
||||||
|
bool a_isSprinting,
|
||||||
|
bool a_isSneaking,
|
||||||
|
bool a_isJumping,
|
||||||
|
bool a_weaponDrawn,
|
||||||
|
float a_movementSpeed,
|
||||||
|
std::chrono::steady_clock::time_point a_currentTime)
|
||||||
|
{
|
||||||
|
(void)a_currentTime;
|
||||||
|
|
||||||
|
if constexpr (!kEnableLocalPlayerAnimationGraphReadOnlyDebug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(!kEnableProxyAnimationGraphReadOnlyDebug);
|
||||||
|
static std::optional<LocalAnimationGraphState> lastState{};
|
||||||
|
|
||||||
|
auto& actor = static_cast<RE::Actor&>(a_player);
|
||||||
|
auto* graphHolder = static_cast<RE::IAnimationGraphManagerHolder*>(&a_player);
|
||||||
|
RE::BSTSmartPointer<RE::BSAnimationGraphManager> graphManager;
|
||||||
|
|
||||||
|
const auto graphManagerAvailable =
|
||||||
|
graphHolder->GetAnimationGraphManagerImpl(graphManager) && static_cast<bool>(graphManager);
|
||||||
|
const auto graphVariableCacheSize = graphHolder->GetGraphVariableCacheSize();
|
||||||
|
const auto graphInTransition =
|
||||||
|
RE::BGSAnimationSystemUtils::IsActiveGraphInTransition(static_cast<RE::TESObjectREFR*>(&a_player));
|
||||||
|
|
||||||
|
const LocalAnimationGraphState currentState{
|
||||||
|
a_isMoving,
|
||||||
|
a_isSprinting,
|
||||||
|
a_isSneaking,
|
||||||
|
a_isJumping,
|
||||||
|
a_weaponDrawn,
|
||||||
|
a_player.IsSneaking(),
|
||||||
|
actor.IsSneaking(),
|
||||||
|
a_player.IsJumping(),
|
||||||
|
a_player.DoGetSprinting(),
|
||||||
|
a_player.GetWeaponMagicDrawn(),
|
||||||
|
a_player.forceSneak != 0,
|
||||||
|
static_cast<std::uint32_t>(a_player.stance),
|
||||||
|
static_cast<std::int32_t>(a_player.weaponState),
|
||||||
|
static_cast<std::int32_t>(a_player.gunState),
|
||||||
|
a_movementSpeed,
|
||||||
|
graphManagerAvailable,
|
||||||
|
graphInTransition,
|
||||||
|
graphVariableCacheSize
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto relevantStateChanged = HasRelevantStateChange(lastState, currentState);
|
||||||
|
const auto graphMetadataChanged = HasGraphMetadataChange(lastState, currentState);
|
||||||
|
if (relevantStateChanged || graphMetadataChanged) {
|
||||||
|
LogStateSnapshot(
|
||||||
|
relevantStateChanged ? "state transition" : "graph metadata changed",
|
||||||
|
currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProbeCandidateGraphVariables(*graphHolder, relevantStateChanged || graphMetadataChanged);
|
||||||
|
ProbeCacheIdGraphVariables(*graphHolder, currentState, lastState);
|
||||||
|
|
||||||
|
lastState = currentState;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "F4TNetworking.h"
|
#include "F4TNetworking.h"
|
||||||
|
#include "F4TLocalAnimationGraphDebug.h"
|
||||||
#include "F4TProxyActorController.h"
|
#include "F4TProxyActorController.h"
|
||||||
|
|
||||||
#include "RE/A/ACTOR_STANCE.h"
|
#include "RE/A/ACTOR_STANCE.h"
|
||||||
@@ -399,6 +400,15 @@ namespace
|
|||||||
const auto currentTime = std::chrono::steady_clock::now();
|
const auto currentTime = std::chrono::steady_clock::now();
|
||||||
const auto currentMovementState =
|
const auto currentMovementState =
|
||||||
GetPlayerMovementState(*player, hasLastTransform ? &lastSampleTransform : nullptr, lastSampleTime, currentTransform, currentTime);
|
GetPlayerMovementState(*player, hasLastTransform ? &lastSampleTransform : nullptr, lastSampleTime, currentTransform, currentTime);
|
||||||
|
F4T::LocalAnimationGraphDebug::UpdateLocalPlayerAnimationGraphDebug(
|
||||||
|
*player,
|
||||||
|
currentMovementState.isMoving,
|
||||||
|
currentMovementState.isSprinting,
|
||||||
|
currentMovementState.isSneaking,
|
||||||
|
currentMovementState.isJumping,
|
||||||
|
currentMovementState.weaponDrawn,
|
||||||
|
currentMovementState.movementSpeed,
|
||||||
|
currentTime);
|
||||||
if (!hasLastTransform) {
|
if (!hasLastTransform) {
|
||||||
lastSentTransform = currentTransform;
|
lastSentTransform = currentTransform;
|
||||||
lastSampleTransform = currentTransform;
|
lastSampleTransform = currentTransform;
|
||||||
|
|||||||
Reference in New Issue
Block a user