@@ -591,3 +591,106 @@ TEST(Parallel, PauseWithRetry)
591591 // the whole process should take about 300 milliseconds
592592 ASSERT_LE (toMsec (t2 - t1) - 300 , margin_msec * 2 );
593593}
594+
595+ // Issue #819: Demonstrates that Sequence does NOT re-evaluate conditions
596+ // while a sibling action is RUNNING, whereas ReactiveSequence DOES.
597+ // This is expected behavior, not a bug.
598+ TEST (Parallel, Issue819_SequenceVsReactiveSequence)
599+ {
600+ using namespace BT ;
601+
602+ // Test 1: Regular Sequence - condition NOT re-evaluated
603+ {
604+ static const char * xml_text = R"(
605+ <root BTCPP_format="4">
606+ <BehaviorTree ID="TestTree">
607+ <Parallel success_count="2" failure_count="1">
608+ <Sequence>
609+ <TestCondition name="cond1"/>
610+ <Sleep msec="200"/>
611+ </Sequence>
612+ <Sequence>
613+ <TestCondition name="cond2"/>
614+ <Sleep msec="200"/>
615+ </Sequence>
616+ </Parallel>
617+ </BehaviorTree>
618+ </root>
619+ )" ;
620+ BehaviorTreeFactory factory;
621+ std::array<int , 2 > tick_counts = { 0 , 0 };
622+
623+ // Register conditions that count their ticks
624+ factory.registerSimpleCondition (" TestCondition" , [&](TreeNode& node) {
625+ const std::string& name = node.name ();
626+ if (name == " cond1" )
627+ tick_counts[0 ]++;
628+ else if (name == " cond2" )
629+ tick_counts[1 ]++;
630+ return NodeStatus::SUCCESS;
631+ });
632+
633+ auto tree = factory.createTreeFromText (xml_text);
634+
635+ // First tick: both conditions evaluated
636+ auto status = tree.tickExactlyOnce ();
637+ ASSERT_EQ (NodeStatus::RUNNING, status);
638+ ASSERT_EQ (1 , tick_counts[0 ]); // cond1 ticked once
639+ ASSERT_EQ (1 , tick_counts[1 ]); // cond2 ticked once
640+
641+ // Second tick: conditions should NOT be re-evaluated (Sequence behavior)
642+ std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
643+ status = tree.tickExactlyOnce ();
644+ ASSERT_EQ (NodeStatus::RUNNING, status);
645+ // Conditions are NOT re-ticked because Sequence remembers current_child_idx_
646+ ASSERT_EQ (1 , tick_counts[0 ]); // Still 1 - NOT re-evaluated
647+ ASSERT_EQ (1 , tick_counts[1 ]); // Still 1 - NOT re-evaluated
648+ }
649+
650+ // Test 2: ReactiveSequence - condition IS re-evaluated every tick
651+ {
652+ static const char * xml_text = R"(
653+ <root BTCPP_format="4">
654+ <BehaviorTree ID="TestTree">
655+ <Parallel success_count="2" failure_count="1">
656+ <ReactiveSequence>
657+ <TestCondition name="cond1"/>
658+ <Sleep msec="200"/>
659+ </ReactiveSequence>
660+ <ReactiveSequence>
661+ <TestCondition name="cond2"/>
662+ <Sleep msec="200"/>
663+ </ReactiveSequence>
664+ </Parallel>
665+ </BehaviorTree>
666+ </root>
667+ )" ;
668+ BehaviorTreeFactory factory;
669+ std::array<int , 2 > tick_counts = { 0 , 0 };
670+
671+ factory.registerSimpleCondition (" TestCondition" , [&](TreeNode& node) {
672+ const std::string& name = node.name ();
673+ if (name == " cond1" )
674+ tick_counts[0 ]++;
675+ else if (name == " cond2" )
676+ tick_counts[1 ]++;
677+ return NodeStatus::SUCCESS;
678+ });
679+
680+ auto tree = factory.createTreeFromText (xml_text);
681+
682+ // First tick
683+ auto status = tree.tickExactlyOnce ();
684+ ASSERT_EQ (NodeStatus::RUNNING, status);
685+ ASSERT_EQ (1 , tick_counts[0 ]);
686+ ASSERT_EQ (1 , tick_counts[1 ]);
687+
688+ // Second tick: conditions SHOULD be re-evaluated (ReactiveSequence behavior)
689+ std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
690+ status = tree.tickExactlyOnce ();
691+ ASSERT_EQ (NodeStatus::RUNNING, status);
692+ // Conditions ARE re-ticked because ReactiveSequence always starts from index 0
693+ ASSERT_EQ (2 , tick_counts[0 ]); // Re-evaluated!
694+ ASSERT_EQ (2 , tick_counts[1 ]); // Re-evaluated!
695+ }
696+ }
0 commit comments