fix(alignment): CENTER_OPTIMIZE 방향 오류 — CH0 놓칠 때 MOVE_UP 은 반대

CENTER_OPTIMIZE 단계에서 CH0 만 놓친 상태로 "↑ 조금 더 위로" 안내가 계속 나옴.
Probe 를 더 올릴수록 CH0 beam 이 방광 dome 위로 더 벗어남 → 무한 상승 loop.

CH3 를 잃었을 때도 마찬가지: MOVE_UP 으로 회복 시도하지만 CH3 잃은 원인이 이미
probe 가 너무 올라간 것이라 상황 악화.

V1 preset 기하:
- CH0 +6.89° (위로 tilt, 상단 channel) — 방광 dome 담당
- CH3 −13.66° (아래로 tilt, 하단 channel) — 방광 inferior edge 담당

Probe 위치 vs 미검출 채널:
- probe 너무 낮음 → CH3 miss, CH0 catch → MOVE_UP 정답
- probe 너무 높음 → CH0 miss, CH3 catch → MOVE_DOWN 정답

기존 코드는 CH0~2 미검출을 모두 "너무 낮음" 으로 가정 → MOVE_UP.

CENTER_OPTIMIZE 미검출 처리 방향 결정 로직:
- CH3 잃음 (CENTER_OPTIMIZE 진입 후) = probe 가 위로 올라감 → MOVE_DOWN 으로 회복
- 상단만 미검출 (CH0 or CH0+CH1) = probe too high → MOVE_DOWN
- 하단 포함 미검출 (CH2/CH3 포함) = probe too low → MOVE_UP (기존 default)

- 정상 서있음 자세로 배꼽 근처에서 시작 → CENTER_OPTIMIZE 진입 시 "↓ 조금 아래로" 안내
- 치골 근처에서 시작 → 이전과 동일하게 "↑ 조금 더 위로"
- CH3 검출 후 위로 살짝 지나쳤을 때 "↓ ch3 재확인" 나오는지

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 15:10:30 +09:00
parent 15db5ec8bf
commit 45f5efc278
@@ -306,21 +306,32 @@ class AlignGuide4Stage(
if (phase == Phase4.CENTER_OPTIMIZE) {
val center4Detected = (0..3).all { it < s.detected.size && s.detected[it] }
if (!s.ch3) {
// CH3 잃으면 CH3_STABILIZE 로 복귀
// 2026-07-06 fix: CH3 를 CENTER_OPTIMIZE 진입 후 잃음 = probe 가 위로 올라가
// CH3 beam 이 방광 inferior edge 를 넘어감 → 방광이 probe 아래로 이동.
// → MOVE_DOWN 으로 CH3 회복. 이전 MOVE_UP 은 잃음 원인과 반대라 무한 상승.
phase = Phase4.CH3_STABILIZE
stabilizeCount = 0
centerOptimizeStreak = 0
return Out(AlignAction.MOVE_UP, "↑ ch3 재확인 필요",
phase, 0, msgKey = R.string.align_recover_up)
return Out(AlignAction.MOVE_DOWN, "↓ ch3 재확인 필요",
phase, 0, msgKey = R.string.align_recover_down_short)
}
if (!center4Detected) {
// CH0~2 중 일부 미검출 → probe 를 위로 올려 CH0 잡기
// 2026-07-06 fix: 미검출 채널 위치로 probe 위치 판단.
// CH0 (+6.89° 위로 tilt, 상단 channel) 만 놓치고 CH3 검출 = probe 가 방광
// dome 위에 있어 CH0 beam 이 위로 빠짐 → MOVE_DOWN 으로 방광 중심 재정렬.
// CH1/CH2 만 놓치고 CH0+CH3 검출 = 드물지만 부분적 dropout, default MOVE_UP.
// 상단 (CH0 or CH0+CH1) 만 놓침 = probe too high → MOVE_DOWN.
centerOptimizeStreak = 0
val missing = (0..3).filter { !s.detected[it] }
val missingLabel = missing.joinToString(",") { "ch$it" }
return Out(AlignAction.MOVE_UP,
"↑ 조금 더 위로 ($missingLabel 미검출)", phase, 0,
msgKey = R.string.align_move_up)
val topOnly = missing.isNotEmpty() && missing.all { it <= 1 } // {0} or {1} or {0,1}
val (direction, arrow, msgKey) = if (topOnly)
Triple(AlignAction.MOVE_DOWN, "↓", R.string.align_recover_down_short)
else
Triple(AlignAction.MOVE_UP, "↑", R.string.align_move_up)
return Out(direction,
"$arrow 조금 ${if (topOnly) "아래" else "더 위"}로 ($missingLabel 미검출)",
phase, 0, msgKey = msgKey)
}
// 4채널 모두 검출됨 → chord CV 계산 (summarize 에서 angle-corrected mm 값 저장됨)
val chords = DoubleArray(4) { ch -> s.chords[ch] ?: 0.0 }