From 332deeafe172ec045fe28ce955278441b755e9c3 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 19:27:06 +0200 Subject: [PATCH] test: add more cases for testBehavior --- tests/testBehavior.cpp | 169 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/tests/testBehavior.cpp b/tests/testBehavior.cpp index 10a2950..553932e 100644 --- a/tests/testBehavior.cpp +++ b/tests/testBehavior.cpp @@ -475,3 +475,172 @@ VF_TEST(theAnimalTreeFollowsHeldFoodAndBreedsOnlyOncePerCooldown) { } } + CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) <= ctx.tuning.breedRadius); + CHECK(agent.breedCount >= 2); + for (std::size_t i = 1; i < breedTicks.size(); ++i) { + CHECK(breedTicks[i] - breedTicks[i - 1] > ctx.tuning.breedCooldownTicks); + } +} + +VF_TEST(theAnimalTreeGrazesGrassForTheFullChewDuration) { + World world = grassWorld(); + BehaviorAgent agent = makeAgent(Species::Cow, {0.5f, 1.0f, 0.5f}); + + BehaviorContext ctx = freshContext(&world); + ctx.agent = &agent; + ctx.playerPresent = false; + ctx.matePresent = false; + + BehaviorTree tree = makeGrazingAnimalTree(); + for (int step = 0; step < ctx.tuning.grazeDurationTicks - 1; ++step) tree.tick(ctx); + CHECK_EQ(ctx.blackboard.count("meals"), 0); + CHECK(agent.grazeTicks > 0); + + tree.tick(ctx); + CHECK_EQ(ctx.blackboard.count("meals"), 1); + CHECK_EQ(agent.grazeTicks, 0); +} + +VF_TEST(theSunAvoiderWalksIntoShadeAndWaitsOutTheDay) { + World world = flatWorld(); + for (int x = 3; x <= 6; ++x) { + for (int z = -1; z <= 1; ++z) world.setBlock(x, 4, z, Block::Stone); + } + + BehaviorAgent agent = makeAgent(Species::Skeleton, {0.5f, 1.0f, 0.5f}); + BehaviorContext ctx = freshContext(&world); + ctx.agent = &agent; + ctx.daylight = true; + ctx.skyLight = 15; + + BehaviorTree tree = makeSunAvoiderTree(); + CHECK(!agent.sheltered); + for (int step = 0; step < 80; ++step) tree.tick(ctx); + + CHECK(agent.sheltered); + CHECK(agent.position.x >= 3.0f); + CHECK(agent.shelterTicks > 0); + CHECK(ctx.blackboard.hasCell("shadeCell")); +} + +VF_TEST(theSunAvoiderPatrolsInsteadOfHidingAtNight) { + World world = flatWorld(); + BehaviorAgent agent = makeAgent(Species::Skeleton, {0.5f, 1.0f, 0.5f}); + Vec3 start = agent.position; + + BehaviorContext ctx = freshContext(&world); + ctx.agent = &agent; + ctx.daylight = false; + ctx.skyLight = 4; + + BehaviorTree tree = makeSunAvoiderTree(); + for (int step = 0; step < 30; ++step) tree.tick(ctx); + + CHECK(behaviorHorizontalDistance(agent.position, start) > 1.0f); + CHECK_EQ(agent.shelterTicks, 0); + CHECK(!ctx.blackboard.hasCell("shadeCell")); +} + +VF_TEST(twoRunsOfTheSameTreeOnTheSameSeedProduceTheSameNodeSequence) { + World world = grassWorld(); + + auto runOnce = [&world](std::vector& trace, Vec3& endPosition) { + BehaviorAgent agent = makeAgent(Species::Pig, {0.5f, 1.0f, 0.5f}); + agent.rng = 0x1234abcdu; + BehaviorContext ctx; + ctx.world = &world; + ctx.agent = &agent; + ctx.traceEnabled = true; + ctx.playerPresent = true; + ctx.playerPosition = {4.5f, 1.0f, 0.5f}; + ctx.playerHeldItem = ItemId::None; + + BehaviorTree tree = makeGrazingAnimalTree(); + for (int step = 0; step < 60; ++step) tree.tick(ctx); + trace = ctx.trace; + endPosition = agent.position; + }; + + std::vector firstTrace; + std::vector secondTrace; + Vec3 firstEnd; + Vec3 secondEnd; + runOnce(firstTrace, firstEnd); + runOnce(secondTrace, secondEnd); + + CHECK(firstTrace.size() > 100u); + CHECK_EQ(firstTrace.size(), secondTrace.size()); + for (std::size_t i = 0; i < firstTrace.size() && i < secondTrace.size(); ++i) { + CHECK_EQ(firstTrace[i], secondTrace[i]); + } + CHECK_NEAR(firstEnd.x, secondEnd.x, 1e-6f); + CHECK_NEAR(firstEnd.z, secondEnd.z, 1e-6f); +} + +VF_TEST(eachSpeciesMapsToTheTreeThatFitsIt) { + CHECK(roleForSpecies(Species::Zombie) == BehaviorRole::HostileHunter); + CHECK(roleForSpecies(Species::Creeper) == BehaviorRole::HostileHunter); + CHECK(roleForSpecies(Species::Skeleton) == BehaviorRole::SunAvoider); + CHECK(roleForSpecies(Species::Sheep) == BehaviorRole::GrazingAnimal); + + CHECK(breedingFoodFor(Species::Cow) == ItemId::Bread); + CHECK(breedingFoodFor(Species::Pig) == ItemId::Apple); + CHECK(breedingFoodFor(Species::Zombie) == ItemId::None); + CHECK(isBreedingFood(Species::Cow, ItemId::Bread)); + CHECK(!isBreedingFood(Species::Cow, ItemId::Apple)); + CHECK(!isBreedingFood(Species::Zombie, ItemId::None)); + + for (int index = 0; index < static_cast(BehaviorRole::Count); ++index) { + BehaviorRole role = static_cast(index); + BehaviorTree tree = makeTreeForRole(role); + CHECK(tree.structureValid()); + CHECK(tree.stats().nodeCount > 4); + CHECK(std::string(behaviorRoleName(role)) != std::string("unknown")); + } +} + +VF_TEST(theSharedRandomStreamIsReproducibleAndStaysInRange) { + uint32_t first = 0x2545f491u; + uint32_t second = 0x2545f491u; + for (int draw = 0; draw < 64; ++draw) { + CHECK_EQ(behaviorNextRandom(first), behaviorNextRandom(second)); + } + + uint32_t stream = 7u; + for (int draw = 0; draw < 200; ++draw) { + float unit = behaviorUnitFloat(stream); + CHECK(unit >= 0.0f); + CHECK(unit < 1.0f); + int ranged = behaviorRandomRange(stream, -3, 3); + CHECK(ranged >= -3); + CHECK(ranged <= 3); + } + CHECK_EQ(behaviorRandomRange(stream, 5, 5), 5); + + CHECK(behaviorCellOf(Vec3{-0.5f, 1.9f, 3.2f}) == Vec3i(-1, 1, 3)); + CHECK_NEAR(behaviorCellCenter(Vec3i{2, 3, 4}).x, 2.5f, 1e-6f); + CHECK_NEAR(behaviorCellCenter(Vec3i{2, 3, 4}).y, 3.0f, 1e-6f); + CHECK(std::string(behaviorStatusName(BehaviorStatus::Running)) == std::string("running")); +} + +VF_TEST(routeFollowingWalksCellByCellAndReportsSuccessAtTheEnd) { + World world = flatWorld(); + BehaviorAgent agent = makeAgent(Species::Cow, {0.5f, 1.0f, 0.5f}); + BehaviorContext ctx = freshContext(&world); + ctx.agent = &agent; + + CHECK(planRouteTo(ctx, Vec3i{4, 1, 0}, true)); + CHECK(agent.route.size() >= 5u); + CHECK_EQ(agent.routeCursor, 1); + + BehaviorStatus status = BehaviorStatus::Running; + int guard = 0; + while (status == BehaviorStatus::Running && guard < 200) { + status = followRouteStep(ctx); + ++guard; + } + CHECK(status == BehaviorStatus::Success); + CHECK(agent.routeFinished()); + CHECK_NEAR(agent.position.x, 4.5f, 1e-4f); + CHECK(guard < 200); +}