Drop GPS dropout bridging
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run

Revert 6777656; the app again writes zero distance across a gap
over 30 s. Also drop the importer-test skip for its fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYwyP67ppvkeie3X1F
This commit is contained in:
Krzysztof Rudnicki 2026-11-11 12:00:58 +01:00
parent eb4ce6fd68
commit 71cb5eecdb
10 changed files with 29 additions and 1070 deletions

View File

@ -1,811 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">
<Activities>
<Activity Sport="Running">
<Id>2026-11-09T17:00:00Z</Id>
<Lap StartTime="2026-11-09T17:00:00Z">
<TotalTimeSeconds>1800</TotalTimeSeconds>
<DistanceMeters>5400.009</DistanceMeters>
<Calories>0</Calories>
<Intensity>Active</Intensity>
<TriggerMethod>Manual</TriggerMethod>
<Track>
<Trackpoint>
<Time>2026-11-09T17:00:00Z</Time>
<Position>
<LatitudeDegrees>48.8584000</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>0.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:00:20Z</Time>
<Position>
<LatitudeDegrees>48.8589396</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>60.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:00:40Z</Time>
<Position>
<LatitudeDegrees>48.8594792</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>120.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:01:00Z</Time>
<Position>
<LatitudeDegrees>48.8600188</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>180.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:01:20Z</Time>
<Position>
<LatitudeDegrees>48.8605584</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>240.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:01:40Z</Time>
<Position>
<LatitudeDegrees>48.8610980</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>300.000</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:02:00Z</Time>
<Position>
<LatitudeDegrees>48.8616376</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>360.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:02:20Z</Time>
<Position>
<LatitudeDegrees>48.8621772</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>420.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:02:40Z</Time>
<Position>
<LatitudeDegrees>48.8627167</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>480.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:03:00Z</Time>
<Position>
<LatitudeDegrees>48.8632563</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>540.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:03:20Z</Time>
<Position>
<LatitudeDegrees>48.8637959</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>600.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:03:40Z</Time>
<Position>
<LatitudeDegrees>48.8643355</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>660.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:04:00Z</Time>
<Position>
<LatitudeDegrees>48.8648751</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>720.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:04:20Z</Time>
<Position>
<LatitudeDegrees>48.8654147</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>780.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:04:40Z</Time>
<Position>
<LatitudeDegrees>48.8659543</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>840.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:05:00Z</Time>
<Position>
<LatitudeDegrees>48.8664939</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>900.001</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:05:20Z</Time>
<Position>
<LatitudeDegrees>48.8670335</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>960.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:05:40Z</Time>
<Position>
<LatitudeDegrees>48.8675731</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1020.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:06:00Z</Time>
<Position>
<LatitudeDegrees>48.8681127</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1080.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:06:20Z</Time>
<Position>
<LatitudeDegrees>48.8686523</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1140.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:06:40Z</Time>
<Position>
<LatitudeDegrees>48.8691919</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1200.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:07:00Z</Time>
<Position>
<LatitudeDegrees>48.8697315</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1260.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:07:20Z</Time>
<Position>
<LatitudeDegrees>48.8702710</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1320.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:07:40Z</Time>
<Position>
<LatitudeDegrees>48.8708106</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1380.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:08:00Z</Time>
<Position>
<LatitudeDegrees>48.8713502</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1440.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:08:20Z</Time>
<Position>
<LatitudeDegrees>48.8718898</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1500.002</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:08:40Z</Time>
<Position>
<LatitudeDegrees>48.8724294</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1560.003</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:09:00Z</Time>
<Position>
<LatitudeDegrees>48.8729690</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1620.003</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:09:20Z</Time>
<Position>
<LatitudeDegrees>48.8735086</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1680.003</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:09:40Z</Time>
<Position>
<LatitudeDegrees>48.8740482</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1740.003</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:10:00Z</Time>
<Position>
<LatitudeDegrees>48.8745878</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>1800.003</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:12:00Z</Time>
<Position>
<LatitudeDegrees>48.8778254</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2160.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:12:20Z</Time>
<Position>
<LatitudeDegrees>48.8783649</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2220.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:12:40Z</Time>
<Position>
<LatitudeDegrees>48.8789045</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2280.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:13:00Z</Time>
<Position>
<LatitudeDegrees>48.8794441</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2340.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:13:20Z</Time>
<Position>
<LatitudeDegrees>48.8799837</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2400.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:13:40Z</Time>
<Position>
<LatitudeDegrees>48.8805233</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2460.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:14:00Z</Time>
<Position>
<LatitudeDegrees>48.8810629</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2520.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:14:20Z</Time>
<Position>
<LatitudeDegrees>48.8816025</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2580.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:14:40Z</Time>
<Position>
<LatitudeDegrees>48.8821421</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2640.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:15:00Z</Time>
<Position>
<LatitudeDegrees>48.8826817</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2700.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:15:20Z</Time>
<Position>
<LatitudeDegrees>48.8832213</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2760.004</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:15:40Z</Time>
<Position>
<LatitudeDegrees>48.8837609</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2820.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:16:00Z</Time>
<Position>
<LatitudeDegrees>48.8843005</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2880.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:16:20Z</Time>
<Position>
<LatitudeDegrees>48.8848401</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>2940.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:16:40Z</Time>
<Position>
<LatitudeDegrees>48.8853797</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3000.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:17:00Z</Time>
<Position>
<LatitudeDegrees>48.8859192</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3060.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:17:20Z</Time>
<Position>
<LatitudeDegrees>48.8864588</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3120.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:17:40Z</Time>
<Position>
<LatitudeDegrees>48.8869984</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3180.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:18:00Z</Time>
<Position>
<LatitudeDegrees>48.8875380</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3240.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:18:20Z</Time>
<Position>
<LatitudeDegrees>48.8880776</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3300.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:18:40Z</Time>
<Position>
<LatitudeDegrees>48.8886172</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3360.005</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:19:00Z</Time>
<Position>
<LatitudeDegrees>48.8891568</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3420.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:19:20Z</Time>
<Position>
<LatitudeDegrees>48.8896964</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3480.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:19:40Z</Time>
<Position>
<LatitudeDegrees>48.8902360</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3540.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:20:00Z</Time>
<Position>
<LatitudeDegrees>48.8907756</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3600.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:20:20Z</Time>
<Position>
<LatitudeDegrees>48.8913152</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3660.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:20:40Z</Time>
<Position>
<LatitudeDegrees>48.8918548</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3720.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:21:00Z</Time>
<Position>
<LatitudeDegrees>48.8923944</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3780.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:21:20Z</Time>
<Position>
<LatitudeDegrees>48.8929340</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3840.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:21:40Z</Time>
<Position>
<LatitudeDegrees>48.8934736</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3900.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:22:00Z</Time>
<Position>
<LatitudeDegrees>48.8940131</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>3960.006</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:22:20Z</Time>
<Position>
<LatitudeDegrees>48.8945527</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4020.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:22:40Z</Time>
<Position>
<LatitudeDegrees>48.8950923</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4080.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:23:00Z</Time>
<Position>
<LatitudeDegrees>48.8956319</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4140.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:23:20Z</Time>
<Position>
<LatitudeDegrees>48.8961715</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4200.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:23:40Z</Time>
<Position>
<LatitudeDegrees>48.8967111</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4260.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:24:00Z</Time>
<Position>
<LatitudeDegrees>48.8972507</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4320.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:24:20Z</Time>
<Position>
<LatitudeDegrees>48.8977903</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4380.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:24:40Z</Time>
<Position>
<LatitudeDegrees>48.8983299</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4440.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:25:00Z</Time>
<Position>
<LatitudeDegrees>48.8988695</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4500.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:25:20Z</Time>
<Position>
<LatitudeDegrees>48.8994091</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4560.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:25:40Z</Time>
<Position>
<LatitudeDegrees>48.8999487</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4620.007</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:26:00Z</Time>
<Position>
<LatitudeDegrees>48.9004883</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4680.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:26:20Z</Time>
<Position>
<LatitudeDegrees>48.9010279</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4740.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:26:40Z</Time>
<Position>
<LatitudeDegrees>48.9015674</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4800.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:27:00Z</Time>
<Position>
<LatitudeDegrees>48.9021070</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4860.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:27:20Z</Time>
<Position>
<LatitudeDegrees>48.9026466</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4920.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:27:40Z</Time>
<Position>
<LatitudeDegrees>48.9031862</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>4980.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:28:00Z</Time>
<Position>
<LatitudeDegrees>48.9037258</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5040.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:28:20Z</Time>
<Position>
<LatitudeDegrees>48.9042654</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5100.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:28:40Z</Time>
<Position>
<LatitudeDegrees>48.9048050</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5160.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:29:00Z</Time>
<Position>
<LatitudeDegrees>48.9053446</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5220.008</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:29:20Z</Time>
<Position>
<LatitudeDegrees>48.9058842</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5280.009</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:29:40Z</Time>
<Position>
<LatitudeDegrees>48.9064238</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5340.009</DistanceMeters>
</Trackpoint>
<Trackpoint>
<Time>2026-11-09T17:30:00Z</Time>
<Position>
<LatitudeDegrees>48.9069634</LatitudeDegrees>
<LongitudeDegrees>2.2945000</LongitudeDegrees>
</Position>
<AltitudeMeters>30.0</AltitudeMeters>
<DistanceMeters>5400.009</DistanceMeters>
</Trackpoint>
</Track>
</Lap>
<Creator xsi:type="Device_t">
<Name>kuhy workout_app</Name>
<UnitId>0</UnitId>
<ProductID>0</ProductID>
<Version>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
</Version>
</Creator>
</Activity>
</Activities>
<Author xsi:type="Application_t">
<Name>kuhy workout_app</Name>
<Build>
<Version>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
</Version>
</Build>
<LangID>en</LangID>
<PartNumber>000-00000-00</PartNumber>
</Author>
</TrainingCenterDatabase>

