test: extend coverage of testBehavior

This commit is contained in:
dualfroz committed 2026-09-10 19:14:01 +02:00
1 parent 8103be3f96
commit e7781bb5ca
1 file changed
+162
+162
View File
@@ -313,3 +313,165 @@ VF_TEST(timeLimitCutsOffAChildThatNeverSettles) {
CHECK_EQ(runs, 4);
}
VF_TEST(parallelSettlesOnItsSuccessAndFailureThresholds) {
int slowRuns = 0;
int slowProgress = 0;
auto wide = std::unique_ptr<ParallelNode>(new ParallelNode("parallel", 2, 2));
wide->add(constantAction("ok", BehaviorStatus::Success, nullptr));
wide->add(slowAction("slow", 2, &slowRuns, &slowProgress));
wide->add(constantAction("nope", BehaviorStatus::Failure, nullptr));
BehaviorTree tree(std::move(wide));
BehaviorContext ctx;
// One success, one failure, one still running: neither threshold is met.
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
// The slow child succeeds on its third visit, which is the second success.
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(slowRuns, 3);
auto doomed = std::unique_ptr<ParallelNode>(new ParallelNode("parallel", 3, 2));
doomed->add(constantAction("no", BehaviorStatus::Failure, nullptr));
doomed->add(constantAction("alsoNo", BehaviorStatus::Failure, nullptr));
doomed->add(constantAction("yes", BehaviorStatus::Success, nullptr));
BehaviorTree unlucky(std::move(doomed));
CHECK(unlucky.tick(ctx) == BehaviorStatus::Failure);
}
VF_TEST(anOverDeepTreeIsRejectedInsteadOfOverflowingTheStack) {
std::unique_ptr<BehaviorNode> node = constantAction("leaf", BehaviorStatus::Success, nullptr);
for (int level = 0; level < maxBehaviorDepth + 12; ++level) {
node = std::unique_ptr<BehaviorNode>(new InverterNode("wrap", std::move(node)));
}
BehaviorTree tree(std::move(node));
CHECK(!tree.structureValid());
CHECK(tree.stats().depth > maxBehaviorDepth);
CHECK_EQ(tree.stats().nodeCount, maxBehaviorDepth + 13);
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(ctx.tick, 0);
CHECK_EQ(tree.tickCount(), 0);
}
VF_TEST(aTreeExactlyAtTheDepthLimitStillRuns) {
std::unique_ptr<BehaviorNode> node = constantAction("leaf", BehaviorStatus::Success, nullptr);
for (int level = 0; level < maxBehaviorDepth - 1; ++level) {
node = std::unique_ptr<BehaviorNode>(new InverterNode("wrap", std::move(node)));
}
BehaviorTree tree(std::move(node));
CHECK_EQ(tree.stats().depth, maxBehaviorDepth);
CHECK(tree.structureValid());
BehaviorContext ctx;
tree.tick(ctx);
CHECK_EQ(ctx.tick, 1);
}
VF_TEST(theTraceRecordsEveryNodeVisitedInOrder) {
auto root = std::unique_ptr<SequenceNode>(new SequenceNode("root"));
root->add(constantAction("first", BehaviorStatus::Success, nullptr));
root->add(constantAction("second", BehaviorStatus::Failure, nullptr));
root->add(constantAction("third", BehaviorStatus::Success, nullptr));
BehaviorTree tree(std::move(root));
BehaviorContext ctx;
ctx.traceEnabled = true;
tree.tick(ctx);
std::vector<std::string> expected{"root", "first", "second"};
CHECK_EQ(ctx.trace.size(), expected.size());
for (std::size_t i = 0; i < expected.size() && i < ctx.trace.size(); ++i) {
CHECK_EQ(ctx.trace[i], expected[i]);
}
}
VF_TEST(theHostileTreePathsToThePlayerAndLandsRepeatedBlows) {
World world = flatWorld();
BehaviorAgent agent = makeAgent(Species::Zombie, {2.5f, 1.0f, 2.5f});
BehaviorContext ctx = freshContext(&world);
ctx.agent = &agent;
ctx.playerPresent = true;
ctx.playerPosition = {10.5f, 1.0f, 2.5f};
BehaviorTree tree = makeHostileHunterTree();
CHECK(tree.structureValid());
float startDistance = behaviorHorizontalDistance(agent.position, ctx.playerPosition);
for (int step = 0; step < 120; ++step) tree.tick(ctx);
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) < startDistance);
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) <=
speciesStats(Species::Zombie).attackRange);
CHECK(agent.attacksLanded >= 2);
CHECK(ctx.blackboard.flag("engaged"));
CHECK_NEAR(ctx.blackboard.number("lastAttackDamage"),
speciesStats(Species::Zombie).attackDamage, 1e-6f);
CHECK(agent.routeGoal == behaviorCellOf(ctx.playerPosition));
CHECK(agent.routePlannedTick > 0);
}
VF_TEST(theHostileTreeWalksHomeOnceTheTargetIsGone) {
World world = flatWorld();
BehaviorAgent agent = makeAgent(Species::Zombie, {9.5f, 1.0f, 2.5f});
agent.homePosition = {2.5f, 1.0f, 2.5f};
BehaviorContext ctx = freshContext(&world);
ctx.agent = &agent;
ctx.playerPresent = false;
BehaviorTree tree = makeHostileHunterTree();
for (int step = 0; step < 120; ++step) tree.tick(ctx);
CHECK(behaviorHorizontalDistance(agent.position, agent.homePosition) <=
ctx.tuning.homeReturnRadius);
CHECK_EQ(agent.attacksLanded, 0);
}
VF_TEST(theAnimalTreeRunsAwayFromAnEmptyHandedPlayer) {
World world = grassWorld();
BehaviorAgent agent = makeAgent(Species::Sheep, {0.5f, 1.0f, 0.5f});
BehaviorContext ctx = freshContext(&world);
ctx.agent = &agent;
ctx.playerPresent = true;
ctx.playerPosition = {2.5f, 1.0f, 0.5f};
ctx.playerHeldItem = ItemId::None;
BehaviorTree tree = makeGrazingAnimalTree();
float before = behaviorHorizontalDistance(agent.position, ctx.playerPosition);
for (int step = 0; step < 20; ++step) tree.tick(ctx);
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) > before + 2.0f);
CHECK_EQ(agent.breedCount, 0);
}
VF_TEST(theAnimalTreeFollowsHeldFoodAndBreedsOnlyOncePerCooldown) {
World world = grassWorld();
BehaviorAgent agent = makeAgent(Species::Sheep, {0.5f, 1.0f, 0.5f});
BehaviorContext ctx = freshContext(&world);
ctx.agent = &agent;
ctx.playerPresent = true;
ctx.playerPosition = {6.5f, 1.0f, 0.5f};
ctx.playerHeldItem = breedingFoodFor(Species::Sheep);
ctx.matePresent = true;
ctx.matePosition = {5.0f, 1.0f, 0.5f};
BehaviorTree tree = makeGrazingAnimalTree();
std::vector<long long> breedTicks;
int lastCount = 0;
for (int step = 0; step < 400; ++step) {
tree.tick(ctx);
if (agent.breedCount != lastCount) {
lastCount = agent.breedCount;
breedTicks.push_back(ctx.tick);
}
}