107 lines
12 KiB
Markdown
107 lines
12 KiB
Markdown
# 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.
|