1294.42 FallbackWithMemory의 이전 실패 자식 건너뛰기

1. 건너뛰기의 정의

FallbackWithMemory의 이전 실패 자식 건너뛰기(skipping previously failed children)란, current_child_idx에 기억된 인덱스 이전의 자식들이 다음 Tick에서 평가되지 않고 생략되는 동작이다. 이 자식들은 이전 Tick에서 FAILURE를 반환하여 대안 탐색이 다음 자식으로 진행된 것이므로, 재평가가 생략된다(Colledanchise & Ogren, 2018).

2. 건너뛰기의 동작 과정

2.1 기본 동작

children: [ActA, ActB, AsyncActC, ActD]

Tick 1:
  i=0: ActA → FAILURE        (평가됨)
  i=1: ActB → FAILURE        (평가됨)
  i=2: AsyncActC → RUNNING   (평가됨)
  current_child_idx ← 2
  → RUNNING

Tick 2: (인덱스 2부터 시작)
  i=0: ActA → (건너뜀)       ← 이전 FAILURE 건너뛰기
  i=1: ActB → (건너뜀)       ← 이전 FAILURE 건너뛰기
  i=2: AsyncActC → RUNNING   (평가됨)
  → RUNNING

Tick 2에서 ActA와 ActB는 current_child_idx(2) 이전에 위치하므로 평가가 생략된다.

2.2 건너뛰기 후 진행

RUNNING 자식이 FAILURE를 반환하면, 건너뛰어진 자식은 여전히 생략되고 후속 자식으로 진행한다.

Tick 3:
  i=0: ActA → (건너뜀)
  i=1: ActB → (건너뜀)
  i=2: AsyncActC → FAILURE   (대안 C 실패)
  i=3: ActD → SUCCESS        (대안 D 성공)
  current_child_idx ← 0
  → SUCCESS

ActA와 ActB는 건너뛰어진 채로 ActD가 성공하여 전체가 SUCCESS를 반환한다.

3. 건너뛰기의 효과

3.1 성능 향상

건너뛰기에 의해 RUNNING 상태가 지속되는 동안의 Tick당 노드 방문 수가 감소한다.

children: [C₁, C₂, C₃, C₄, AsyncAct]   (5개 자식)

ReactiveFallback (건너뛰기 없음):
  매 Tick: C₁, C₂, C₃, C₄, AsyncAct → 5개 방문

FallbackWithMemory (건너뛰기):
  첫 Tick: C₁, C₂, C₃, C₄, AsyncAct → 5개 방문
  이후 Tick: AsyncAct만 → 1개 방문

M Tick 동안 RUNNING이 지속되는 경우, 총 방문 수는:

변형총 노드 방문 수
ReactiveFallback5 \times M
FallbackWithMemory5 + (M - 1) \times 1 = M + 4

M = 100이면 ReactiveFallback은 500회, FallbackWithMemory는 104회로 약 4.8배의 차이가 발생한다.

3.2 부수 효과 방지

건너뛰어진 자식의 executeTick()이 호출되지 않으므로, 해당 자식의 부수 효과가 반복 발생하지 않는다.

<Fallback>
    <Action ID="LogAndTryMethodA"/>    <!-- 로그 기록 + 시도 A -->
    <Action ID="AsyncMethodB"/>         <!-- 비동기 시도 B -->
</Fallback>

LogAndTryMethodA가 FAILURE를 반환한 후, AsyncMethodB가 RUNNING 상태를 유지하는 동안 LogAndTryMethodA의 로그 기록이 매 Tick마다 반복되지 않는다.

4. 건너뛰기의 위험성

4.1 우선순위 높은 대안의 가용성 변화 미감지

건너뛰기의 가장 큰 위험은, 이전에 FAILURE였던 앞쪽 자식이 SUCCESS로 전환되더라도 감지하지 못한다는 점이다.

<Fallback>
    <Condition ID="IsPrimaryPathClear"/>   <!-- 주 경로 가용? -->
    <Action ID="UseDetourPath"/>            <!-- 우회 경로 사용 -->
</Fallback>
Tick 1: IsPrimaryPathClear→F, UseDetourPath→R (idx=1)
Tick 2: UseDetourPath→R          (주 경로가 정리되었지만 건너뜀)
Tick 3: UseDetourPath→R          (여전히 건너뜀)
Tick 4: UseDetourPath→S          (우회 완료)
        → SUCCESS

Tick 2에서 주 경로가 정리되어 IsPrimaryPathClear가 SUCCESS를 반환할 수 있었지만, 건너뛰기에 의해 확인되지 않았다. 로봇은 불필요하게 긴 우회 경로를 계속 사용한다.

4.2 안전 조건 미검사

안전 관련 조건이 Fallback의 앞쪽에 배치된 경우, 건너뛰기에 의해 안전 조건이 재검사되지 않을 수 있다.

<!-- 위험한 패턴 -->
<Fallback>
    <Condition ID="IsOperatingNormally"/>
    <Action ID="RecoveryProcedure"/>
</Fallback>

정상 상태가 아니어서 RecoveryProcedure가 RUNNING 상태에서 복구를 수행하는 동안, IsOperatingNormally가 건너뛰어진다. 만약 복구 중 정상 상태로 돌아왔다면, FallbackWithMemory에서는 이를 감지하지 못하고 불필요한 복구를 계속한다.

5. SequenceWithMemory의 건너뛰기와의 대칭성

특성SequenceWithMemoryFallbackWithMemory
건너뛰는 자식이전에 SUCCESS 반환한 자식이전에 FAILURE 반환한 자식
건너뛰기 이유이미 성공한 단계 반복 불필요이미 실패한 대안 재시도 불필요
미감지 위험SUCCESS→FAILURE 전환 미감지FAILURE→SUCCESS 전환 미감지
안전 위험완료된 전제조건의 무효화 미감지가용해진 우선 대안 미감지

6. 건너뛰기 위험의 완화 전략

6.1 전략 1: ReactiveFallback 사용

조건의 지속적 재검사가 필요한 경우 ReactiveFallback을 사용하여 매 Tick마다 처음부터 재평가한다.

6.2 전략 2: 안전 조건의 분리

안전 조건을 Fallback 내부가 아닌 상위 ReactiveSequence에 배치하여, 안전 감시가 건너뛰기에 의해 생략되지 않도록 한다.

<ReactiveSequence>
    <Condition ID="IsSystemSafe"/>    <!-- 건너뛰어지지 않음 -->
    <Fallback>
        <Action ID="PrimaryAction"/>
        <Action ID="RecoveryAction"/>
    </Fallback>
</ReactiveSequence>

6.3 전략 3: 대안 탐색과 조건 감시의 분리

FallbackWithMemory를 대안 탐색에만 사용하고, 조건 감시는 별도의 ReactiveSequence나 ReactiveFallback으로 구현한다. 이 분리를 통해 각 변형의 장점을 활용하면서 단점을 회피할 수 있다.


참고 문헌

  • 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/