From 16c746f552e0ebf47af6c9b0c48c15b24cd05531 Mon Sep 17 00:00:00 2001 From: QIU Date: Mon, 17 Aug 2026 17:56:09 +0000 Subject: [PATCH] fix(mutual): advance the search when refine collapses a pass window findMutualPassesFallback skips a candidate with `if (refinedLos <= refinedAos) continue` but left searchStart untouched, so the next loop iteration called findNextMutualPass with the same start time and received the same pass again. Today that branch is theoretically unreachable: refineEdge's 70 s window always spans findNextMutualPass's 60 s sampling step, so the refined AOS/LOS can only move inward and never cross. But the invariant is fragile - any future change to the search step or the refine window (or a near-horizon pass whose crossings land at the window edges) makes the loop spin forever on one pass, freezing the query coroutine. Advance searchStart past the collapsed pass before skipping, breaking the cycle regardless of how the windows shift. Behaviour for the current reachable paths is unchanged. --- .../look4sat/feature/mutual/MutualViewModel.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt index 0f59b6ce..02054123 100644 --- a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt +++ b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt @@ -400,7 +400,15 @@ class MutualViewModel( val refinedAos = refineEdge(sat, posA, posB, aos, 1_000L, true) val refinedLos = refineEdge(sat, posA, posB, los, 1_000L, false) - if (refinedLos <= refinedAos) continue + if (refinedLos <= refinedAos) { + // Refine collapsed the window (e.g. a near-horizon pass whose + // AOS/LOS both sit at the edge of the 70 s refine window). + // Advance the search regardless: skipping without moving + // searchStart would make findNextMutualPass return the same + // pass forever once the refine windows can no longer span it. + searchStart = los + 120_000L + continue + } val mutualPass = sampleMutualPass(sat, posA, posB, refinedAos, refinedLos, minElevADeg, minElevBDeg, sampleInterval)