fix(alignment): detach 지연 · 힌트 왔다갔다 fix · V1 sanity + Detachment OR 규칙

증상 (사용자 보고 2026-08-11 · demo-final):
  센서가 아예 분리된 상태인데 · alignment 화면에서 전벽/후벽이 하나씩
  잡혀 "위로/아래로" 힌트가 왔다갔다 · 몇 초 기다린 후에야 "분리됨"
  팝업이 뜸.

원인 (두 요인 조합):
  1) DetachmentDetection AND 규칙 (mean<30 AND std<30) 이 노이즈 상황에
     너무 관대. 공기 중 EMI/RF 노이즈로 std 가 30 이상인 경우 흔해
     detach 판정 지연.
  2) Method C detection 이 노이즈 peak 을 우연히 벽으로 오검출 · alignment
     computePlacementGuide 가 그 벽으로 힌트 계산 → 왔다갔다.
  3) V1 default 흐름에는 0/6 walls sanity check 없음 (V2 전용).

수정:
  1) DetachmentDetection: AND → OR
     mean·std 둘 중 하나만 30 미만이면 detach. 노이즈 std 높아도 mean
     낮으면 즉시 판정. 실제 방광 신호는 mean·std 둘 다 100+ 수준이라
     false positive 실질 낮음.

  2) PlacementGuideView V1 브랜치 sanity check 이식 (V2 line 924-949 로직
     동일).
     6채널 모두 wall 미검출 → 부착 확인 안내 · Start 후면 waitingForStart
     복귀 + 팝업 강제 (2026-08-05 V2 sanity 와 동일 UX).

Method C 노이즈 오검출 자체는 별건 · sanity + 강화된 Detachment 로 UX
관점 방어.
This commit is contained in:
2026-08-11 10:44:00 +09:00
parent 1aa15fad1a
commit b2c1ba2101
2 changed files with 29 additions and 2 deletions
@@ -79,8 +79,14 @@ object DetachmentDetection {
val meanAbs = meanAbsPerCh.average()
val stdVal = stdPerCh.average()
// "and" rule: 둘 다 threshold 미만이면 미부착
val isDetached = meanAbs < THR_MEAN && stdVal < THR_STD
// 2026-08-11: AND → OR 로 변경.
// 기존 AND: mean·std 둘 다 30 미만이어야 detach. 공기 중 EMI/RF 노이즈로
// std 가 30 이상인 경우가 흔해 detach 판정이 지연 · 사용자가 몇 초
// 기다린 후에야 팝업 뜨는 문제.
// 신규 OR: 둘 중 하나만 30 미만이어도 detach. 노이즈로 std 높아도 mean 낮으면
// 즉시 판정. 실제 신호 약할 때 false positive 위험 있으나 · 방광 신호는
// mean·std 둘 다 100+ 수준이라 실제로 오탐 가능성 낮음.
val isDetached = meanAbs < THR_MEAN || stdVal < THR_STD
return DetachmentResult(
isDetached = isDetached,
@@ -997,6 +997,27 @@ fun PlacementGuideView(appState: AppState) {
}
} // end of !waitingForStart V2 algorithm
} else {
// 2026-08-11: V1 sanity check (V2 와 동일 로직 이식 · line 924-949).
// 6채널 모두 wall 미검출 → 부착 이슈 · alignment advice 무시 · 즉시
// 부착 확인 안내. Start 후면 Start 화면 복귀 + 팝업 강제.
// DetachmentDetection AND→OR 완화 (2026-08-11) 로 대부분 잡히지만
// Method C 검출이 노이즈에 우연히 반응하는 경우 이 sanity 가 backstop.
val v1DetectedCount = detected.count { it }
if (v1DetectedCount == 0) {
directionHint = context.getString(R.string.placement_check_attachment)
directionIcon = ""
placementScore = 0
if (!waitingForStart) {
isContinuousMode = false
waitingForStart = true
prevDetached = true
showDetachAlert = true
bleManager.debugLogger.warn("ALIGN_V1 sanity: 0/6 walls · Start 후 부착 실패 · waitingForStart 복귀 + 팝업")
} else {
bleManager.debugLogger.info("ALIGN_V1 sanity: 0/6 walls · override to attachment check")
}
return@post
}
// Sphere-fit 계산 (SWEEP 모드에서 사용, 다른 모드에서는 무시됨)
val fitResult = com.medithings.vesiscan.managers.BladderSphereSeek
.fitSagittalCircle(allWalls, detected)