/* * Copyright (c) 2026 Medithings Co., Ltd. * Author: Charles KWON OhJun * Project: CharlesKWONsLaw — wall-detect live compare * * Stability check on V41 detection outputs. Complements SweepStabilizer: * the stabilizer guards the INPUT, this guards the OUTPUT. * * Per-channel rules (relative to the previous live frame): * • antIdx jump > maxIndexJump → unstable * • postIdx jump > maxIndexJump → unstable * • |chord change| / chord > maxChordRatio → unstable * * "unstable" is a flag for the diagnostic log, NOT a hard reject — V41's * own anatomical gate already drops genuinely-bad detections. This layer * just surfaces "this channel's reading just jumped, double-check". */ package com.example.medilightv2android.walldetect import com.example.medilightv2android.walldetect.dto.ChannelResult import kotlin.math.abs class DetectionSanity( private val maxIndexJump: Int = 8, private val maxChordRatio: Double = 0.20, private val channels: Int = 6, ) { private val lastAnt = IntArray(channels) { -1 } private val lastPost = IntArray(channels) { -1 } data class ChannelReport( val channel: Int, val antJump: Int?, val postJump: Int?, val chordChangeRatio: Double?, val unstable: Boolean, ) fun reset() { for (i in lastAnt.indices) { lastAnt[i] = -1; lastPost[i] = -1 } } fun check(perChannel: List): List { val reports = mutableListOf() for ((ch, cr) in perChannel.withIndex()) { val ant = cr.antIdx val post = cr.postIdx val prevA = lastAnt.getOrElse(ch) { -1 } val prevP = lastPost.getOrElse(ch) { -1 } var antJump: Int? = null var postJump: Int? = null var chordRatio: Double? = null var unstable = false if (ant != null && prevA >= 0) { val jump = abs(ant - prevA) antJump = jump if (jump > maxIndexJump) unstable = true } if (post != null && prevP >= 0) { val jump = abs(post - prevP) postJump = jump if (jump > maxIndexJump) unstable = true } if (ant != null && post != null && prevA >= 0 && prevP >= 0) { val curChord = (post - ant).toDouble() val prevChord = (prevP - prevA).toDouble() if (prevChord > 0.0) { val r = abs(curChord - prevChord) / prevChord chordRatio = r if (r > maxChordRatio) unstable = true } } reports += ChannelReport( channel = ch, antJump = antJump, postJump = postJump, chordChangeRatio = chordRatio, unstable = unstable, ) // Update state ONLY when we have a valid current detection, // otherwise comparison against -1 sentinel resumes after gap. if (ant != null) lastAnt[ch] = ant if (post != null) lastPost[ch] = post } return reports } }