fix: resolve Phase 6 compilation errors - unblock testing
- Made F4AnimationDescriptor constructor public - Fixed logging macros by using std::string_view and renaming helpers - Fixed int32_t/uint32_t type mismatch in animation variable reads/writes - Disabled actor state flag setting (needs bitfield mapping in Phase 7) - Disabled dynamic spawn APIs pending CommonLibF4 verification (Phase 7) - Added move semantics to RemoteActionQueue for ProxyActorSlot Build status: clean compilation, Phase 6 testing ready with pre-placed proxies Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4275,3 +4275,57 @@ If yes → Animations working! Tune sync intervals as needed.
|
||||
If no → Investigate why Move() isn't being called or velocity isn't updating.
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-03 - Compilation Error Fixes (Phase 6 Unblock)
|
||||
|
||||
### What Changed
|
||||
|
||||
**Compilation Errors Resolved:**
|
||||
|
||||
1. **Private Constructor Issue**: `F4AnimationDescriptor` had a private default constructor that prevented global singleton initialization.
|
||||
- **Fix**: Changed constructor to public; removed duplicate declaration in private section.
|
||||
|
||||
2. **Logging Macro Incompatibility**: `REX::INFO` and `REX::WARN` expect format strings with std::string_view, not std::string parameters.
|
||||
- **Fix**: Renamed helper functions from `LogInfo/LogWarning` to `LogInfoMessage/LogWarningMessage` and wrapped arguments with `std::string_view{...}`.
|
||||
|
||||
3. **Type Mismatch in `GetGraphVariableImplInt`**: CommonLibF4's API expects `std::int32_t&` but we were passing `std::uint32_t&`.
|
||||
- **Fix**: Created a temporary `int32_t` for the API call, then cast result to `uint32_t` for snapshot storage in both `TryReadIntVariable` and `TryWriteIntVariable`.
|
||||
|
||||
4. **Actor State Access**: Initially tried accessing `actorState.flags1/flags2` but Actor inherits from ActorState as a base class, not as a member.
|
||||
- **Fix**: Disabled actor state flag setting for Phase 6; marked as TODO for Phase 7 (requires bitfield mapping instead of direct flag assignment).
|
||||
|
||||
5. **Dynamic Spawn API Incompatibility**: Phase 4's `SpawnDynamicProxyActor` used CommonLibF4 APIs that don't exist or have different signatures (`LookupByID`, `PlaceAtMe`, `IsDead` parameter).
|
||||
- **Fix**: Disabled dynamic spawning for Phase 6 testing; falls back to pre-placed proxy pool. Marked as TODO for Phase 7 API verification.
|
||||
|
||||
6. **Move Semantics**: `RemoteActionQueue` in `ProxyActorSlot` had deleted copy operators but no move operators, causing template instantiation errors.
|
||||
- **Fix**: Added explicit move semantics: `RemoteActionQueue(RemoteActionQueue&&) noexcept = default;`
|
||||
|
||||
### Build Status
|
||||
|
||||
✅ **SUCCESS** - Plugin compiles clean with only non-critical warnings (unreferenced parameters, unreachable code).
|
||||
|
||||
### Current Limitations
|
||||
|
||||
- Dynamic actor spawning disabled (uses Phase 4 pre-placed proxy pool fallback)
|
||||
- Actor state bitfield updates deferred to Phase 7
|
||||
- Some CommonLibF4 APIs need verification for FO4-specific implementations
|
||||
|
||||
### Next Steps
|
||||
|
||||
- **IMMEDIATE**: Start Phase 6 testing with proxy animations
|
||||
- **Phase 7**: Verify and fix CommonLibF4 API usage for dynamic spawning
|
||||
- **Phase 7**: Implement actor state bitfield mapping (combat, sneaking, etc.)
|
||||
- **Phase 7**: Full code cleanup and documentation
|
||||
|
||||
### Key Discovery
|
||||
|
||||
The compilation errors were mostly API surface mismatches between the codebase assumptions (based on Skyrim SE patterns) and FO4's CommonLibF4 implementation. The core animation logic is sound; we just needed to:
|
||||
1. Work with the correct C++ language features (move semantics)
|
||||
2. Match the exact logging/API signatures
|
||||
3. Use fallback mechanisms where APIs differ
|
||||
|
||||
This is a good indication that the phased, iterative approach is working - we can disable unverified Phase 4 code and proceed with Phase 6 testing.
|
||||
|
||||
```
|
||||
@@ -35,6 +35,9 @@ namespace F4T::AnimationDescriptor
|
||||
class F4AnimationDescriptor
|
||||
{
|
||||
public:
|
||||
// Default constructor (public for singleton initialization)
|
||||
F4AnimationDescriptor() = default;
|
||||
|
||||
// Get the humanoid master behavior graph descriptor (most common)
|
||||
static const F4AnimationDescriptor& GetHumanoidDescriptor();
|
||||
|
||||
@@ -75,9 +78,6 @@ namespace F4T::AnimationDescriptor
|
||||
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;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "F4AnimationDescriptor.h"
|
||||
#include "RE/A/Actor.h"
|
||||
#include "RE/A/ActorState.h"
|
||||
|
||||
namespace F4T::RemoteActionComponent
|
||||
{
|
||||
@@ -33,6 +34,10 @@ namespace F4T::RemoteActionComponent
|
||||
RemoteActionQueue(const RemoteActionQueue&) = delete;
|
||||
RemoteActionQueue& operator=(const RemoteActionQueue&) = delete;
|
||||
|
||||
// Enable moves
|
||||
RemoteActionQueue(RemoteActionQueue&&) noexcept = default;
|
||||
RemoteActionQueue& operator=(RemoteActionQueue&&) noexcept = default;
|
||||
|
||||
// Enqueue an action for replay
|
||||
void Enqueue(const RemoteActionSnapshot& a_action);
|
||||
|
||||
|
||||
@@ -13,19 +13,15 @@ namespace F4T::AnimationDescriptor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Singleton instance
|
||||
F4AnimationDescriptor g_humanoidDescriptor;
|
||||
bool g_initialized = false;
|
||||
|
||||
// Logging helpers
|
||||
void LogInfo(const std::string& a_message)
|
||||
void LogInfoMessage(const std::string& a_message)
|
||||
{
|
||||
REX::INFO(a_message);
|
||||
REX::INFO(std::string_view{ a_message });
|
||||
}
|
||||
|
||||
void LogWarning(const std::string& a_message)
|
||||
void LogWarningMessage(const std::string& a_message)
|
||||
{
|
||||
REX::WARN(a_message);
|
||||
REX::WARN(std::string_view{ a_message });
|
||||
}
|
||||
|
||||
// Safe float comparison (from F4TProxyAnimationSync)
|
||||
@@ -39,6 +35,10 @@ namespace F4T::AnimationDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
// Static singleton instance
|
||||
static F4AnimationDescriptor g_humanoidDescriptor;
|
||||
static bool g_initialized = false;
|
||||
|
||||
// ============================================================================
|
||||
// Humanoid Master Behavior Graph Variables (FO4)
|
||||
// ============================================================================
|
||||
@@ -112,7 +112,7 @@ namespace F4T::AnimationDescriptor
|
||||
m_intVariableIndexMap[m_intVariables[i]] = i;
|
||||
}
|
||||
|
||||
LogInfo(std::format(
|
||||
LogInfoMessage(std::format(
|
||||
"F4AnimationDescriptor: Initialized humanoid graph with {} floats, {} bools, {} ints",
|
||||
m_floatVariables.size(),
|
||||
m_boolVariables.size(),
|
||||
@@ -211,7 +211,7 @@ namespace F4T::AnimationDescriptor
|
||||
{
|
||||
RE::BSTSmartPointer<RE::BSAnimationGraphManager> graphManager;
|
||||
if (!a_holder.GetAnimationGraphManagerImpl(graphManager) || !graphManager) {
|
||||
LogWarning("SaveAnimationVariablesFromCache: Graph manager unavailable");
|
||||
LogWarningMessage("SaveAnimationVariablesFromCache: Graph manager unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ namespace F4T::AnimationDescriptor
|
||||
{
|
||||
RE::BSTSmartPointer<RE::BSAnimationGraphManager> graphManager;
|
||||
if (!a_holder.GetAnimationGraphManagerImpl(graphManager) || !graphManager) {
|
||||
LogWarning("LoadAnimationVariablesToCache: Graph manager unavailable");
|
||||
LogWarningMessage("LoadAnimationVariablesToCache: Graph manager unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -342,8 +342,11 @@ namespace F4T::AnimationDescriptor
|
||||
const auto& varName = m_intVariables[a_index];
|
||||
const RE::BSFixedString fixedName{ varName };
|
||||
|
||||
// Note: CommonLibF4 may not have GetGraphVariableImplInt; fall back to template
|
||||
return a_holder.GetGraphVariableImplInt(fixedName, a_outValue);
|
||||
// Note: CommonLibF4 expects int32_t, convert from uint32_t
|
||||
std::int32_t tempValue = 0;
|
||||
const auto result = a_holder.GetGraphVariableImplInt(fixedName, tempValue);
|
||||
a_outValue = static_cast<std::uint32_t>(tempValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool F4AnimationDescriptor::TryWriteFloatVariable(
|
||||
@@ -388,6 +391,7 @@ namespace F4T::AnimationDescriptor
|
||||
const auto& varName = m_intVariables[a_index];
|
||||
const RE::BSFixedString fixedName{ varName };
|
||||
|
||||
return a_holder.SetGraphVariableInt(fixedName, a_value);
|
||||
// Convert uint32_t to int32_t for API compatibility
|
||||
return a_holder.SetGraphVariableInt(fixedName, static_cast<std::int32_t>(a_value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ namespace F4T::RemoteActionComponent
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void LogInfo(const std::string& a_message)
|
||||
void LogInfoMessage(const std::string& a_message)
|
||||
{
|
||||
REX::INFO(a_message);
|
||||
REX::INFO(std::string_view{ a_message });
|
||||
}
|
||||
|
||||
void LogWarning(const std::string& a_message)
|
||||
void LogWarningMessage(const std::string& a_message)
|
||||
{
|
||||
REX::WARN(a_message);
|
||||
REX::WARN(std::string_view{ a_message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace F4T::RemoteActionComponent
|
||||
void RemoteActionQueue::Enqueue(const RemoteActionSnapshot& a_action)
|
||||
{
|
||||
if (m_actionQueue.size() >= m_maxQueueSize) {
|
||||
LogWarning(std::format(
|
||||
LogWarningMessage(std::format(
|
||||
"RemoteActionQueue: Action queue full (max={}); dropping oldest action to make room.",
|
||||
m_maxQueueSize));
|
||||
m_actionQueue.erase(m_actionQueue.begin());
|
||||
@@ -38,7 +38,7 @@ namespace F4T::RemoteActionComponent
|
||||
|
||||
m_actionQueue.push_back(a_action);
|
||||
|
||||
LogInfo(std::format(
|
||||
LogInfoMessage(std::format(
|
||||
"RemoteActionQueue: Enqueued action '{}' (type={}); queue size={}",
|
||||
a_action.eventName,
|
||||
a_action.actionType,
|
||||
@@ -48,7 +48,7 @@ namespace F4T::RemoteActionComponent
|
||||
void RemoteActionQueue::Clear()
|
||||
{
|
||||
if (!m_actionQueue.empty()) {
|
||||
LogInfo(std::format(
|
||||
LogInfoMessage(std::format(
|
||||
"RemoteActionQueue: Cleared {} pending actions.",
|
||||
m_actionQueue.size()));
|
||||
m_actionQueue.clear();
|
||||
@@ -65,7 +65,7 @@ namespace F4T::RemoteActionComponent
|
||||
m_actionQueue.erase(m_actionQueue.begin());
|
||||
|
||||
if (ApplyActionToProxy(a_proxy, action)) {
|
||||
LogInfo(std::format(
|
||||
LogInfoMessage(std::format(
|
||||
"RemoteActionQueue: Replayed action '{}' on proxy {:08X}; {} pending.",
|
||||
action.eventName,
|
||||
a_proxy.GetFormID(),
|
||||
@@ -73,7 +73,7 @@ namespace F4T::RemoteActionComponent
|
||||
return true;
|
||||
}
|
||||
|
||||
LogWarning(std::format(
|
||||
LogWarningMessage(std::format(
|
||||
"RemoteActionQueue: Failed to replay action '{}' on proxy {:08X}.",
|
||||
action.eventName,
|
||||
a_proxy.GetFormID()));
|
||||
@@ -88,7 +88,7 @@ namespace F4T::RemoteActionComponent
|
||||
TriggerActionAnimation(a_proxy, a_action);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LogWarning(std::format(
|
||||
LogWarningMessage(std::format(
|
||||
"RemoteActionQueue: Exception applying action to proxy {:08X}: {}",
|
||||
a_proxy.GetFormID(),
|
||||
e.what()));
|
||||
@@ -102,16 +102,16 @@ namespace F4T::RemoteActionComponent
|
||||
{
|
||||
// Set actor state flags from snapshot
|
||||
// This ensures the animation graph is in the correct FSM state for the action
|
||||
if (a_action.actorStateFlags1 != 0 || a_action.actorStateFlags2 != 0) {
|
||||
a_proxy.actorState.flags1 = a_action.actorStateFlags1;
|
||||
a_proxy.actorState.flags2 = a_action.actorStateFlags2;
|
||||
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Applied actor state flags to proxy {:08X}: flags1={:08X}, flags2={:08X}",
|
||||
a_proxy.GetFormID(),
|
||||
a_action.actorStateFlags1,
|
||||
a_action.actorStateFlags2));
|
||||
}
|
||||
// NOTE: Actor state management in FO4 is complex - for Phase 6 testing,
|
||||
// we'll focus on animation variables. Actor state flags may be handled separately.
|
||||
// if (a_action.actorStateFlags1 != 0 || a_action.actorStateFlags2 != 0) {
|
||||
// // Actor inherits from ActorState which has bitfields, not direct flags
|
||||
// // TODO: Phase 5.2.3 - Implement proper actor state field updates
|
||||
// LogInfoMessage(std::format(
|
||||
// "RemoteActionQueue: Actor state flags captured (flags1={:08X}, flags2={:08X}) - not yet applied",
|
||||
// a_action.actorStateFlags1,
|
||||
// a_action.actorStateFlags2));
|
||||
// }
|
||||
}
|
||||
|
||||
void RemoteActionQueue::TriggerActionAnimation(
|
||||
@@ -129,7 +129,7 @@ namespace F4T::RemoteActionComponent
|
||||
if (!descriptor.LoadAnimationVariablesToCache(
|
||||
*static_cast<RE::IAnimationGraphManagerHolder*>(&a_proxy),
|
||||
a_action.animationVariables)) {
|
||||
LogWarning(std::format(
|
||||
LogWarningMessage(std::format(
|
||||
"RemoteActionQueue: Failed to load animation variables for action '{}' on proxy {:08X}.",
|
||||
a_action.eventName,
|
||||
a_proxy.GetFormID()));
|
||||
@@ -147,7 +147,7 @@ namespace F4T::RemoteActionComponent
|
||||
//
|
||||
// F4SE may expose this differently; needs investigation
|
||||
|
||||
LogInfo(std::format(
|
||||
LogInfoMessage(std::format(
|
||||
"RemoteActionQueue: Triggered action '{}' (type={}) animation load on proxy {:08X}.",
|
||||
a_action.eventName,
|
||||
a_action.actionType,
|
||||
|
||||
@@ -2203,7 +2203,12 @@ namespace
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get proxy base actor (use custom proxy or Codsworth for testing)
|
||||
// Phase 4: Dynamic spawning implementation
|
||||
// NOTE: Disabled for Phase 6 testing - API verification needed
|
||||
// TODO: Phase 7 - Fix CommonLibF4 API calls for dynamic spawning
|
||||
//
|
||||
// Original code disabled - API calls need verification
|
||||
/*
|
||||
RE::TESNPC* proxyBase = nullptr;
|
||||
|
||||
// First try to find custom proxy base in ESP
|
||||
@@ -2232,35 +2237,8 @@ namespace
|
||||
a_remotePlayerId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Spawn the actor via PlaceAtMe
|
||||
const auto spawnedRef = a_player.PlaceAtMe(proxyBase, 1, false, true);
|
||||
if (!spawnedRef) {
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"SpawnDynamicProxyActor: PlaceAtMe failed for remote player {} with base {:08X}.",
|
||||
a_remotePlayerId,
|
||||
proxyBase->GetFormID()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* spawnedActor = spawnedRef->As<RE::Actor>();
|
||||
if (!spawnedActor) {
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"SpawnDynamicProxyActor: PlaceAtMe returned non-actor ref for remote player {}.",
|
||||
a_remotePlayerId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Store in dynamic pool for reuse
|
||||
g_dynamicProxyPool[a_remotePlayerId] = spawnedActor->GetHandle();
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"SpawnDynamicProxyActor: Spawned proxy actor {:08X} for remote player {} with base {:08X}.",
|
||||
spawnedActor->GetFormID(),
|
||||
a_remotePlayerId,
|
||||
proxyBase->GetFormID()));
|
||||
|
||||
return spawnedActor;
|
||||
*/
|
||||
return nullptr; // Phase 6: Disabled - API verification needed for Phase 7
|
||||
}
|
||||
|
||||
// Phase 4.1: Retrieve or spawn proxy for a remote player
|
||||
@@ -2275,7 +2253,8 @@ namespace
|
||||
const auto proxyRef = it->second.get();
|
||||
if (auto actor = proxyRef ? proxyRef->As<RE::Actor>() : nullptr) {
|
||||
// Verify proxy is still valid
|
||||
if (actor->GetNPC() && !actor->IsDead()) {
|
||||
// if (actor->GetNPC() && !actor->IsDead()) {
|
||||
if (actor->GetNPC()) {
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
@@ -4865,11 +4844,12 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
ApplyRuntimeProxyTransform(a_proxy, targetPosition, a_remotePlayer.angleZ, a_remotePlayer, shouldSnap, &a_slot);
|
||||
|
||||
// Phase 4.3: Apply actor state flags before animation sync
|
||||
// This signals the animation graph FSM about combat, sneaking, etc.
|
||||
if (a_remotePlayer.actorStateFlags1 != 0 || a_remotePlayer.actorStateFlags2 != 0) {
|
||||
a_proxy.actorState.flags1 = a_remotePlayer.actorStateFlags1;
|
||||
a_proxy.actorState.flags2 = a_remotePlayer.actorStateFlags2;
|
||||
}
|
||||
// NOTE: Actor state in FO4 is complex - for Phase 6 testing we focus on animations.
|
||||
// Actor state bitfields may need separate handling.
|
||||
// if (a_remotePlayer.actorStateFlags1 != 0 || a_remotePlayer.actorStateFlags2 != 0) {
|
||||
// // Actor inherits from ActorState which uses bitfields
|
||||
// // TODO: Implement proper actor state field updates (Phase 5.2.3)
|
||||
// }
|
||||
|
||||
a_slot.lifecycleState = ProxyLifecycleState::kActive;
|
||||
a_slot.holdReason.reset();
|
||||
|
||||
Reference in New Issue
Block a user