M2-09: Speed and distance from the track #52

Merged
wk merged 1 commit from m2-09-track-figures into main 2026-08-10 12:09:03 -04:00
Member

Closes #36.

Four numbers under the route on a memo's detail screen — distance, moving time, average speed and top speed — derived from the .gpx on every read and never stored.

What the filter does, and why

A phone standing still scatters its fixes over tens of metres. Summed point to point that scatter reads as walking, and over forty minutes it is a kilometre of route nobody walked, printed with exactly the confidence of a real one. So:

  • The device's own speed reading decides whether the phone was moving, and an anchored displacement decides how far it went. Doppler speed is a different measurement from the position, and it is near zero for a phone that is not moving even while the coordinates scatter. A stationary track measures as zero rather than approximately zero.
  • Nothing is measured across the end of a run — a segment break, a gap over thirty seconds, a stretch of fixes too imprecise to measure against, or a stretch where the device stopped reporting speed.
  • Top speed is the fastest speed held across two consecutive readings, and it is withheld when the positions contradict it. The GPX carries whether the device could measure a speed but not how well, so a single wild reading would otherwise be printed as the fastest somebody went.
  • Measuring nothing is three separate facts with three sentences — too coarse, too few, no speed — and none of them is a zero. A memo recorded at a desk really did go nowhere.
  • Coverage is two facts, not a choice. A route can overrun its memo's audio (an interruption) and be measured over only part of itself, and both are said.

AGENTS.md gains a Speed and distance section; the decision measuring a walk from a noisy track is in stash-docs.

Validated

  • make lint, make build, make test — 619 tests green.
  • Simulator, against synthetic tracks: a 1,746 m loop measured as 1.1 mi at 6.5 mph against a truth of 1.08 mi at 2.9 m/s; the half-length track showed the coverage sentence; coarse fixes and stripped speeds each showed their own sentence and no numbers.
  • Built, signed and installed on the phone.

The rule that withholds a top speed below the average came out of the simulator rather than a test — the first fixture's speeds disagreed with its own geometry and the screen printed a contradiction.

Not validated

  • No real walk. The issue's first acceptance criterion is a plausible distance against a known route, and no simulator can answer it. Same for a phone genuinely at rest, which is the criterion the whole filter exists for. Both are device checks in PLAN.md.
  • No test of the detail screen's wiringStashTests is logic only and a @State assignment inside a SwiftUI body has no seam to reach. Codex raised it and the rejection is recorded in SUMMARY.md.
  • speedAccuracy is not carried in the track file, so a grossly uncertain speed reading can only be outvoted by the reading beside it, not rejected. A follow-up issue is owed; it is a change to what M2-07 writes.

Codex

Four passes, artifacts in .agent/m2-09-track-figures/. Plan: 6 findings, all right. Code review: 6 findings, all right — the worst was minutes of missing speed readings being bridged and counted as covered. Test review: 13 findings, 12 accepted, all boundary gaps. Security: none. Every disposition is in SUMMARY.md.

Closes #36. Four numbers under the route on a memo's detail screen — distance, moving time, average speed and top speed — derived from the `.gpx` on every read and never stored. ## What the filter does, and why A phone standing still scatters its fixes over tens of metres. Summed point to point that scatter reads as walking, and over forty minutes it is a kilometre of route nobody walked, printed with exactly the confidence of a real one. So: - **The device's own speed reading decides whether the phone was moving**, and an anchored displacement decides how far it went. Doppler speed is a different measurement from the position, and it is near zero for a phone that is not moving even while the coordinates scatter. A stationary track measures as zero rather than approximately zero. - **Nothing is measured across the end of a run** — a segment break, a gap over thirty seconds, a stretch of fixes too imprecise to measure against, or a stretch where the device stopped reporting speed. - **Top speed is the fastest speed held across two consecutive readings**, and it is withheld when the positions contradict it. The GPX carries whether the device could measure a speed but not how well, so a single wild reading would otherwise be printed as the fastest somebody went. - **Measuring nothing is three separate facts** with three sentences — too coarse, too few, no speed — and none of them is a zero. A memo recorded at a desk really did go nowhere. - **Coverage is two facts, not a choice.** A route can overrun its memo's audio (an interruption) and be measured over only part of itself, and both are said. `AGENTS.md` gains a *Speed and distance* section; the decision **measuring a walk from a noisy track** is in `stash-docs`. ## Validated - `make lint`, `make build`, `make test` — 619 tests green. - Simulator, against synthetic tracks: a 1,746 m loop measured as `1.1 mi` at `6.5 mph` against a truth of 1.08 mi at 2.9 m/s; the half-length track showed the coverage sentence; coarse fixes and stripped speeds each showed their own sentence and no numbers. - Built, signed and installed on the phone. The rule that withholds a top speed below the average came out of the simulator rather than a test — the first fixture's speeds disagreed with its own geometry and the screen printed a contradiction. ## Not validated - **No real walk.** The issue's first acceptance criterion is a plausible distance against a known route, and no simulator can answer it. Same for a phone genuinely at rest, which is the criterion the whole filter exists for. Both are device checks in `PLAN.md`. - **No test of the detail screen's wiring** — `StashTests` is logic only and a `@State` assignment inside a SwiftUI body has no seam to reach. Codex raised it and the rejection is recorded in `SUMMARY.md`. - **`speedAccuracy` is not carried in the track file**, so a grossly uncertain speed reading can only be outvoted by the reading beside it, not rejected. A follow-up issue is owed; it is a change to what M2-07 writes. ## Codex Four passes, artifacts in `.agent/m2-09-track-figures/`. Plan: 6 findings, all right. Code review: 6 findings, all right — the worst was minutes of missing speed readings being bridged and counted as covered. Test review: 13 findings, 12 accepted, all boundary gaps. Security: none. Every disposition is in `SUMMARY.md`.
Four numbers under the route on a memo's detail screen — distance, moving
time, average speed and top speed — derived from the .gpx on every read and
never stored.

The substance is refusing to print a number the track cannot support. A
phone standing still scatters its fixes over tens of metres, and summing
point to point reads that scatter as a kilometre of walking. So the device's
own speed reading decides whether the phone was moving and an anchored
displacement decides how far it went; nothing is measured across a segment
break, a gap, a stretch too imprecise to measure against, or a stretch where
the device stopped reporting speed; and measuring nothing is three separate
facts with three sentences rather than a zero.

Issue: #36
wk merged commit f0f82000fc into main 2026-08-10 12:09:03 -04:00
wk deleted branch m2-09-track-figures 2026-08-10 12:09:03 -04:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Stash/stash-ios!52
No description provided.