1293.38 데코레이터 노드의 자식 Tick 변환
1. 자식 Tick 변환의 개념
데코레이터 노드의 자식 Tick 변환(child tick transformation)이란, 데코레이터가 자식 노드의 반환 상태를 수신하여 다른 상태로 변환한 후 부모에게 전달하는 과정을 말한다. 이 변환은 자식의 행동 자체를 변경하지 않으면서, 행동의 결과에 대한 상위 트리의 해석을 조정하는 역할을 수행한다. 데코레이터에 의한 상태 변환은 행동 트리에서 기본 행동 노드를 수정 없이 재사용하면서 다양한 실행 정책을 적용하는 핵심 메커니즘이다(Colledanchise & Ogren, 2018).
2. 상태 변환 함수의 형식화
데코레이터의 상태 변환을 함수 f로 형식화하면 다음과 같다.
f: \{SUCCESS, FAILURE, RUNNING\} \rightarrow \{SUCCESS, FAILURE, RUNNING\}
데코레이터 D의 반환 상태는 자식 C의 반환 상태에 대한 변환 함수의 적용 결과이다.
D.\text{status} = f(C.\text{status})
각 데코레이터 유형은 고유한 변환 함수 f를 정의하며, 이 함수는 전사(surjective)일 수도, 상수 함수일 수도 있다.
3. 주요 상태 변환 데코레이터
3.1 Inverter (반전)
Inverter는 자식의 SUCCESS와 FAILURE를 상호 교환한다.
f_{inverter}(s) = \begin{cases} FAILURE & \text{if } s = SUCCESS \\ SUCCESS & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \end{cases}
class InverterNode : public BT::DecoratorNode {
public:
BT::NodeStatus tick() override {
auto child_status = child_node_->executeTick();
switch (child_status) {
case BT::NodeStatus::SUCCESS:
return BT::NodeStatus::FAILURE;
case BT::NodeStatus::FAILURE:
return BT::NodeStatus::SUCCESS;
case BT::NodeStatus::RUNNING:
return BT::NodeStatus::RUNNING;
}
}
};
Inverter는 조건 노드의 논리적 부정(NOT)을 구현할 때 주로 사용된다. 예를 들어 IsObstacleDetected 조건 노드에 Inverter를 적용하면 IsPathClear와 동등한 의미론을 얻는다.
<Inverter>
<IsObstacleDetected/>
</Inverter>
<!-- IsPathClear와 동등 -->
3.2 ForceSuccess (강제 성공)
ForceSuccess는 자식의 FAILURE를 SUCCESS로 변환한다.
f_{force\_success}(s) = \begin{cases} SUCCESS & \text{if } s = SUCCESS \\ SUCCESS & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \end{cases}
이 변환은 자식의 실패가 상위 트리의 실행 흐름에 영향을 미치지 않아야 하는 경우에 사용된다. 예를 들어, Sequence 내에서 선택적 작업(optional task)의 실패가 후속 작업의 실행을 방해하지 않도록 할 때 적용된다.
<Sequence>
<NavigateToGoal/>
<ForceSuccess>
<PlayArrivalSound/> <!-- 실패해도 시퀀스 계속 -->
</ForceSuccess>
<PerformTask/>
</Sequence>
3.3 ForceFailure (강제 실패)
ForceFailure는 자식의 SUCCESS를 FAILURE로 변환한다.
f_{force\_failure}(s) = \begin{cases} FAILURE & \text{if } s = SUCCESS \\ FAILURE & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \end{cases}
이 변환은 Fallback 노드 내에서 특정 자식의 성공이 Fallback의 조기 종료를 유발하지 않아야 하는 경우에 사용된다.
3.4 KeepRunningUntilFailure
이 데코레이터는 자식의 SUCCESS를 RUNNING으로 변환하여, 자식이 FAILURE를 반환할 때까지 반복적으로 Tick을 전파한다.
f_{keep\_running}(s) = \begin{cases} RUNNING & \text{if } s = SUCCESS \\ FAILURE & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \end{cases}
BT::NodeStatus tick() override {
auto child_status = child_node_->executeTick();
if (child_status == BT::NodeStatus::FAILURE) {
return BT::NodeStatus::FAILURE;
}
return BT::NodeStatus::RUNNING;
}
이 변환은 조건이 위반될 때까지 감시를 지속하는 패턴에 유용하다.
4. 조건부 상태 변환
일부 데코레이터는 내부 상태에 따라 변환 함수가 달라진다. 이 경우 변환 함수는 자식의 반환 상태뿐 아니라 데코레이터의 내부 상태에도 의존한다.
D.\text{status} = f(C.\text{status}, D.\text{internal\_state})
4.1 Repeat 데코레이터의 상태 변환
Repeat 데코레이터는 반복 카운터 k에 따라 자식의 SUCCESS를 다르게 변환한다.
f_{repeat}(s, k) = \begin{cases} RUNNING & \text{if } s = SUCCESS \text{ and } k < N \\ SUCCESS & \text{if } s = SUCCESS \text{ and } k \geq N \\ FAILURE & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \end{cases}
여기서 N은 목표 반복 횟수이다. 자식이 SUCCESS를 반환할 때마다 카운터가 증가하고, 목표 횟수에 도달하면 비로소 SUCCESS를 상위로 전달한다.
4.2 Retry 데코레이터의 상태 변환
Retry 데코레이터는 재시도 카운터 k에 따라 자식의 FAILURE를 다르게 변환한다.
f_{retry}(s, k) = \begin{cases} SUCCESS & \text{if } s = SUCCESS \\ RUNNING & \text{if } s = FAILURE \text{ and } k < M \\ FAILURE & \text{if } s = FAILURE \text{ and } k \geq M \\ RUNNING & \text{if } s = RUNNING \end{cases}
여기서 M은 최대 재시도 횟수이다.
5. 시간 기반 상태 변환
5.1 Timeout 데코레이터
Timeout 데코레이터는 경과 시간에 따라 자식의 RUNNING 상태를 FAILURE로 변환한다.
f_{timeout}(s, t) = \begin{cases} SUCCESS & \text{if } s = SUCCESS \\ FAILURE & \text{if } s = FAILURE \\ RUNNING & \text{if } s = RUNNING \text{ and } t < T_{max} \\ FAILURE & \text{if } s = RUNNING \text{ and } t \geq T_{max} \end{cases}
여기서 t는 자식이 RUNNING 상태를 시작한 이후의 경과 시간이고, T_{max}는 허용 시간이다.
BT::NodeStatus tick() override {
if (child_node_->status() == BT::NodeStatus::IDLE) {
start_time_ = std::chrono::steady_clock::now();
}
auto elapsed = std::chrono::steady_clock::now() - start_time_;
if (elapsed > timeout_duration_) {
child_node_->halt();
return BT::NodeStatus::FAILURE;
}
return child_node_->executeTick();
}
5.2 Delay 데코레이터
Delay 데코레이터는 지정된 시간 동안 자식에 Tick을 전파하지 않고 RUNNING을 반환하며, 시간이 경과한 후에야 자식에 Tick을 전파한다.
f_{delay}(t) = \begin{cases} RUNNING & \text{if } t < T_{delay} \\ C.\text{status} & \text{if } t \geq T_{delay} \end{cases}
6. 상태 변환의 합성
복수의 데코레이터를 연쇄적으로 배치하면, 상태 변환 함수가 합성된다. 내부 데코레이터의 변환이 먼저 적용되고, 외부 데코레이터의 변환이 그 결과에 적용된다.
D_{outer}.\text{status} = f_{outer}(f_{inner}(C.\text{status}))
예를 들어, Inverter 내부에 ForceSuccess를 배치하면 다음과 같은 합성 변환이 형성된다.
Inverter
└── ForceSuccess
└── Child
| 자식 상태 | ForceSuccess 출력 | Inverter 출력 |
|---|---|---|
| SUCCESS | SUCCESS | FAILURE |
| FAILURE | SUCCESS | FAILURE |
| RUNNING | RUNNING | RUNNING |
이 합성은 자식이 SUCCESS 또는 FAILURE를 반환하면 항상 FAILURE를 반환하는 변환으로, ForceFailure와 동등하다.
7. RUNNING 상태 보존의 원칙
모든 표준 데코레이터에서 RUNNING 상태는 보존된다는 공통 원칙이 존재한다. 즉, 자식이 RUNNING을 반환하면 데코레이터도 RUNNING을 반환한다(Timeout 데코레이터의 시간 초과 경우 제외). 이 원칙은 비동기 노드의 진행 상태 정보가 상위 트리에 정확하게 전달되도록 보장한다.
\forall f \in \text{StandardDecorators}: f(RUNNING) = RUNNING \quad (\text{시간 초과 조건 제외})
RUNNING 상태를 임의로 SUCCESS나 FAILURE로 변환하면, 비동기 작업이 완료되지 않은 상태에서 상위 트리가 완료로 판정하게 되어, 자원 누수나 예상치 못한 행동이 발생할 수 있다. Timeout과 같이 RUNNING을 변환하는 데코레이터는 반드시 자식에 Halt를 호출하여 진행 중인 작업을 안전하게 중단시켜야 한다.
8. 상태 변환의 로봇공학 적용
| 데코레이터 | 변환 규칙 | 로봇공학 사용 사례 |
|---|---|---|
| Inverter | SUCCESS↔FAILURE | 조건 부정: IsObstacleDetected → IsPathClear |
| ForceSuccess | FAILURE→SUCCESS | 선택적 작업의 실패 무시 |
| ForceFailure | SUCCESS→FAILURE | Fallback 내 부수 효과 실행 |
| Repeat | SUCCESS→RUNNING (N회 미만) | 순찰 경로 반복 |
| Retry | FAILURE→RUNNING (M회 미만) | 통신 재시도 |
| Timeout | RUNNING→FAILURE (시간 초과) | 작업 시간 제한 |
| Delay | (대기)→자식 상태 | 작업 시작 지연 |
참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D. (2022). BehaviorTree.CPP documentation and API reference. https://www.behaviortree.dev/