Chaque pause effaçait jusqu'à 24 points de trace déjà parcourus

setPaused vidait `pendingRoute` pour « ne pas tracer la pause ». Mais ce tampon
ne contient que des points DÉJÀ acceptés par le filtre et DÉJÀ comptés dans
`distanceMeters` : ils attendent seulement leur écriture, qui ne part qu'au
seuil de routeFlushThreshold (25). À 1 Hz, c'est une douzaine de points en
moyenne, jusqu'à 24.

L'écart était donc visible : la distance affichée les incluait, la
HKWorkoutRoute non. Six arrêts sur une sortie VTTAE — feux, photos, ravito — et
jusqu'à 144 points manquaient à la trace, avec un D+ recalculé depuis elle qui
divergeait d'autant de celui affiché pendant la séance. Aucun log ne
mentionnait l'effacement.

Ils sont désormais écrits, via le même `Task { await flushPendingRoute() }` que
le flush au seuil. Le raisonnement du commentaire d'origine visait le point qui
SUIT la reprise, à des centaines de mètres de là — mais celui-là n'est pas
encore dans le tampon quand la pause commence, et c'est `implausibleSpeed` qui
l'écarte. Nettoyer la pause est l'affaire du filtre, pas du tampon d'écriture.

Pas de course possible : `isPaused` est posé avant, et `ingest` sort dès qu'il
est vrai — aucun point neuf ne peut s'ajouter pendant que le flush s'exécute.

Relevé § 2.5 de docs/AUDIT-2026-09-22.md, seul constat de la section vérifié
ligne à ligne avant correction. 88 tests Linux verts (RouteFilter compris), mais
ils ne couvrent pas ce fichier : CoreLocation n'existe pas sur Linux.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-09-22 09:20:30 +00:00
parent a5cbb70210
commit 2c9af6c684

View File

@@ -142,9 +142,21 @@ final class LocationTracker: NSObject, ObservableObject {
guard isTracking else { return } guard isTracking else { return }
isPaused = paused isPaused = paused
if paused { if paused {
// Le point qui suivra la reprise serait à des centaines de mètres : // ⚠️ Ici se jouait une perte silencieuse. `pendingRoute` ne contient
// on repart d'une origine neuve pour ne pas tracer la pause. // que des points DÉJÀ acceptés par le filtre et DÉJÀ comptés dans
pendingRoute.removeAll() // `distanceMeters` — ils attendent seulement leur écriture, qui ne
// part qu'au seuil de `routeFlushThreshold` (25). À 1 Hz, c'est une
// douzaine de points en moyenne, jusqu'à 24. Les jeter creusait un
// écart visible entre la distance affichée et la trace enregistrée :
// six arrêts sur une sortie, et jusqu'à 144 points manquaient à la
// HKWorkoutRoute, avec un D+ recalculé qui divergeait d'autant.
//
// On les écrit donc au lieu de les perdre. Le raisonnement du
// commentaire d'origine visait le point qui SUIT la reprise, à des
// centaines de mètres de là — mais celui-là n'est pas encore dans
// le tampon, et c'est `implausibleSpeed` qui l'écarte. Nettoyer la
// pause est l'affaire du filtre, pas du tampon d'écriture.
Task { await flushPendingRoute() }
} }
} }