# MOTORSIGNAL DEVELOPER OWNERSHIP MAP — PHASE 1 ## A. Source State - Known Baseline Branch: `master` - Checkpoint Commit: `d3caf37bb2ff0031bec5982791b77127dd341cbd` (Merge pull request #3) - No tracked or staged modifications exist in production files. ## B. Data Flow (Sensor to Pixels) 1. **Acquisition (`lib/services/location_service.dart`)** - `Geolocator.getPositionStream()` initializes the Android Fused Location Provider. - Settings: `LocationAccuracy.bestForNavigation` at a requested 100ms interval. - Triggers `_handleRawPosition()` upon receiving an OS-level GPS fix. 2. **Validation (`LocationService._isValidPosition`)** - Validates the fix before applying logic. - Drops fixes with > 200m accuracy (bootstrapping) or > 80m-100m accuracy (moving). - Drops impossible speeds (> 100.0 m/s or ~223 MPH). - Drops impossible acceleration (> 30.0 m/s²) and detects GPS teleportation over 50 meters. 3. **Filtering & Smoothing (`LocationService._applyEmaFilter`)** - **Stationary Jitter Deadband**: Speeds under 0.6 m/s (~1.3 MPH) are forced to 0.0 to prevent drifting at stoplights. - **Fast Dynamic Exponential Moving Average (EMA)**: - `alpha = 0.90` when delta > 0.4 m/s (preserves instant responsiveness when accelerating/braking). - `alpha = 0.75` for cruising. - The smoothed speed (in m/s) is added to `_controller.stream`. 4. **Sampling & The Primary Clock (`lib/services/telemetry_service.dart`)** - Subscribes to the location stream but **decouples** acquisition from reporting. - Uses a `Timer.periodic(const Duration(milliseconds: 50))` to establish a requested 20Hz monotonic clock. - `_emitSample()` reads the latest filtered position, converts speed from m/s to MPH (`speed * 2.23694`), and emits a `TelemetrySample`. 5. **State Aggregation (`lib/features/rides/ride_state_machine.dart`)** - Consumes `TelemetrySample`s at 20Hz via `_onTelemetryUpdate`. - Modifies dynamic metric tracking (`_processStats`) and performs auto-starts (gate: `>= 1.5 MPH`). - Isolates motorcycle dynamics (Top Speed, Lean Angle, G-Force) behind a `>= 3.0 MPH` gate to prevent kickstand or walking artifacts from registering as personal records. 6. **UI Display (`lib/features/dashboard/widgets/speed_readout.dart`)** - Rebuilding the entire dashboard at 20Hz would cause severe UI lag. - `ResponsiveDashboardLayout` isolates updates using the `SpeedReadout` leaf widget. - `SpeedReadout` selects freshness-aware `sample.freshSpeed` from `telemetryStreamProvider`; unavailable/stale speed renders as `--`. - Clamps the value to `>= 0.0`, converts to an integer string (`toStringAsFixed(0)`), and renders. ## C. Discoveries (Current implementation speeds) MotorSignal manages speed in several distinct concepts: - **Raw GNSS Speed**: `position.speed` in `LocationService` (m/s directly from OS, unfiltered). - **Filtered Speed**: `_emaSpeed` in `LocationService` (m/s after EMA and deadband). - **Telemetry Speed**: `sample.speed` in `TelemetrySample` (converted to MPH, sampled at 20Hz). - **Display Speed**: Clamped to `>= 0.0` inside `SpeedReadout` before integer rounding. - **Top Speed (Current implementation)**: `state.topSpeedMph` managed by `RideStateMachine`, rejecting single spikes > 220 MPH. - **Average Speed**: Derived in `RideStateMachine` (`(tripDistanceMiles / newMovingTime) * 3600`) — relies heavily on the `1.8 MPH` threshold determining `movingTime`. ## D. Boundaries (High-Risk Concepts) The following behaviors are load-bearing and architecturally high-risk: 1. **The 20Hz / 50ms Clock**: `TelemetryService._samplingTimer` is the primary telemetry sampling clock. The UI does not poll for location or dictate sampling frequency. 2. **Stationary Deadband (< 0.6 m/s)**: Enforced in `LocationService` to prevent the speedometer from bouncing while idling. 3. **Dynamic Metrics Gate (< 3.0 MPH)**: `RideStateMachine` ignores lean angles and heavy G-forces when rolling slowly. 4. **Auto-Start Gate (>= 1.5 MPH)**: `RideStateMachine` uses this threshold to transition from `ready` to active recording. 5. **GPS Freshness Invariant**: GPS freshness advances from genuinely newer underlying GNSS evidence, not merely from creation of a new telemetry wrapper. Repeated or cached GNSS fixes repackaged into 20 Hz `TelemetrySample`s do not refresh `_lastGpsEvidenceMonotonicMs`. 6. **Speed Freshness Invariant**: Fresh telemetry wrapper does not imply fresh GNSS-derived speed. Speed freshness (`TelemetrySample.isSpeedFresh` / `TelemetrySample.freshSpeed`) is valid only while the underlying GNSS fix age is within the canonical 4000 ms staleness bound. Stale GNSS fixes render as `'--'` on `SpeedReadout` rather than freezing on the last measured speed or fabricating 0 MPH. 7. **GNSS-Derived Metrics Integrity Invariant**: Stale GNSS evidence may remain preserved in raw telemetry (`sample.speed`), but it must not create new GNSS-derived ride facts: - **Top Speed**: `state.topSpeedMph` updates only when `sample.isSpeedFresh && sample.speed != null && sample.speed! <= 220.0`. - **Moving Time & Stopped Time**: Sub-second continuous time accumulation (`_movingTimeAccumulator` / `_stoppedTimeAccumulator`) requires `sample.isSpeedFresh && _lastSample!.isSpeedFresh`. Stale samples do not accumulate moving or stopped time. - **Dynamic Motorcycle Metrics Gate**: Dynamic metrics (max lean, max accel/braking G) require `sample.isSpeedFresh && sample.speed != null && sample.speed! >= 3.0`, preventing stationary/kickstand artifacts during a GPS outage from polluting records. - **Trip Distance & Elevation**: Requires `hasFreshGps = sample.isSpeedFresh && (_lastSample?.isSpeedFresh ?? false)`. Distance and elevation do not accumulate across an outage, preventing artificial catch-up jumps upon recovery. - **High Speed Event Detection**: Logging 'High Speed' requires `sample.isSpeedFresh && sample.speed != null && sample.speed! > 100.0`, preventing repeated events during an outage. - **Performance Launch State Machine**: Active runs (`launched` or `countdown`) abort with `PerformanceAbortReason.gpsLost` when GPS becomes stale. `ready` state reverts to `idle` on stale GPS, preventing false movement detection or high-speed launch anomalies upon recovery. - **Performance Timer Service**: Active runs in `timing` state abort when GPS becomes stale, preventing false 0-60/0-100 completions across outages. 8. **Heading & Location Freshness Invariant**: - **Heading Selection**: `TelemetryService._emitSample()` selects GPS course (`_latestPosition.heading`) only when GPS is fresh (`isGpsFresh`), `_latestPosition.heading >= 0`, and `speedMph > 3.0`. When GPS becomes stale (> 4000 ms), heading selection immediately falls back to live hardware compass azimuth (`_latestCompassHeading`), preventing stale course angles from overriding the rider's actual orientation. When fresh GPS resumes and speed exceeds 3.0 MPH, GPS heading automatically recovers. Normal 10 Hz / 20 Hz sample repetitions within 4000 ms maintain heading stability without flicker. - **Location & Map Presentation**: `DashboardMapPanel` consumes `s.value?.isGpsFresh`. When GPS coordinates are fresh, the vehicle marker renders in active CyberCyan without warning overlays. When GPS becomes stale (> 4000 ms), the last-known coordinate is retained without artificial dead-reckoning or fake drift, the vehicle marker is dimmed (alpha 0.38), and a subtle `LAST KNOWN LOCATION` pill badge is displayed. When fresh GPS recovers, the badge disappears and the marker restores full CyberCyan. - **GPS Status Indicator**: `GPSStatusIndicator` evaluates `isGpsFresh`. When GPS is stale, it truthfully indicates `NO GPS` with `Icons.gps_off` rather than falsely reporting `GPS 3D FIX` from cached accuracy. ## E. Tests - `test/location_service_test.dart`: - Protects physical/GPS thresholds (0.6 m/s deadband == 1.34 MPH, 100 m/s limit == 223 MPH, 3.0s 0-60 == 0.91g). - `test/telemetry_validation_test.dart`: - Validates `TelemetryValidator`'s sanity checks, such as rejecting > 220 MPH jumps and flagging poor GPS confidence. - `test/stale_gps_regression_test.dart`: - Validates GPS evidence freshness semantics: duplicate fix timestamps do not refresh GPS freshness, genuinely newer fixes refresh evidence, older out-of-order fixes are ignored, null fixes do not refresh evidence, normal 10 Hz / 20 Hz sampling remains healthy, GPS outages degrade health to `recordingDegraded`, and fresh fixes recover to `recordingActive`. - `test/truthful_speed_presentation_test.dart`: - Validates speed freshness semantics on `TelemetrySample` and `SpeedReadout` presentation across normal moving GPS, 10 Hz / 20 Hz duplication, GNSS outages, non-zero placeholder behavior (`'--'`), genuine stopped motorcycle (`0` MPH), automatic recovery, and ride-independent operation. - `test/gnss_metrics_integrity_test.dart`: - Validates GNSS-derived metrics integrity during stale GPS: moving time cessation, distance cessation, top speed isolation, boundary conditions at 3999ms / 4000ms / 4001ms, recovery without jump distance or time integration across outage, dynamic motorcycle metrics isolation, High Speed event deduplication, PerformanceLaunchStateMachine abort on stale GPS in launched/countdown and reversion in ready, and PerformanceTimerService abort in timing. - `test/truthful_heading_location_test.dart`: - Validates heading selection and map/location presentation truthfulness during stale GPS: GPS course priority when moving fresh, normal 10 Hz / 20 Hz fix stability, fallback to live compass when GPS is stale (> 4000ms), automatic heading recovery on fresh GPS, 3999/4000/4001ms boundaries, map marker dimming, LAST KNOWN LOCATION pill badge, absence of fake drift / dead reckoning, automatic map recovery, ride independence, and raw forensic data preservation. ## F. Documents Created - `docs/CODE_MAP.md` - `docs/DEBUGGING_MAP.md` ## G. Change Scope - `lib/features/rides/ride_state_machine.dart`: Added `_lastSeenGpsTimestamp` tracking so GPS evidence freshness advances only on genuinely newer GNSS fixes. Aligned lifecycle reset in `_resetHealthEvidence()` and `reset()`. Gated `newTopSpeed`, `isDynamicallyMoving`, continuous moving/stopped time accumulation, distance accumulation, elevation accumulation, and 'High Speed' event detection on `sample.isSpeedFresh`. - `lib/models/telemetry_sample.dart`: Added `isSpeedFresh`, `freshSpeed`, and `isGpsFresh` getters reusing `RecordingHealthContract.evidenceStaleAfterMs` (4000 ms). - `lib/features/dashboard/widgets/speed_readout.dart`: Updated `SpeedReadout` to present `freshSpeed` and render `'--'` when stale. - `lib/features/performance/services/performance_launch_state_machine.dart`: Added stale GPS check in `_onTelemetryUpdate` to abort active runs (`launched`, `countdown`) with `PerformanceAbortReason.gpsLost` and revert `ready` to `idle`. - `lib/features/performance/performance_timer_service.dart`: Added stale GPS check in `_onTelemetryUpdate` to abort `timing` state on stale GPS. - `lib/services/telemetry_service.dart`: Gated GPS heading selection on `isGpsFresh` using `RecordingHealthContract.evidenceStaleAfterMs` (4000 ms). - `lib/features/dashboard/widgets/dashboard_map_panel.dart`: Dimmed vehicle marker and added `LAST KNOWN LOCATION` pill badge when `!isGpsFresh`. - `lib/features/dashboard/widgets/gps_status_indicator.dart`: Required `isGpsFresh` for reporting active GPS fix status. - `test/stale_gps_regression_test.dart`: Added comprehensive behavioral test suite for stale GPS, duplicate fixes, outage degradation, and recovery. - `test/truthful_speed_presentation_test.dart`: Added comprehensive behavioral and widget test suite for truthful speed presentation. - `test/gnss_metrics_integrity_test.dart`: Added comprehensive behavioral test suite for GNSS-derived metrics integrity during stale GPS. - `test/truthful_heading_location_test.dart`: Added comprehensive behavioral and widget test suite for truthful heading selection and location presentation. ## H. Remaining Issues / Future Considerations - All primary GNSS-derived subsystems (recording health, speedometer presentation, ride statistics, performance launch timers, heading selection, map/location presentation, and GPS status indicator) now operate under a single, unified canonical freshness contract (4000 ms bound). No further unaddressed stale GNSS corruption vectors remain in core telemetry paths.