1294.20 ReactiveSequence의 매 Tick 재평가 규칙
1. 매 Tick 재평가의 정의
ReactiveSequence의 매 Tick 재평가(per-tick re-evaluation) 규칙이란, Sequence가 RUNNING 상태인 동안 매번 Tick을 수신할 때마다 첫 번째 자식(인덱스 0)부터 순차적으로 모든 자식을 다시 평가하는 규칙이다. 이전 Tick에서의 자식 반환 결과는 기억되지 않으며, 매 Tick이 독립적인 완전한 평가 주기를 구성한다(Colledanchise & Ogren, 2018).
2. 재평가 규칙의 상세
2.1 규칙 1: 항상 인덱스 0부터 시작
이전 Tick에서 어떤 자식이 RUNNING을 반환했는지와 무관하게, 매 Tick에서 인덱스 0의 자식부터 평가를 시작한다.
Tick N: i=0 → i=1 → i=2(RUNNING) → RUNNING
Tick N+1: i=0 → i=1 → i=2(RUNNING) → RUNNING (0부터 재시작)
Tick N+2: i=0 → i=1(FAILURE) → FAILURE (조건 변화 감지)
2.2 규칙 2: SUCCESS 자식도 재평가
SequenceWithMemory에서 건너뛰어지는 SUCCESS 자식들이 ReactiveSequence에서는 매 Tick마다 다시 평가된다. 이를 통해 조건의 유효성을 지속적으로 확인한다.
2.3 규칙 3: FAILURE 발생 시 RUNNING 자식 Halt
재평가 과정에서 이전에 SUCCESS였던 자식이 FAILURE를 반환하면, 현재 RUNNING 상태인 후속 자식에게 Halt를 전달한다.
Tick N:
children[0]: CondA → SUCCESS
children[1]: ActB → RUNNING
ReactiveSequence → RUNNING
Tick N+1:
children[0]: CondA → FAILURE ← 재평가에서 변화 감지
children[1]: ActB → Halt ← RUNNING 자식 중단
ReactiveSequence → FAILURE
2.4 규칙 4: RUNNING 이후의 자식은 미평가
하나의 자식이 RUNNING을 반환하면, 그 이후의 자식에게는 Tick이 전달되지 않는다. 이 부분은 SequenceWithMemory와 동일하다.
3. 재평가에 의한 조건 감시
ReactiveSequence의 재평가 규칙은 조건 노드를 감시(monitor) 역할로 활용하는 패턴을 가능하게 한다. 조건 노드를 앞에, 액션 노드를 뒤에 배치하면, 액션 실행 중 조건을 지속적으로 감시하는 구조가 된다.
<ReactiveSequence>
<!-- 감시 조건들 -->
<Condition ID="IsNotEmergency"/>
<Condition ID="IsBatteryOK"/>
<Condition ID="IsCommsAlive"/>
<!-- 감시 대상 액션 -->
<Action ID="ExecuteMission"/>
</ReactiveSequence>
매 Tick마다 세 가지 조건이 모두 재평가되며, 하나라도 FAILURE를 반환하면 ExecuteMission이 즉시 Halt된다.
4. 재평가의 비용 분석
4.1 Tick당 노드 방문 수
RUNNING 상태의 자식이 인덱스 k에 위치할 때, 매 Tick마다 k+1개의 노드가 방문된다.
자식 배열: [Cond₁, Cond₂, Cond₃, AsyncAct]
AsyncAct가 RUNNING일 때:
매 Tick 방문: Cond₁, Cond₂, Cond₃, AsyncAct → 4개
SequenceWithMemory에서는 AsyncAct만 방문하므로 1개이다.
4.2 총 비용 비교
비동기 작업이 M Tick 동안 RUNNING을 유지하는 경우:
- ReactiveSequence: (k+1) \times M 노드 방문
- SequenceWithMemory: k + 1 + (M-1) \times 1 = k + M 노드 방문
k가 크고 M이 긴 경우, ReactiveSequence의 비용이 유의미하게 높아진다.
5. 재평가와 액션 노드의 관계
ReactiveSequence에서 앞쪽에 배치된 액션 노드는 매 Tick마다 재실행된다. 이미 SUCCESS를 반환한 액션이 다시 실행되면, onStart()부터 다시 시작하여 의도하지 않은 동작이 발생할 수 있다. 따라서 ReactiveSequence에서는 앞쪽에 조건 노드(부수 효과 없는 순수 평가)만 배치하고, 비동기 액션은 마지막 자식에 배치하는 것이 설계 원칙이다(Faconti, 2022).
<!-- 권장 패턴: 조건 → 조건 → 액션 -->
<ReactiveSequence>
<Condition ID="CondA"/>
<Condition ID="CondB"/>
<Action ID="AsyncAction"/> <!-- 마지막에 배치 -->
</ReactiveSequence>
<!-- 비권장 패턴: 액션이 앞에 위치 -->
<ReactiveSequence>
<Action ID="SomeAction"/> <!-- 매 Tick 재실행 — 위험 -->
<Action ID="AnotherAction"/>
</ReactiveSequence>
참고 문헌
- 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/