11 KiB
Website recorded-GPS replay fix — 2026-10-04
Outcome and scope
Deployed commit b25b6e57a77f35bf6c81d3bfedd605c6f4d8f708 replaces 8b3a7aa29290b58a9456a564785e9c73cd8e3093. Public /api/version confirms the full revision. One existing production ride was replayed through an existing authorized signed-in browser, without production fixtures or exporting its authentication token.
Ride: ride_d82e047e97c6f7668ad785dd6d8d6467, September 3, 2026 at 08:52 PM, 2001 Yamaha YZF-R6, 15.66 miles. This existing affected ride was independently reproduced; the user did not explicitly confirm that this ID was their originally open ride.
Demonstrated root cause
The old browser telemetry request omitted its Firebase bearer token. The old endpoint looked for local GPX/demo files, did not enforce ride ownership, and discarded a successfully retrieved R2 response instead of decoding it. This ride's endpoint returned HTTP 404. The modal ignored that failure and generated 2,400 invented points around 35.18520, -83.92140 from summary statistics. It displayed that invented track as replay telemetry.
Consequently this was not merely a map-tile or latitude/longitude inversion problem: the recorded coordinates never reached the map. An independent screen-space canvas fallback could additionally detach the overlay from map geography. Legacy parsers also lost mixed-order GPX points and recording segment boundaries; ride changes could retain previously fetched data.
Corrected data path
- Browser selects the stable ride ID and sends its normal Firebase ID token with a private, uncached telemetry request.
- Website Firebase Admin verifies the token, including revocation, then reads
users/{uid}/rides/{rideId}. The owner, stored ride identity, and bundle object key must match. Server SDK access is not treated as a substitute for those checks. - For bundle-backed rides, the existing authenticated R2 download gateway receives the same token. Its ride-ID header and SHA-256 are checked before ZIP decoding; the optional stored hash is checked when present. Download and selected-entry extraction limits are enforced.
telemetry.csvis preferred because it retains GPS-loss rows omitted by legacy GPX. GPX remains a fallback. Inline points/CSV/GPX follow the recorded-data parsing path without R2; both paths reject missing recorded GPS instead of inventing geometry.- Valid geographic latitude/longitude and original timestamps are retained and sorted chronologically. Explicit segments, invalid/missing GPS rows, and time gaps over five seconds break continuity. No road snapping, route reconstruction, coordinate translation, or sensor normalization is performed.
- Leaflet receives actual latitude/longitude pairs for bounds, rider markers, and segmented colored polylines. Independent screen-space canvas rendering is removed. Loading/error/ride-switch barriers clear obsolete geometry; closing removes map instances and resize listeners.
The verified production ride reports r2-csv, 19,759 recorded GPS points, and a recorded duration of 16:28. Its cloud summary duration is different; replay now uses recorded timestamps rather than manufacturing a summary-length trace.
Production browser coordinate comparisons
An independent Web Mercator calculation uses an observed OSM tile's z/x/y and screen rectangle. It is compared with both the rider icon center and the nearest actual Leaflet SVG geographic trace vertex. GPS HUD precision is five decimal places. All sampled errors are below one pixel; no canvas is present.
| Recorded sample | Index | Latitude, longitude | Marker and trace error |
|---|---|---|---|
| Start | 0 | 34.04784, -85.23513 | 0.4035 px |
| Turn 4 | 2,731 | 34.08204, -85.23273 | 0.4544 px |
| Turn 11 | 15,324 | 34.22821, -85.16143 | 0.5629 px |
| Turn 17 | 19,390 | 34.24516, -85.15971 | 0.5570 px |
| Finish | 19,758 | 34.24507, -85.15964 | 0.4663 px |
Pan, actual keyboard zoom from level 10 to 11, resize to 1280×900, and viewport reset retain alignment. Close leaves zero dialogs and map containers. Reopen first shows loading with no prior geometry, then returns to index zero at the recorded start with 0.4035-pixel alignment error.
Focused validation
| Check | Result and evidence category |
|---|---|
| Parser/authentication/R2/replay regressions | 19 tests passed across four focused files; automated, not a broad suite |
| Sanitized regression data | GPX attribute order/segments, canonical CSV missing GPS/zero coordinates/invalid ranges and times, ZIP retrieval/integrity, exact Leaflet geometry, loading/error/late response/cleanup |
| TypeScript | tsc --noEmit --incremental false passed |
| Fresh production build | Next 16.3.4 production build passed; 37 generated pages; exit 0 |
| Candidate staging | Version/account HTTP 200, 10 JavaScript assets HTTP 200, guest telemetry HTTP 401; candidate process stopped |
| Public production smoke | Full deployed revision HTTP 200; account HTTP 200; structured guest telemetry HTTP 401, private/no-store |
| Authenticated production replay | Existing R2-backed ride loaded recorded data and geographic overlay; coordinate comparison, pan, keyboard zoom, resize, close/reopen passed |
Commands, from the deployment worktree's website directory (using the configured Node 24 binary):
node node_modules/vitest/vitest.mjs run tests/telemetry-track-regression.test.ts tests/ride-telemetry-api.test.ts tests/replay-recorded-data.test.tsx tests/telemetry-replay-export.test.tsx --maxWorkers=1 --reporter=dot
node node_modules/typescript/bin/tsc --noEmit --incremental false
node C:/Users/Bill/Desktop/flutter-app/output/replay-20261004/build-release.mjs
node C:/Users/Bill/Desktop/flutter-app/output/replay-20261004/package-release.mjs
VPS commands used after artifact verification:
python3 /home/motorsignal-admin/deploy-replay-b25b6e57a77f.py stage
python3 /home/motorsignal-admin/deploy-replay-b25b6e57a77f.py deploy
Final secret-free preservation/public smoke:
node C:/Users/Bill/Desktop/flutter-app/output/replay-20261004/capture-final.mjs
Production configuration and changed files
Fresh build and existing secure website runtime both target Firebase project motoapp-d22e0. Backend remains https://api.motorsignal.app. Build loaded no environment files or credential material; emulator flags/hosts were absent. The approved existing runtime EnvironmentFile and ADC file were left unchanged. No demo-configured .next was reused. fflate was pinned to 0.8.3; no other dependency versions changed. It is bundled into the server artifact, so no VPS dependency installation was required.
Committed files (website-relative):
app/api/account/rides/[rideId]/telemetry/route.ts
components/web-telemetry-replay-modal.tsx
lib/telemetry-export.ts
lib/telemetry-track.ts
package.json
package-lock.json
tests/replay-recorded-data.test.tsx
tests/ride-telemetry-api.test.ts
tests/telemetry-replay-export.test.tsx
tests/telemetry-track-regression.test.ts
The production package contains the fresh runtime and product sources, not regression tests, QA fixtures, emulator configuration, evidence, environment files, build caches, or unrelated dirty work. Artifact SHA-256: 452117b11f9ec4e93835dc21e15f32802665f8b18816cc69a54ad291aaef8f29.
Only the website restarted. Backend and nginx remained active with unchanged PIDs. The release symlink, Firestore rules, mobile, billing, and unrelated backend services were not modified. No push occurred.
Rollback and cleanup
Rollback archive, mode 0600:
/home/motorsignal-admin/rollback-website-8b3a7aa-to-b25b6e5-20261004.tar.gz
SHA-256: 7b0cdeeb6743e8bcb270233c308bfc7b5e5616790f26d74d173c873dd62675e5.
Previous runtime is also retained at:
/opt/motorsignal/releases/20260920-master-7efd29a/website/.next.rollback-b25b6e57a77f
With secure administrator SSH access to the VPS, exact rollback command:
python3 /home/motorsignal-admin/deploy-replay-b25b6e57a77f.py rollback
The helper restores the prior website runtime and scoped sources, restarts only the website, and checks revision 8b3a7aa and website health. Backup contents/hashes were verified; actual rollback was not executed. Candidate process is stopped, temporary viewport override is reset, and old local build/evidence and VPS rollback files are retained recoverably. No production data was created or changed by verification.
Final Git: main remains release/v1.0.2-build102-community-feed at bd278cdfd3619242a3056c6a2738abb0aff70feb, dirty with existing work plus scoped replay/evidence files, zero staged files. All 150 baseline paths outside the authorized replay file set match their initial hashes; none are missing. Deployment worktree remains deploy/website-20261004 at b25b6e57a77f35bf6c81d3bfedd605c6f4d8f708, clean, zero staged files. Full untracked-inclusive status is recorded in the evidence folder; the preserved previous build accounts for many evidence entries.
Evidence and limits
Local evidence: C:/Users/Bill/Desktop/flutter-app/output/replay-20261004/.
baseline.json,final-git-status.json: branches, full HEADs, untracked-inclusive status, and preservation audit.browser-coordinate-comparisons.json,browser-alignment-check.js: production DOM coordinate results and independent projection method.production-config.json,production-build.log,production-build-result.json,artifact-scan.json,package.json: fresh build/configuration/package evidence.stage-result.json,deployment-result.json,public-smoke.json: staging, cutover, rollback, service isolation, and public revision/health.
Limits: this verifies one actual R2-backed production ride, not all historical rides or all basemaps. The raw production ZIP was integrity-checked by the server but was not independently retained and cross-exported for a separate source audit; pixel comparisons use the decoded recorded GPS displayed in the HUD. Inline and disconnected-segment cases have focused automated coverage, not newly created production examples. The five-second gap rule is conservative and cannot recover disconnection information absent from historical exports. A CSV browser download was attempted but no new artifact was independently verified; an older local CSV was not used as evidence. Post-deployment server journal contents were not separately audited.
The GPS information badge overlaps the mouse “+” control: clicking it did not change zoom. This separate UI defect remains unresolved; actual keyboard zoom and geographic alignment were verified. The shell staging/deployment JSON's authenticated “not run” field applies only to its credential-free HTTP probes; the separate authenticated browser replay above did run successfully. No authenticated-production claim is based on local fixtures.
The verification/shipping skills influenced the focused red/green gates, fresh production configuration checks, strict package scope, and prepared rollback. No unchanged broad suite was rerun.