diff --git a/app/src/main/java/com/example/medilightv2android/managers/AlignmentAdvisorV2.kt b/app/src/main/java/com/example/medilightv2android/managers/AlignmentAdvisorV2.kt index 29acd14..d05e2cd 100644 --- a/app/src/main/java/com/example/medilightv2android/managers/AlignmentAdvisorV2.kt +++ b/app/src/main/java/com/example/medilightv2android/managers/AlignmentAdvisorV2.kt @@ -152,6 +152,12 @@ class AlignGuide4Stage( private val latTol: Int = AlignmentConstants.LAT_TOL, private val initialTarget: Int = 10, private val stabilizeTarget: Int = 10, + /** LR_BALANCE 반전 deadband (samples). imbal 가 직전보다 이 값 초과 증가해야 + * '진짜 악화' 로 카운트. 노이즈/호흡 흡수. LAT_TOL(8) 의 약 1/3. */ + private val lrReverseDeadband: Int = 3, + /** LR_BALANCE 반전 요구 연속 commit 수. 2회 연속 악화여야 방향 반전. + * sliding window 잔여 효과 (사용자가 방향 바꿔도 평균은 직전 데이터 섞임) 흡수. */ + private val lrReverseConsecutive: Int = 2, ) { enum class Phase4 { INITIAL_ACCUM, VERTICAL_CLIMB, CH3_STABILIZE, LR_BALANCE } @@ -165,6 +171,7 @@ class AlignGuide4Stage( private var stabilizeCount = 0 private var lateralDir: AlignAction? = null private var lateralImbal: Int? = null + private var lateralWorseStreak = 0 // LR_BALANCE 연속 악화 카운터 (deadband 초과 증가 시 +1) fun reset() { phase = Phase4.INITIAL_ACCUM @@ -173,6 +180,7 @@ class AlignGuide4Stage( stabilizeCount = 0 lateralDir = null lateralImbal = null + lateralWorseStreak = 0 } fun step(s: AlignSummary): Out { @@ -230,7 +238,7 @@ class AlignGuide4Stage( return Out(recoveryDir, "$arrow $lostLabel 잃음 — 위치 회복", phase, 0) } - // full set 유지 중 — 좌우 균형 + // full set 유지 중 — 좌우 균형 (deadband + consecutive 악화 로 oscillation 차단) val u4 = s.u4 val u5 = s.u5 if (u4 != null && u5 != null) { @@ -240,10 +248,24 @@ class AlignGuide4Stage( "■ 정렬 완료 ($setLabel 유지, |Δ|=$imbal)", phase, 0) } if (lateralDir == null) { + // 첫 진입 — u4/u5 비교로 방향 추정 lateralDir = if (u4 < u5) AlignAction.MOVE_RIGHT else AlignAction.MOVE_LEFT - } else if (lateralImbal != null && imbal > lateralImbal!!) { - lateralDir = if (lateralDir == AlignAction.MOVE_LEFT) AlignAction.MOVE_RIGHT - else AlignAction.MOVE_LEFT + lateralWorseStreak = 0 + } else if (lateralImbal != null) { + // 악화 판정: imbal 가 직전보다 deadband 초과로 증가해야 streak++ + val significantlyWorse = imbal > lateralImbal!! + lrReverseDeadband + if (significantlyWorse) { + lateralWorseStreak++ + if (lateralWorseStreak >= lrReverseConsecutive) { + // 연속 N회 진짜 악화 → 방향 반전, streak reset + lateralDir = if (lateralDir == AlignAction.MOVE_LEFT) + AlignAction.MOVE_RIGHT else AlignAction.MOVE_LEFT + lateralWorseStreak = 0 + } + } else { + // 개선 또는 deadband 안의 변동 → 같은 방향 유지, streak reset + lateralWorseStreak = 0 + } } lateralImbal = imbal val arrow = if (lateralDir == AlignAction.MOVE_LEFT) "←" else "→"