# Predict() 함수 분석 보고서
## 1. 개요 (Overview)
`AGVNavigationCore.Models.VirtualAGV` 클래스의 `Predict()` 함수는 AGV의 현재 상태(위치, RFID 감지, 경로 등)를 기반으로 **다음 행동(이동, 정지, 속도 등)**을 결정하는 핵심 의사결정 함수입니다. 시스템은 이 함수를 주기적으로 호출하여 AGV가 경로를 이탈하지 않고 목적지까지 안전하게 이동하도록 제어합니다.
## 2. 논리 흐름 (Logic Flow)
`Predict()` 함수의 결정 로직은 다음 순서로 진행됩니다.
```mermaid
graph TD
A[Start Predict] --> B{위치 확정 여부
(RFID 2개 이상)}
B -- No --> C[UnknownPosition
(전진/저속/탐색)]
B -- Yes --> D{현재 경로(Path) 존재?}
D -- No --> E[NoPath
(정지)]
D -- Yes --> F{목적지 도착 임박?
(이전 노드 모두 통과)}
F -- Yes --> G{현재 노드 == 최종 노드?}
G -- Yes --> H{최종 노드 완료(IsPass)?}
H -- Yes --> I[Complete
(정지/완료)]
H -- No --> J[MarkStop
(정지/마크센서대기)]
G -- No --> K[Logic Continue]
F -- No --> K
K --> L{경로 이탈 확인
(현재노드가 경로에 존재?)}
L -- No --> M[PathOut
(정지/재탐색요청)]
L -- Yes --> N[GetCommandFromPath
(다음 노드 이동 명령)]
```
### 단계별 상세 분석
1. **위치 미확정 (Position Check)**
* **조건**: `!_isPositionConfirmed` (감지된 RFID 개수 < 2)
* **행동**: `UnknownPosition` 리턴.
* **명령**: `Forward` (전진), `SpeedLevel.L` (저속).
* **목적**: RFID를 추가로 찾아 위치를 확정하기 위해 천천히 이동.
2. **경로 없음 (Path Check)**
* **조건**: `_currentPath`가 null이거나 비어있음.
* **행동**: `NoPath` 리턴.
* **명령**: `Stop`.
3. **목적지 도착 확인 (Goal Check)**
* **조건**: 경로상의 마지막 노드(`lastNode`) 이전의 모든 노드가 `IsPass == true` 상태인지 확인.
* **분기**:
* **최종 완료**: `_currentNode`가 마지막 노드이고, `IsPass == true`로 설정됨 -> `Complete` 리턴.
* **도착 직전(MarkStop)**: `_currentNode`가 마지막 노드이지만, 아직 `IsPass == false`임 -> `MarkStop` 리턴. 이는 물리적인 정지(Mark Sensor 감지)를 기다리는 상태임.
4. **경로 이탈 확인 (Deviation Check)**
* **로직**: 현재 경로(`DetailedPath`) 중에서 `_currentNode`와 ID가 일치하고 아직 지나가지 않은(`IsPass == false`) 노드를 찾음.
* **결과**: 찾지 못하면 `PathOut` 리턴 (경로 이탈로 간주하여 정지).
5. **이동 명령 생성 (Command Generation)**
* **함수**: `GetCommandFromPath(CurrentNodeId)` 호출.
* **로직**: 현재 노드에 정의된 이동 지침(모터 방향, 마그넷 분기, 속도 등)을 가져옴.
* **명령**: 해당 지침대로 `Normal` 명령 리턴.
---
## 3. 주요 메커니즘 및 의존성
### 3.1. 자동 패스 (Auto-Pass) 메커니즘
AGV가 중간 노드의 RFID를 놓치고 다음 노드로 건너뛰었을 때, 로직이 깨지지 않게 하는 중요한 안전장치입니다.
* **위치**: `SetPosition()` 함수 내 (Line 583~593).
* **동작**: AGV가 새로운 노드(B)에 도착했다고 보고되면, 경로상에서 B보다 앞선 모든 노드(A)의 `IsPass` 속성을 강제로 `true`로 변경합니다.
* **효과**: `Predict`의 "목적지 도착 확인" 로직(`All Previous Passed`)이 올바르게 작동하도록 보장합니다.
### 3.2. MarkStop 및 완료 처리
* AGV가 마지막 노드 RFID를 읽으면 `Predict`는 `MarkStop`을 리턴합니다 (아직 `IsPass`는 false).
* `_Util.cs` 등 제어부는 이를 보고 감속/정지 준비를 합니다.
* AGV가 물리적으로 마크 센서에 정지하면 `_AGV.cs`에서 `SetCurrentNodeMarkStop()`을 호출합니다.
* 이때 비로소 마지막 노드의 `IsPass`가 `true`가 됩니다.
* 다음 `Predict` 호출 시 `Complete`가 리턴되어 시퀀스가 종료됩니다.
---
## 4. 식별된 문제점 및 제안 사항 (Issues & Suggestions)
### 4.1. "Skipped Node" 시나리오의 잠재적 위험
* **현상**: `SetPosition`은 RFID 수신 시 호출되어 이전 노드들을 Auto-Pass 처리합니다. 하지만 만약 AGV가 경로를 이탈하여 엉뚱한 노드로 갔는데, 우연히 그 노드가 경로의 훨씬 뒤쪽 노드라면?
* **위험**: 중간의 모든 공정을 건너뛰고 바로 목적지 근처로 인식할 수 있습니다.
* **제안**: `SetPosition`에서 건너뛰는 노드의 개수가 너무 많거나(예: 3개 이상), 거리가 물리적으로 불가능할 정도로 멀다면 에러(`PathJump`)를 발생시키는 안전장치 추가 고려.
### 4.2. 하드코딩된 상수 (Magic Numbers)
* **코드**: `DetectedRfidCount >= 2`, `BatteryLevel < 20.0f` 등.
* **제안**: 이러한 값들을 `const` 또는 설정 파일(`PUB.setting`)로 관리하여 유지보수성을 높여야 합니다.
### 4.3. `GetCommandFromPath`의 Null 처리 불일치
* **현상**: `Predict`에서는 `PathOut`을 먼저 체크하지만, 기저에 있는 `GetCommandFromPath`에도 중복된 Null 체크/`NoTarget` 리턴 로직이 있음.
* **제안**: 로직의 일관성을 위해 검증 로직을 통일하거나, `Predict`에서 확실히 걸러낸 후 `GetCommandFromPath`는 데이터를 가져오는 역할만 하도록 단순화.
### 4.4. `Predict`의 "현재 노드 기준" 명령 생성
* **현상**: `GetCommandFromPath`는 `CurrentNodeId`를 인자로 받습니다. 즉, "현재 노드에서 수행해야 할 행동"을 반환합니다.
* **의문**: 만약 AGV가 노드와 노드 사이(Edge)에 있을 때, `CurrentNodeId`는 "지난 노드"입니다. 이때 "지난 노드"의 명령을 계속 수행하는 것이 맞는지(일반적으로는 맞음, Go Forward 등) 확인 필요.
* **확인**: 현재 로직은 맞습니다. 다음 RFID를 만날 때까지 이전 명령을 유지합니다.
---
## 5. 결론 (Conclusion)
`Predict()` 함수는 `SetPosition`(위치 업데이트 및 Auto-Pass) 및 `MarkStop` 처리 로직과 유기적으로 결합되어 있어 단독으로만 보면 이해하기 어려울 수 있습니다. 현재 로직은 **RFID 누락(Skip)에 대한 복구 능력**을 갖추고 있으며, **물리적 정지(MarkStop)와 논리적 완료(Complete)를 구분**하여 정밀한 제어를 가능하게 설계되어 있습니다.
다만, 매직 넘버 사용과 일부 중복된 검증 로직은 리팩토링을 통해 개선할 여지가 있습니다.