View File

@ -152,36 +152,6 @@ class TestGoldenWalk:
assert by_name["walking"] is True
class TestGoldenDropout:
"""A run whose GPS dropped out for 2 minutes, written by the app's filter.
The app bridges a dropout it did not pause the way RunnerUp's export does
(straight-line metres across the gap), so the PC's dropout rule keeps both
the distance and the moving time. Pinned by the Dart twin
``test/services/run_dropout_tcx_test.dart``.
"""
NAME = "RunnerUp_2026-11-09-18-00-00_Running.tcx"
def test_pc_credits_the_bridged_dropouts_metres_and_moving_time(
self, inbox: Path
) -> None:
"""30 min at 3 m/s with seconds 600-720 unseen: 5400 m and 1800 s."""
_drop(inbox, self.NAME)
data = Verifier()._pull_and_parse_tcx(str(inbox / self.NAME))
assert data is not None
# Zeroed across the gap (the old app) this was 5040 m and 1680 s.
assert data["track_distance_m"] == pytest.approx(5400, abs=1)
assert data["moving_seconds"] == pytest.approx(1800)
def test_the_run_is_verified_on_its_day(self, inbox: Path) -> None:
"""The whole file passes the real file-based verification."""
_drop(inbox, self.NAME)
result = _verify(inbox, "2026-11-09")
assert result is not None
assert result[0] == "verified"
class TestRunnerUpSample:
"""The trimmed real RunnerUp export parses to the numbers Dart's decoder sees."""

