# MOTORSIGNAL DEBUGGING MAP This document maps common symptoms in the MotorSignal app to their specific root causes in the GNSS/Telemetry/Speedometer subsystems. Use this guide to jump directly to the responsible layer during triage. ## 1. Symptom: "Speedometer shows 0 while moving" **Possible Causes & Investigation:** - **Permissions Issue**: If OS-level location permissions were denied or revoked, `LocationService` stream does not initialize. - *Check*: `LocationService.hasPermissions()` - **Deadband Overreach**: If the rider is moving slower than 1.3 MPH (0.6 m/s), `LocationService._applyEmaFilter` enforces 0.0 to prevent stationary jitter. - *Check*: `LocationService._applyEmaFilter` - **Validation Rejection**: The OS is sending fixes with terrible accuracy (> 80m-100m) or physically impossible acceleration, causing `LocationService._isValidPosition` to silently drop the fix instead of updating the EMA. - *Check*: Look for `Accuracy poor` or `Impossible acceleration` logs from `_emitGpsDiagnostic`. ## 2. Symptom: "Speedometer freezes at a high speed during a tunnel/dead zone" **Possible Causes & Investigation:** - **Signal Loss Freeze**: When GPS fixes stop arriving, `LocationService` stops emitting new positions. However, `TelemetryService._emitSample()` continues to fire at 20Hz using `_latestPosition` (which contains the last known high speed). - *Status (RESOLVED)*: 1. **Recording Health (Phase 1D)**: `RideStateMachine._recordTelemetryEvidence()` checks `_lastSeenGpsTimestamp` and only refreshes GPS freshness when a genuinely newer GPS fix arrives. After `evidenceStaleAfterMs` (4000 ms) without a new fix, `RideStateMachine` truthfully transitions recording health to `DEGRADED`. 2. **Speed Presentation (Phase 1E)**: `TelemetrySample` now exposes `isSpeedFresh` and `freshSpeed`, evaluating the GNSS fix age against the canonical 4000 ms staleness bound. `SpeedReadout` consumes `freshSpeed` and truthfully presents `--` when GPS is stale or unavailable, preventing frozen speeds from being displayed while avoiding fabricated zero-speed measurements. When fresh GPS fixes resume, numerical speed presentation automatically recovers. ## 3. Symptom: "Speedometer lags significantly behind real-world braking/acceleration" **Possible Causes & Investigation:** - **EMA Tuning Conflict**: `LocationService._applyEmaFilter` uses a fast dynamic alpha (0.90) for accelerating/braking and 0.75 for cruising. If the delta threshold (> 0.4 m/s) isn't being tripped cleanly due to noisy GPS, it may stick to the slower 0.75 cruising alpha. - *Check*: `LocationService._applyEmaFilter` ## 4. Symptom: "Leaning the bike on the kickstand records a new 'Max Lean Angle' record" **Possible Causes & Investigation:** - **Low-Speed Gate Failure**: `RideStateMachine._processStats` restricts dynamic metrics (lean, accel, braking) to speeds `>= 3.0 MPH`. If a false GPS speed spike momentarily registers > 3.0 MPH while the bike is tilted on the kickstand, it can permanently update `state.maxLeanLeft` or `state.maxLeanRight`. - *Check*: `RideStateMachine._processStats` (`isDynamicallyMoving = sample.speed! >= 3.0`). ## 5. Symptom: "App constantly auto-starts a ride while parked in the driveway" **Possible Causes & Investigation:** - **Auto-Start Jitter**: `RideStateMachine._onTelemetryUpdate` promotes a `ready` state to `riding` if `sample.speed! >= 1.5`. If `LocationService` fails to enforce the 0.6 m/s (1.3 MPH) deadband correctly, GPS multipath bounce in a driveway can trigger a false start. - *Check*: `LocationService._applyEmaFilter` deadband logic. ## 6. Symptom: "Dashboard flickers or UI lags severely during high-speed rides" **Possible Causes & Investigation:** - **Provider Granularity Issue**: The dashboard is supposed to isolate high-frequency (20Hz) UI updates entirely within `SpeedReadout` (which uses `.select` on the telemetry provider). If an outer widget (like `ResponsiveDashboardLayout` or `DashboardScreen`) accidentally watches the root `telemetryStreamProvider` without `.select`, it will trigger 20 rebuilds per second of the entire screen. - *Check*: `EvolutionPortraitDashboard` and `ResponsiveDashboardLayout` usage of `ref.watch`. ## 7. Symptom: "GPS Weak / Degraded indicator won't go away even with clear skies" **Possible Causes & Investigation:** - **Pre-commit DB Failure or Watchdog Staleness**: The dashboard claims `RECORDING` or `DEGRADED` based entirely on the primary `RecordingHealthContract` in `RideStateMachine`. If `_lastDbCommitMonotonicMs` stops advancing (due to a SQLite deadlock) or `_lastAcceptedTelemetryTime` stops advancing, health defaults to degraded. - *Check*: Database flush interval in `RideStateMachine._persistOwnedTelemetrySnapshot` and the `_evaluateRecordingHealth` watchdog loop. ## 8. Symptom: "Trip distance or moving time inflated after riding through tunnel/dead zone" **Possible Causes & Investigation:** - **Stale GNSS Integration (RESOLVED in Phase 1F)**: When GPS fixes stop advancing, `TelemetryService` emits cached positions at 20Hz. Previously, `_movingTimeAccumulator` continued to increment based on the cached speed, and upon GPS recovery, the distance between pre-outage coordinates and post-outage coordinates could be added as an artificial catch-up jump. - *Resolution*: `RideStateMachine._processStats` gates moving/stopped time on `sample.isSpeedFresh && _lastSample!.isSpeedFresh`. Distance and elevation require `hasFreshGps = sample.isSpeedFresh && (_lastSample?.isSpeedFresh ?? false)`. When GPS is lost, time and distance cease accumulating; upon recovery, the first fresh fix resets baseline coordinates without integrating across the outage gap. - *Check*: `RideStateMachine._processStats` time accumulation, distance, and elevation gating. ## 9. Symptom: "Performance launch timer records anomalous 0-60 time or finishes without GPS" **Possible Causes & Investigation:** - **Stale Speed in Performance State Machine (RESOLVED in Phase 1F)**: If GPS fixes stop during an active launch or countdown, the state machine previously retained cached speeds, allowing runs to complete across signal loss or trigger high-speed launch anomalies upon recovery. - *Resolution*: `PerformanceLaunchStateMachine` and `PerformanceTimerService` inspect `sample.isSpeedFresh`. In `launched` or `countdown`, active runs abort immediately with `PerformanceAbortReason.gpsLost`. In `ready`, the state machine reverts to `idle`, preventing high-speed launch anomalies when fresh GPS resumes. - *Check*: `PerformanceLaunchStateMachine._onTelemetryUpdate` and `PerformanceTimerService._onTelemetryUpdate`. ## 10. Symptom: "Compass/Heading indicator remains stuck pointing in old direction after turning into tunnel/garage" **Possible Causes & Investigation:** - **Stale GNSS Heading Override (RESOLVED in Phase 1G)**: When GPS fixes stopped advancing while moving, `TelemetryService` continued to use the cached `_latestPosition.heading`, overriding live compass heading indefinitely. - *Resolution*: `TelemetryService._emitSample()` gates GPS heading selection on `isGpsFresh` (4000 ms bound). When GPS fix age exceeds 4000 ms, heading immediately falls back to live hardware compass. - *Check*: `TelemetryService._emitSample()` heading selection and `CompassService.headingStream`. ## 11. Symptom: "Map marker shows vehicle at active location when GPS signal is lost" **Possible Causes & Investigation:** - **Unindicated Stale Map Coordinate (RESOLVED in Phase 1G)**: `DashboardMapPanel` rendered the vehicle marker identically whether coordinates were current or stale. - *Resolution*: `DashboardMapPanel` watches `s.value?.isGpsFresh`. When stale, the marker is dimmed and a subtle `LAST KNOWN LOCATION` badge is displayed, preserving the last-known position truthfully without fabricating position. - *Check*: `DashboardMapPanel` and `TelemetrySample.isGpsFresh`.