- Change SpellPriority::OPTIONAL to OPTIONAL_PRIORITY (line 69) - Fix DecisionResult: recommendedAction -> actionId - Fix DecisionResult: confidence -> consensusScore - Fix DecisionResult: totalVotes -> contributingVotes.size() - Fix DecisionResult: reasoning -> fusionReasoning - Remove non-existent winningSource field references - Fix SetSystemWeights call (5 floats, not array) - Fix ResetStatistics -> ResetStats - Remove non-existent SetUrgencyThreshold/GetUrgencyThreshold - Fix CombatContext to use 8 values (no RAID_MYTHIC) Co-Authored-By: Claude Opus 4.5 <[email protected]> Signed-off-by: luis <[email protected]>
435 lines
12 KiB
C++
435 lines
12 KiB
C++
/*
|
||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify it
|
||
* under the terms of the GNU General Public License as published by the
|
||
* Free Software Foundation; either version 2 of the License, or (at your
|
||
* option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
* more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along
|
||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "tc_catch2.h"
|
||
#include "AI/Decision/DecisionFusionSystem.h"
|
||
|
||
using namespace Playerbot::bot::ai;
|
||
|
||
TEST_CASE("DecisionFusion - DecisionVote Weighted Score", "[Phase5][DecisionFusion]")
|
||
{
|
||
SECTION("Weighted score calculation")
|
||
{
|
||
DecisionVote vote(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345, // actionId
|
||
nullptr, // target
|
||
0.8f, // confidence
|
||
0.6f, // urgency
|
||
"Test vote"
|
||
);
|
||
|
||
float systemWeight = 1.0f;
|
||
float weightedScore = vote.CalculateWeightedScore(systemWeight);
|
||
|
||
// Score = confidence × urgency × systemWeight
|
||
// 0.8 × 0.6 × 1.0 = 0.48
|
||
REQUIRE(weightedScore == Approx(0.48f));
|
||
}
|
||
|
||
SECTION("Weighted score with different system weight")
|
||
{
|
||
DecisionVote vote(
|
||
DecisionSource::ACTION_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
1.0f, // confidence
|
||
1.0f, // urgency
|
||
"Maximum vote"
|
||
);
|
||
|
||
float systemWeight = 0.5f;
|
||
float weightedScore = vote.CalculateWeightedScore(systemWeight);
|
||
|
||
// 1.0 × 1.0 × 0.5 = 0.5
|
||
REQUIRE(weightedScore == Approx(0.5f));
|
||
}
|
||
|
||
SECTION("Zero confidence gives zero score")
|
||
{
|
||
DecisionVote vote(
|
||
DecisionSource::BEHAVIOR_TREE,
|
||
12345,
|
||
nullptr,
|
||
0.0f, // confidence
|
||
1.0f, // urgency
|
||
"Zero confidence"
|
||
);
|
||
|
||
float weightedScore = vote.CalculateWeightedScore(1.0f);
|
||
REQUIRE(weightedScore == Approx(0.0f));
|
||
}
|
||
|
||
SECTION("Zero urgency gives zero score")
|
||
{
|
||
DecisionVote vote(
|
||
DecisionSource::ADAPTIVE_BEHAVIOR,
|
||
12345,
|
||
nullptr,
|
||
1.0f, // confidence
|
||
0.0f, // urgency
|
||
"Zero urgency"
|
||
);
|
||
|
||
float weightedScore = vote.CalculateWeightedScore(1.0f);
|
||
REQUIRE(weightedScore == Approx(0.0f));
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Vote Fusion Logic", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("Single vote returns that action")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Only vote"
|
||
);
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
REQUIRE(result.actionId == 12345);
|
||
REQUIRE(result.consensusScore > 0.0f);
|
||
}
|
||
|
||
SECTION("Multiple votes for same action increase confidence")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.7f,
|
||
0.6f,
|
||
"Vote 1"
|
||
);
|
||
votes.emplace_back(
|
||
DecisionSource::ACTION_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Vote 2"
|
||
);
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
REQUIRE(result.actionId == 12345);
|
||
REQUIRE(result.contributingVotes.size() >= 1);
|
||
}
|
||
|
||
SECTION("Empty vote list returns no action")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
REQUIRE(result.actionId == 0);
|
||
REQUIRE(result.consensusScore == 0.0f);
|
||
}
|
||
|
||
SECTION("High urgency vote wins over low urgency")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
|
||
// Low urgency vote
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
11111,
|
||
nullptr,
|
||
0.9f, // high confidence
|
||
0.3f, // low urgency
|
||
"Low urgency"
|
||
);
|
||
|
||
// High urgency vote (should win)
|
||
votes.emplace_back(
|
||
DecisionSource::ACTION_PRIORITY,
|
||
22222,
|
||
nullptr,
|
||
0.7f, // moderate confidence
|
||
0.95f, // very high urgency
|
||
"High urgency"
|
||
);
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
// High urgency threshold should prioritize the second vote
|
||
// Actual behavior depends on urgency threshold config
|
||
REQUIRE(result.actionId != 0);
|
||
REQUIRE(result.IsValid());
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - System Weights", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("Set custom weights")
|
||
{
|
||
// SetSystemWeights takes 5 separate float parameters
|
||
fusion.SetSystemWeights(
|
||
1.0f, // BEHAVIOR_PRIORITY
|
||
0.8f, // ACTION_PRIORITY
|
||
0.6f, // BEHAVIOR_TREE
|
||
0.4f, // ADAPTIVE_BEHAVIOR
|
||
0.5f // WEIGHTING_SYSTEM
|
||
);
|
||
|
||
// Weights are set - verified through compilation
|
||
REQUIRE(true);
|
||
}
|
||
|
||
SECTION("Get system weights")
|
||
{
|
||
fusion.SetSystemWeights(0.25f, 0.15f, 0.30f, 0.10f, 0.20f);
|
||
auto weights = fusion.GetSystemWeights();
|
||
// Weights should be normalized
|
||
REQUIRE(weights.size() == static_cast<size_t>(DecisionSource::MAX));
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Debug Logging", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("Enable debug logging")
|
||
{
|
||
fusion.EnableDebugLogging(true);
|
||
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Test vote"
|
||
);
|
||
|
||
fusion.FuseDecisions(votes);
|
||
// Debug logging enabled - verified through compilation
|
||
REQUIRE(true);
|
||
}
|
||
|
||
SECTION("Disable debug logging")
|
||
{
|
||
fusion.EnableDebugLogging(false);
|
||
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Test vote"
|
||
);
|
||
|
||
fusion.FuseDecisions(votes);
|
||
// Debug logging disabled - verified through compilation
|
||
REQUIRE(true);
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Statistics Tracking", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("Statistics are updated after fusion")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Vote 1"
|
||
);
|
||
votes.emplace_back(
|
||
DecisionSource::ACTION_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.7f,
|
||
0.6f,
|
||
"Vote 2"
|
||
);
|
||
|
||
DecisionResult result1 = fusion.FuseDecisions(votes);
|
||
DecisionResult result2 = fusion.FuseDecisions(votes);
|
||
|
||
auto stats = fusion.GetStats();
|
||
|
||
// At least 2 decisions have been made
|
||
REQUIRE(stats.totalDecisions >= 2);
|
||
}
|
||
|
||
SECTION("Reset statistics")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
12345,
|
||
nullptr,
|
||
0.8f,
|
||
0.7f,
|
||
"Vote"
|
||
);
|
||
|
||
fusion.FuseDecisions(votes);
|
||
fusion.ResetStats();
|
||
|
||
auto stats = fusion.GetStats();
|
||
REQUIRE(stats.totalDecisions == 0);
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - DecisionSource Enumeration", "[Phase5][DecisionFusion]")
|
||
{
|
||
SECTION("All decision sources are defined")
|
||
{
|
||
DecisionSource sources[] = {
|
||
DecisionSource::BEHAVIOR_PRIORITY,
|
||
DecisionSource::ACTION_PRIORITY,
|
||
DecisionSource::BEHAVIOR_TREE,
|
||
DecisionSource::ADAPTIVE_BEHAVIOR,
|
||
DecisionSource::WEIGHTING_SYSTEM
|
||
};
|
||
|
||
REQUIRE(sizeof(sources) / sizeof(sources[0]) == 5);
|
||
}
|
||
|
||
SECTION("DecisionSource::MAX is last value")
|
||
{
|
||
// MAX should be the sentinel value
|
||
REQUIRE(static_cast<uint8>(DecisionSource::MAX) == 5);
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - DecisionResult Structure", "[Phase5][DecisionFusion]")
|
||
{
|
||
SECTION("DecisionResult has expected fields")
|
||
{
|
||
DecisionResult result;
|
||
result.actionId = 12345;
|
||
result.target = nullptr;
|
||
result.consensusScore = 0.85f;
|
||
result.fusionReasoning = "Test reasoning";
|
||
|
||
REQUIRE(result.actionId == 12345);
|
||
REQUIRE(result.consensusScore == Approx(0.85f));
|
||
REQUIRE(result.fusionReasoning == "Test reasoning");
|
||
REQUIRE(result.IsValid());
|
||
}
|
||
|
||
SECTION("Invalid result has actionId 0")
|
||
{
|
||
DecisionResult result;
|
||
REQUIRE(result.actionId == 0);
|
||
REQUIRE(!result.IsValid());
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Context-Based Fusion", "[Phase5][DecisionFusion]")
|
||
{
|
||
SECTION("Combat contexts are defined")
|
||
{
|
||
CombatContext contexts[] = {
|
||
CombatContext::SOLO,
|
||
CombatContext::GROUP,
|
||
CombatContext::DUNGEON_TRASH,
|
||
CombatContext::DUNGEON_BOSS,
|
||
CombatContext::RAID_NORMAL,
|
||
CombatContext::RAID_HEROIC,
|
||
CombatContext::PVP_ARENA,
|
||
CombatContext::PVP_BG
|
||
};
|
||
|
||
REQUIRE(sizeof(contexts) / sizeof(contexts[0]) == 8);
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Unanimous Votes", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("All votes for same action are unanimous")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.8f, 0.7f, "Vote 1");
|
||
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 12345, nullptr, 0.9f, 0.8f, "Vote 2");
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_TREE, 12345, nullptr, 0.7f, 0.6f, "Vote 3");
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
// All votes for 12345, should be high confidence
|
||
REQUIRE(result.actionId == 12345);
|
||
REQUIRE(result.IsValid());
|
||
}
|
||
|
||
SECTION("Mixed votes are not unanimous")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 11111, nullptr, 0.8f, 0.7f, "Vote 1");
|
||
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.9f, 0.8f, "Vote 2");
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_TREE, 33333, nullptr, 0.7f, 0.6f, "Vote 3");
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
// Different actions, winner determined by weighted scores
|
||
REQUIRE(result.actionId != 0);
|
||
REQUIRE(result.IsValid());
|
||
}
|
||
}
|
||
|
||
TEST_CASE("DecisionFusion - Edge Cases", "[Phase5][DecisionFusion]")
|
||
{
|
||
DecisionFusionSystem fusion;
|
||
|
||
SECTION("All votes with zero confidence")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.0f, 0.5f, "Zero conf 1");
|
||
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.0f, 0.6f, "Zero conf 2");
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
// With zero confidence, no clear winner but should not crash
|
||
// Result may or may not be valid depending on implementation
|
||
REQUIRE(true); // Just verify it doesn't crash
|
||
}
|
||
|
||
SECTION("All votes with zero urgency")
|
||
{
|
||
std::vector<DecisionVote> votes;
|
||
votes.emplace_back(DecisionSource::BEHAVIOR_PRIORITY, 12345, nullptr, 0.8f, 0.0f, "Zero urg 1");
|
||
votes.emplace_back(DecisionSource::ACTION_PRIORITY, 22222, nullptr, 0.9f, 0.0f, "Zero urg 2");
|
||
|
||
DecisionResult result = fusion.FuseDecisions(votes);
|
||
|
||
// With zero urgency, weighted scores are all 0
|
||
// Result may or may not be valid depending on implementation
|
||
REQUIRE(true); // Just verify it doesn't crash
|
||
}
|
||
}
|