View File

@ -64,12 +64,11 @@ enum FixVerdict {
/// Accumulates accepted points and their cumulative credited distance.
///
/// A step between two accepted points adds its straight-line distance only
/// when it is a moving step (see [isMovingStep]). A signal dropout (a gap over
/// [kMaxCreditGapSeconds] with no pause) is bridged the way RunnerUp's export
/// bridges it, provided it implies [kMinDropoutSpeedMps] or faster; anything
/// above the jump cap is rejected, and standing-still jitter credits nothing.
/// A user pause ([breakSegment]) never bridges: pauses earn nothing.
/// A step between two accepted points adds distance only when it is a moving
/// step (see [isMovingStep]): a dropout longer than [kMaxCreditGapSeconds]
/// and standing-still jitter credit nothing. The PC judges windows of 10 s or
/// more and keeps a dropout moving at 1 m/s or faster, so it can credit more
/// than this does (see `run_math.dart`).
class GpsFilter {
/// Creates a filter; every recording uses the running jump cap.
GpsFilter();
@ -128,7 +127,8 @@ class GpsFilter {
_accept(fix, time, 0);
return FixVerdict.accepted;
}
final moving = step / gap >= minStepSpeed(gap);
final moving =
gap <= kMaxCreditGapSeconds && step / gap >= kMinMovingSpeedMps;
_accept(fix, time, moving ? step : 0);
return FixVerdict.accepted;
}

View File

@ -39,7 +39,6 @@ abstract class PositionSource {
/// Fixes roughly every [interval], best accuracy, until cancelled.
///
/// [interval] must stay well under the PC's 30 s credit gap
/// (`kMaxCreditGapSeconds`): a quieter stream is treated as a dropout and
/// only counts at 1 m/s or faster.
/// (`kMaxCreditGapSeconds`): a quieter stream earns nothing.
Stream<GpsFix> fixes({Duration interval = const Duration(seconds: 2)});
}

View File

@ -10,14 +10,11 @@
/// 30 s or less earns moving time at 0.5 m/s or faster; a longer one (a GPS
/// dropout) at 1 m/s or faster; paused time earns nothing.
///
/// The app mirrors those bounds per pair of its 2 s fixes, and bridges a
/// dropout the way a RunnerUp export does: the straight-line distance across a
/// gap over 30 s that the runner did not pause is credited when it implies
/// 1 m/s to 25 km/h (`GpsFilter`); a user pause credits nothing. The
/// on-screen distance, the saved run and the TCX `DistanceMeters` all come from
/// that one cumulative figure. The only remaining difference is that the PC
/// judges 10 s windows rather than single pairs, so its moving time can differ
/// slightly on a jittery or stop-and-go stretch.
/// The app mirrors only the bounds, per pair of its 2 s fixes, and is stricter:
/// a pair over 30 s credits nothing, where the PC keeps a dropout at 1 m/s or
/// faster. The app writes zero distance across such a gap, so the PC does not
/// credit a dropout in an app-recorded run. Its on-screen moving time is
/// therefore an estimate, not the number the PC will credit.
library;
import 'dart:math' as math;
@ -27,8 +24,8 @@ import 'package:workout_app/models/run_track.dart';
/// Mean Earth radius in metres (IUGG), the haversine constant.
const double kEarthRadiusM = 6371008.8;
/// A pair of fixes further apart in time than this is a GPS dropout whose
/// motion is unknown: it counts only at [kMinDropoutSpeedMps] or faster.
/// A pair of fixes further apart in time than this earns neither distance
/// nor moving time: a GPS dropout whose motion is unknown.
///
/// Mirrors `WALK_MAX_GAP_SECONDS` in the PC's `screen_locker/_constants.py`.
const int kMaxCreditGapSeconds = 30;
@ -38,12 +35,6 @@ const int kMaxCreditGapSeconds = 30;
/// Mirrors `MIN_MOVING_SPEED_MPS` on the PC.
const double kMinMovingSpeedMps = 0.5;
/// A dropout (a gap over [kMaxCreditGapSeconds]) counts only when the straight
/// line across it implies at least this speed; slower is standing or a pause.
///
/// Mirrors `MIN_DROPOUT_SPEED_MPS` on the PC.
const double kMinDropoutSpeedMps = 1;
/// Speed cap above which a fix is a GPS jump, 25 km/h.
///
/// Mirrors `MAX_MOVING_SPEED_MPS` on the PC, which applies it to each window
@ -65,23 +56,18 @@ double haversineM(double lat1, double lon1, double lat2, double lon2) {
return 2 * kEarthRadiusM * math.asin(math.min(1, math.sqrt(a)));
}
/// The slowest speed that counts as moving across a [gapSeconds] step.
double minStepSpeed(num gapSeconds) => gapSeconds > kMaxCreditGapSeconds
? kMinDropoutSpeedMps
: kMinMovingSpeedMps;
/// Whether the step from [a] to [b] counts as moving: travelled at a speed in
/// `[kMinMovingSpeedMps, maxSpeed]`, or in `[kMinDropoutSpeedMps, maxSpeed]`
/// when the fixes are further apart than [kMaxCreditGapSeconds] (a dropout).
/// Whether the step from [a] to [b] counts as moving: seen within
/// [kMaxCreditGapSeconds] and travelled at a speed in
/// `[kMinMovingSpeedMps, maxSpeed]`.
///
/// Speed comes from the cumulative `distanceM` difference, as the PC reads a
/// TCX, but over one pair of fixes rather than a window of 10 s or more, so the
/// two can disagree on a jittery or stop-and-go stretch.
bool isMovingStep(RunPoint a, RunPoint b, double maxSpeed) {
final gap = b.time.difference(a.time).inMilliseconds / 1000;
if (gap <= 0) return false;
if (gap <= 0 || gap > kMaxCreditGapSeconds) return false;
final speed = (b.distanceM - a.distanceM) / gap;
return speed >= minStepSpeed(gap) && speed <= maxSpeed;
return speed >= kMinMovingSpeedMps && speed <= maxSpeed;
}
/// Moving seconds over [points]: the summed gaps of every moving step.

View File

@ -13,16 +13,10 @@ import 'package:workout_app/services/tcx_codec.dart';
const String kFixtureDir = '../../screen_locker/tests/fixtures/run_tcx';
/// The GPS-dropout export: shares the folder for the golden twin, but is not
/// one of the three exports these importer and backup tests count on.
const String kDropoutFixture = 'RunnerUp_2026-11-09-18-00-00_Running.tcx';
/// The three golden exports, by file name.
Map<String, Uint8List> fixtureTcx() => {
for (final f in Directory(kFixtureDir).listSync().whereType<File>())
if (f.path.endsWith('.tcx') &&
f.uri.pathSegments.last != kDropoutFixture)
f.uri.pathSegments.last: f.readAsBytesSync(),
if (f.path.endsWith('.tcx')) f.uri.pathSegments.last: f.readAsBytesSync(),
};
final DufsCredentials kLogin = DufsCredentials(

View File

@ -90,14 +90,14 @@ void main() {
expect(f.points.first.altitudeM, 30);
});
test('standing-still jitter and standing through a gap earn nothing', () {
test('standing-still jitter and a long dropout earn no distance', () {
final f = GpsFilter()
..add(_fix(0, 0))
..add(_fix(10, 2)); // 0.2 m/s: standing
expect(f.distanceM, 0);
f.add(_fix(60, 2)); // 50 s later, same spot: nothing to bridge
f.add(_fix(60, 100)); // 50 s later: unseen motion
expect(f.distanceM, 0);
f.add(_fix(70, 29)); // seen again, moving
f.add(_fix(70, 127)); // seen again, moving
expect(f.distanceM, closeTo(27, 0.2));
expect(f.points, hasLength(4));
});
@ -129,60 +129,6 @@ void main() {
});
});
group('signal dropout (a gap over 30 s while running)', () {
// Mirrors the PC: WALK_MAX_GAP_SECONDS 30, MIN_DROPOUT_SPEED_MPS 1.0,
// MAX_MOVING_SPEED_MPS 25 km/h.
GpsFilter runningTo30m() => GpsFilter()
..add(_fix(0, 0))
..add(_fix(10, 30));
test('at 3 m/s the straight-line distance is bridged', () {
final f = runningTo30m();
expect(f.add(_fix(70, 210)), FixVerdict.accepted); // 60 s, 180 m
expect(f.distanceM, closeTo(210, 0.5));
expect(f.points.last.distanceM, closeTo(210, 0.5));
});
test('just over the dropout floor (1 m/s) is bridged', () {
final f = runningTo30m()..add(_fix(70, 91)); // 60 s, 61 m: 1.02 m/s
expect(f.distanceM, closeTo(91, 0.5));
});
test('0.5 m/s is standing about, not a run: nothing is bridged', () {
final f = runningTo30m();
expect(f.add(_fix(70, 60)), FixVerdict.accepted); // 60 s, 30 m
expect(f.distanceM, closeTo(30, 0.5));
});
test('40 km/h is a GPS jump: rejected, nothing is bridged', () {
final f = runningTo30m();
expect(f.add(_fix(70, 30 + 666)), FixVerdict.tooFast); // 11.1 m/s
expect(f.distanceM, closeTo(30, 0.5));
});
test('31 s is a dropout needing 1 m/s, 30 s still needs only 0.5', () {
final slow = runningTo30m()..add(_fix(41, 50)); // 31 s, 20 m: 0.65 m/s
expect(slow.distanceM, closeTo(30, 0.5));
final ok = runningTo30m()..add(_fix(40, 50)); // 30 s, 20 m: 0.67 m/s
expect(ok.distanceM, closeTo(50, 0.5));
});
test('a user pause with the same geometry bridges nothing', () {
final f = runningTo30m()..breakSegment();
expect(f.add(_fix(70, 210)), FixVerdict.accepted);
expect(f.distanceM, closeTo(30, 0.5));
});
test('moving time and distance agree across the bridged dropout', () {
final f = runningTo30m()
..add(_fix(70, 210))
..add(_fix(80, 240));
expect(f.distanceM, closeTo(240, 0.5));
// 10 s + 60 s dropout + 10 s, as the PC counts it.
expect(movingSeconds(f.points), 80);
});
});
group('breakSegment', () {
test('the fix after a pause earns nothing, then crediting resumes', () {
final f = GpsFilter()

View File

@ -1,79 +0,0 @@
@TestOn('vm')
library;
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:workout_app/models/run_track.dart';
import 'package:workout_app/services/gps_filter.dart';
import 'package:workout_app/services/run_math.dart';
import 'package:workout_app/services/tcx_codec.dart';
import '../run_test_fixtures.dart';
/// A run with a GPS dropout, written by the app's own filter and TCX writer.
///
/// 30 min at 3 m/s, a fix every 20 s, except that the signal is gone between
/// second 600 and 720. The PC credits a dropout that moved at 1 m/s or more
/// (`MIN_DROPOUT_SPEED_MPS`), so the cumulative `DistanceMeters` must jump
/// 360 m across it. The Python twin, `screen_locker/tests/test_run_tcx_golden.py`,
/// feeds the SAME checked-in file to the real verifier. Regenerate it with
/// `UPDATE_GOLDEN=1 flutter test test/services/run_dropout_tcx_test.dart`.
const String _dir = '../../screen_locker/tests/fixtures/run_tcx';
const String _file = 'RunnerUp_2026-11-09-18-00-00_Running.tcx';
RunTrack _recordedRun() {
final filter = GpsFilter();
for (var s = 0; s <= 1800; s += 20) {
if (s > 600 && s < 720) continue; // the dropout
final fix = fixAt(s, s * 3.0);
filter.add(
GpsFix(
time: DateTime.utc(2026, 11, 9, 17).add(Duration(seconds: s)),
latitude: fix.latitude,
longitude: fix.longitude,
altitudeM: 30,
accuracyM: 5,
),
);
}
final points = filter.points;
return RunTrack.started(
sport: RunSport.running,
startedAt: points.first.time,
points: points,
totalSeconds: movingSeconds(points),
distanceM: filter.distanceM,
);
}
void main() {
final track = _recordedRun();
test('cumulative DistanceMeters jumps across the dropout', () {
final back = decodeTcx(encodeTcx(track), fileName: _file);
final before = back.points.firstWhere(
(p) => p.time.minute == 10 && p.time.second == 0,
);
final after = back.points.firstWhere(
(p) => p.time.minute == 12 && p.time.second == 0,
);
expect(before.distanceM, closeTo(1800, 0.5));
expect(after.distanceM - before.distanceM, closeTo(360, 0.5));
});
test('lap distance and moving time count the dropout', () {
expect(track.distanceM, closeTo(5400, 1));
expect(track.totalSeconds, 1800);
});
test('encodes to exactly the checked-in file the PC test reads', () {
final encoded = encodeTcx(track);
final file = File('$_dir/$_file');
if (Platform.environment['UPDATE_GOLDEN'] == '1') {
file.writeAsStringSync(encoded);
}
expect(file.readAsStringSync(), encoded);
expect(tcxFilename(track, utcOffset: const Duration(minutes: 60)), _file);
});
}

View File

@ -50,17 +50,9 @@ void main() {
expect(isMovingStep(_p(10, 0), _p(5, 10), cap), isFalse);
});
test('30 s exactly needs 0.5 m/s, a longer dropout needs 1 m/s', () {
test('a gap over 30 s earns nothing, 30 s exactly does', () {
expect(isMovingStep(_p(0, 0), _p(30, 40), cap), isTrue);
expect(isMovingStep(_p(0, 0), _p(30, 16), cap), isTrue);
// 31 s at 0.65 m/s: a pause-like stand, not a bridged dropout.
expect(isMovingStep(_p(0, 0), _p(31, 20), cap), isFalse);
// 60 s at 1 m/s and at 3 m/s: bridged dropouts.
expect(isMovingStep(_p(0, 0), _p(60, 60), cap), isTrue);
expect(isMovingStep(_p(0, 0), _p(60, 180), cap), isTrue);
// A dropout faster than the cap or with no distance (pause) is not.
expect(isMovingStep(_p(0, 0), _p(60, 400), cap), isFalse);
expect(isMovingStep(_p(0, 0), _p(60, 0), cap), isFalse);
expect(isMovingStep(_p(0, 0), _p(31, 40), cap), isFalse);
});
test('movingSeconds sums only the moving steps', () {
@ -68,11 +60,10 @@ void main() {
_p(0, 0),
_p(10, 13), // moving, 10 s
_p(20, 13), // standing, 0 distance
_p(55, 23), // 35 s gap at 0.29 m/s, not credited
_p(65, 36), // moving, 10 s
_p(125, 216), // 60 s dropout at 3 m/s, bridged
_p(55, 60), // 35 s gap, not credited
_p(65, 73), // moving, 10 s
];
expect(movingSeconds(points), 80);
expect(movingSeconds(points), 20);
expect(movingSeconds(const []), 0);
});
});

View File

@ -184,43 +184,6 @@ void main() {
recorder.dispose();
});
test(
'a signal dropout is bridged, a pause of the same length is not',
() async {
Future<RunRecorder> run({required bool paused}) async {
final source = ManualPositionSource();
final recorder = _recorder(source);
await recorder.start();
source.controller
..add(fixAt(0, 0))
..add(fixAt(2, 6));
await pumpEventQueue();
if (paused) recorder.pause();
// 58 s later and 174 m further: 3 m/s, as if the signal dropped out.
if (paused) recorder.resume();
source.controller
..add(fixAt(60, 180))
..add(fixAt(62, 186));
await pumpEventQueue();
return recorder;
}
final dropout = await run(paused: false);
expect(dropout.distanceM, closeTo(186, 0.5));
expect(dropout.seconds, 62);
final track = (await dropout.finish())!;
expect(track.distanceM, closeTo(186, 0.5));
expect(track.totalSeconds, 62);
expect(track.points.last.distanceM, closeTo(186, 0.5));
final pause = await run(paused: true);
expect(pause.distanceM, closeTo(12, 0.5));
expect(pause.seconds, 4);
dropout.dispose();
pause.dispose();
},
);
test(
'finish works from paused; pause and resume elsewhere are no-ops',
() async {