fix(todo): only a full wait counts towards lying still (ADR 0033, D32)

Asking for fixes at a new interval brings one at once, so each doubling
brought the next: on the emulator's walk the wait went from 30 s to
120 s within 40 s, and would reach 5 min within a couple of minutes,
leaving someone who sets off again unnoticed for that long. A still fix
now counts only once at least three quarters of the wait asked for
has passed; a move still starts over at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-05 21:04:07 +01:00
commit 62f5dbb2d6
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
3 changed files with 60 additions and 2 deletions

View file

@ -58,6 +58,20 @@ object LocationDwell {
/** Whether the phone, [moved] metres since the last fix, lies still. */
fun still(moved: Float): Boolean = moved < STILL_METRES
/**
* How many fixes the phone has lain still, [before] this one, [moved] metres
* from the last and [waited] milliseconds after it, [asked] being the wait
* asked for. A move starts over. A fix sooner than asked says nothing (a
* quarter short still counts, as Android may hand one over a little early):
* asking anew brings one at once, and counting those reached the longest
* wait within a minute or two.
*/
fun stillFixes(before: Int, moved: Float, waited: Long, asked: Long): Int = when {
!still(moved) -> 0
waited * 4 >= asked * 3 -> before + 1
else -> before
}
/**
* How long until the next fix: as long as reaching the nearest circle,
* [metres] away, takes at the fastest, doubled for each of [stillFixes]

View file

@ -110,7 +110,16 @@ class LocationWatchService : Service(), LocationListener {
val accuracy = if (fix.hasAccuracy()) fix.accuracy else null
LocationReminders.log(this, "fix, ${accuracy ?: "?"} m")
if (!LocationDwell.usable(accuracy)) return
stillFixes = if (before != null && LocationDwell.still(before.distanceTo(fix))) stillFixes + 1 else 0
stillFixes = if (before == null) {
0
} else {
LocationDwell.stillFixes(
before = stillFixes,
moved = before.distanceTo(fix),
waited = (fix.elapsedRealtimeNanos - before.elapsedRealtimeNanos) / 1_000_000,
asked = requested ?: 0,
)
}
last = fix
LocationReminders.seen(this, fix.latitude, fix.longitude)
ask(next(fix))

View file

@ -101,4 +101,37 @@ class LocationDwellTest {
assertTrue(LocationDwell.still(moved = 49f))
assertFalse(LocationDwell.still(moved = 50f))
}
@Test
fun `a still fix after the wait asked for counts`() {
assertEquals(3, LocationDwell.stillFixes(before = 2, moved = 10f, waited = 60_000L, asked = 60_000L))
assertEquals(3, LocationDwell.stillFixes(before = 2, moved = 10f, waited = 45_000L, asked = 60_000L))
}
@Test
fun `a fix sooner than asked for says nothing of lying still`() {
assertEquals(2, LocationDwell.stillFixes(before = 2, moved = 10f, waited = 5_000L, asked = 60_000L))
assertEquals(2, LocationDwell.stillFixes(before = 2, moved = 10f, waited = 44_000L, asked = 60_000L))
}
@Test
fun `a move starts over, however soon it came`() {
assertEquals(0, LocationDwell.stillFixes(before = 3, moved = 50f, waited = 5_000L, asked = 240_000L))
assertEquals(0, LocationDwell.stillFixes(before = 3, moved = 900f, waited = 240_000L, asked = 240_000L))
}
@Test
fun `a phone lying still takes minutes to reach the longest wait`() {
// Each wait is asked anew, which brings a fix at once: the fix in the
// emulator's walk that doubled it within seconds, 5 Nov 2026.
var fixes = 0
var elapsed = 0L
while (LocationDwell.interval(metres = 1_000f, stillFixes = fixes) < LocationDwell.LATEST_MILLIS) {
val asked = LocationDwell.interval(metres = 1_000f, stillFixes = fixes)
fixes = LocationDwell.stillFixes(fixes, moved = 0f, waited = 5_000L, asked = asked)
fixes = LocationDwell.stillFixes(fixes, moved = 0f, waited = asked, asked = asked)
elapsed += 5_000L + asked
}
assertTrue(elapsed >= 7 * 60_000L)
}
}