From 1eb3a523fd8aac8da3a007c0647f448ff12c67bc Mon Sep 17 00:00:00 2001 From: Jonas Otto Date: Thu, 29 Oct 2026 11:08:33 +0100 Subject: [PATCH 1/4] add unit test for subtree with default values and autoremap enabled --- tests/gtest_subtree.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/gtest_subtree.cpp b/tests/gtest_subtree.cpp index 1808d87e4..8c65251e7 100644 --- a/tests/gtest_subtree.cpp +++ b/tests/gtest_subtree.cpp @@ -594,6 +594,73 @@ TEST(SubTree, SubtreeModels) tree.tickWhileRunning(); } +TEST(SubTree, SubtreeModelDefault) +{ + // clang-format off + + static const char* xml_text = R"( + + + + + + + + + + + + + + + + + + + + )"; + + // clang-format on + + BehaviorTreeFactory factory; + auto tree = factory.createTreeFromText(xml_text); + tree.tickWhileRunning(); +} + + +TEST(SubTree, SubtreeModelDefaultWithAutoremap) +{ + // clang-format off + + static const char* xml_text = R"( + + + + + + + + + + + + + + + + + + + + )"; + + // clang-format on + + BehaviorTreeFactory factory; + auto tree = factory.createTreeFromText(xml_text); + tree.tickWhileRunning(); +} + class PrintToConsole : public BT::SyncActionNode { public: From 51634d5f67c55f41b7381a6e323a027ac86f13f7 Mon Sep 17 00:00:00 2001 From: Jonas Otto Date: Thu, 29 Oct 2026 11:13:05 +0100 Subject: [PATCH 2/4] clang-format --- tests/gtest_subtree.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/gtest_subtree.cpp b/tests/gtest_subtree.cpp index 8c65251e7..d1bafd75a 100644 --- a/tests/gtest_subtree.cpp +++ b/tests/gtest_subtree.cpp @@ -627,7 +627,6 @@ TEST(SubTree, SubtreeModelDefault) tree.tickWhileRunning(); } - TEST(SubTree, SubtreeModelDefaultWithAutoremap) { // clang-format off From a9e6ab44add965d4e032532217fb47d9c3d53ad7 Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Sat, 7 Nov 2026 17:42:06 +0100 Subject: [PATCH 3/4] Apply SubTree model input defaults with _autoremap (#1271) With _autoremap="true" the SubTree model was skipped entirely, so an input_port default was never applied and reading the port failed with "Variable not found". Now, for a modelled input port that is not explicitly remapped, a parent key of the same name still wins (autoremap); if no such entry exists when the tree is created, the literal default is set in the SubTree blackboard, as it is without autoremap. Output/inout ports and "{key}" defaults keep the previous autoremap behavior. Co-Authored-By: Claude Opus 5.5 --- src/xml_parsing.cpp | 12 ++++++++++++ tests/gtest_subtree.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index 40e773742..5663c7889 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -1208,6 +1208,18 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree( for(const auto& [port_name, port_info] : subtree_model_ports) { auto it = subtree_remapping.find(port_name); + const auto& default_value = port_info.defaultValueString(); + // _autoremap shares the port with the parent key of the same name; if + // that key doesn't exist (yet), an input port gets its literal default + // (#1271). NOTE: checked at creation time, so a parent key created + // later (e.g. by a Script) is shadowed by the default. + if(it == subtree_remapping.end() && do_autoremap && + port_info.direction() == PortDirection::INPUT && !default_value.empty() && + !TreeNode::isBlackboardPointer(default_value) && + !new_bb->getEntry(port_name)) + { + subtree_remapping.insert({ port_name, default_value }); + } // don't override existing remapping if(it == subtree_remapping.end() && !do_autoremap) { diff --git a/tests/gtest_subtree.cpp b/tests/gtest_subtree.cpp index a09b86736..47c1efe41 100644 --- a/tests/gtest_subtree.cpp +++ b/tests/gtest_subtree.cpp @@ -661,6 +661,36 @@ TEST(SubTree, SubtreeModelDefaultWithAutoremap) tree.tickWhileRunning(); } +// With _autoremap, a parent key of the same name wins over the model default +// (#1271), and the default stays local to the SubTree. +TEST(SubTree, SubtreeModelDefaultWithAutoremapParentWins) +{ + static const char* xml_text = R"( + + + + + + + + + + + + + + + +)"; + + BehaviorTreeFactory factory; + auto root_bb = Blackboard::create(); + root_bb->set("in_value", 7); + auto tree = factory.createTreeFromText(xml_text, root_bb); + EXPECT_EQ(tree.tickWhileRunning(), NodeStatus::SUCCESS); + EXPECT_FALSE(root_bb->getEntry("other")); +} + class PrintToConsole : public BT::SyncActionNode { public: From 7704b2f1903964761514267775dfafeeb3224bab Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Sat, 7 Nov 2026 18:05:24 +0100 Subject: [PATCH 4/4] Document and pin the creation-time rule for SubTree model defaults (#1271) With _autoremap="true", a SubTree model input_port literal default is applied only if no entry with that name exists in the parent blackboard chain when the tree is created. Document this (including the workaround: set the key in the blackboard before createTree, or remap the port explicitly) and add a test that pins it: a key written later by a Script does not win over the default. Co-Authored-By: Claude Opus 5.5 --- docs/PORT_CONNECTION_RULES.md | 6 ++++++ tests/gtest_subtree.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/docs/PORT_CONNECTION_RULES.md b/docs/PORT_CONNECTION_RULES.md index 61a964875..23f2523d1 100644 --- a/docs/PORT_CONNECTION_RULES.md +++ b/docs/PORT_CONNECTION_RULES.md @@ -179,6 +179,12 @@ InputPort("pointE", R"(json:{"x":9,"y":10})", "description") **Test reference:** `gtest_ports.cpp` +## SubTree Model Defaults with `_autoremap` + +A `` entry in `` can declare an `input_port` with a literal `default`. With `_autoremap="true"`, that default is applied **only if no entry with that name exists in the parent blackboard chain at the moment the tree is created**. If the key is missing at that point, the literal default is stored in the SubTree's own blackboard and shadows the parent: a value written to the parent later (for instance by a `