53a07195f8
- Developer mode: 캐릭터 3탭 토글, dev-only settings/graphs/logs - SWEEP placement (CKLaw): BladderSphereSeek circle fit + RTracker + argmax + coronal lateral + R-peak gate GREEN lock - Gradient placement: weighted center gradient 기반 상하 판정, 방향 로직 수정 (ch0=위, ch3=아래), 초기 멘트 "치골 위에 배치" - Spot 측정: isSpotInProgress 플래그로 중간값 화면 깜빡임 제거 - 좌우 애매한 ↔ 멘트 제거 → 구체적 방향 또는 상태 안내 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
73 lines
2.3 KiB
Kotlin
73 lines
2.3 KiB
Kotlin
package com.example.medilightv2android.managers
|
|
|
|
import kotlin.math.abs
|
|
|
|
/**
|
|
* R-fit trend tracker — watches per-frame sphere-fit radius and classifies:
|
|
* RISING — sliding toward bladder centre (R increasing)
|
|
* PEAK — at / near session maximum R
|
|
* FALLING — sliding past centre (R decreasing)
|
|
* NOISE — fluctuating without clear trend
|
|
* IDLE — no valid fit (too few channels)
|
|
*
|
|
* Ported from CKLaw BladderSeekRTracker.kt.
|
|
*/
|
|
class BladderSeekRTracker(
|
|
private val historySize: Int = 8,
|
|
private val peakToleranceMm: Double = 1.5,
|
|
private val noiseDeltaMm: Double = 0.4,
|
|
) {
|
|
enum class Trend { RISING, PEAK, FALLING, NOISE, IDLE }
|
|
|
|
data class Status(
|
|
val currentR: Double?,
|
|
val bestR: Double,
|
|
val trend: Trend,
|
|
val improvementMm: Double,
|
|
val hintEn: String,
|
|
)
|
|
|
|
private val history = ArrayDeque<Double>()
|
|
var bestR: Double = 0.0
|
|
private set
|
|
|
|
fun reset() {
|
|
history.clear()
|
|
bestR = 0.0
|
|
}
|
|
|
|
fun submit(r: Double?): Status {
|
|
if (r == null || r.isNaN() || r <= 0.0) {
|
|
return Status(null, bestR, Trend.IDLE, 0.0,
|
|
"Not enough channels — check signal")
|
|
}
|
|
history.addLast(r)
|
|
while (history.size > historySize) history.removeFirst()
|
|
if (r > bestR) bestR = r
|
|
|
|
val trend = computeTrend(r)
|
|
val gap = r - bestR
|
|
val en = when (trend) {
|
|
Trend.RISING -> "Getting closer (R=${"%.0f".format(r)}mm)"
|
|
Trend.PEAK -> "At peak (R=${"%.0f".format(r)}mm)"
|
|
Trend.FALLING -> "Past peak — reverse (best ${"%.0f".format(bestR)}mm)"
|
|
Trend.NOISE -> "Searching (R=${"%.0f".format(r)}mm)"
|
|
Trend.IDLE -> ""
|
|
}
|
|
return Status(r, bestR, trend, gap, en)
|
|
}
|
|
|
|
private fun computeTrend(currentR: Double): Trend {
|
|
if (history.size < 3) return Trend.NOISE
|
|
if (bestR - currentR < peakToleranceMm) return Trend.PEAK
|
|
val prev = history.toList().dropLast(1).takeLast(3)
|
|
val prevMean = prev.average()
|
|
val delta = currentR - prevMean
|
|
return when {
|
|
abs(delta) < noiseDeltaMm -> Trend.NOISE
|
|
delta > 0 -> Trend.RISING
|
|
else -> Trend.FALLING
|
|
}
|
|
}
|
|
}
|