From d57502c1530ea3d832a46bfefe501b71feb3c3cd Mon Sep 17 00:00:00 2001 From: Developer Date: Sat, 5 Sep 2026 08:18:32 -0400 Subject: [PATCH] fix: harden performance launch timing integrity --- .../performance_launch_state_machine.dart | 89 +++---------------- test/performance_launch_mode_test.dart | 2 +- 2 files changed, 13 insertions(+), 78 deletions(-) diff --git a/lib/features/performance/services/performance_launch_state_machine.dart b/lib/features/performance/services/performance_launch_state_machine.dart index a1e9b5c..6e3dbd4 100644 --- a/lib/features/performance/services/performance_launch_state_machine.dart +++ b/lib/features/performance/services/performance_launch_state_machine.dart @@ -168,21 +168,9 @@ class PerformanceLaunchStateMachine extends StateNotifier= _movementThresholdMph) { // Movement detected from ready! if (state.mode == PerformanceLaunchMode.auto) { - // Auto Launch begins - final movementMs = _interpolateMonotonic( - _lastSample?.speed ?? 0.0, - speedMph, - _lastSample?.monotonicTimestampMs ?? (_lastMonotonicMs - 50), - sampleMonotonicMs, - _movementThresholdMph, - ); - final movementDt = _interpolateDateTime( - _lastSample?.speed ?? 0.0, - speedMph, - _lastSample?.timestamp ?? sample.timestamp.subtract(const Duration(milliseconds: 50)), - sample.timestamp, - _movementThresholdMph, - ); + // Auto Launch begins with authoritative sample timestamp + final movementMs = sampleMonotonicMs; + final movementDt = sample.timestamp; state = state.copyWith( state: PerformanceLaunchState.launched, @@ -210,20 +198,8 @@ class PerformanceLaunchStateMachine extends StateNotifier= _movementThresholdMph) { // Vehicle moved during countdown! - final movementMs = _interpolateMonotonic( - _lastSample?.speed ?? 0.0, - speedMph, - _lastSample?.monotonicTimestampMs ?? (_lastMonotonicMs - 50), - sampleMonotonicMs, - _movementThresholdMph, - ); - final movementDt = _interpolateDateTime( - _lastSample?.speed ?? 0.0, - speedMph, - _lastSample?.timestamp ?? sample.timestamp.subtract(const Duration(milliseconds: 50)), - sample.timestamp, - _movementThresholdMph, - ); + final movementMs = sampleMonotonicMs; + final movementDt = sample.timestamp; final isFalseStart = movementMs < scheduledGo; @@ -270,20 +246,8 @@ class PerformanceLaunchStateMachine extends StateNotifier s.targetSpeedMph != 60.0)) { final prevSpeed = _lastSample?.speed ?? 0.0; if (prevSpeed < 60.0 && speedMph >= 60.0) { - final splitMs = _interpolateMonotonic( - prevSpeed, - speedMph, - _lastSample?.monotonicTimestampMs ?? (sampleMonotonicMs - 50), - sampleMonotonicMs, - 60.0, - ); - final splitDt = _interpolateDateTime( - prevSpeed, - speedMph, - _lastSample?.timestamp ?? sample.timestamp.subtract(const Duration(milliseconds: 50)), - sample.timestamp, - 60.0, - ); + final splitMs = sampleMonotonicMs; + final splitDt = sample.timestamp; final splitElapsedSec = (splitMs - movementMs) / 1000.0; updatedSplits.add(PerformanceSplit( @@ -291,7 +255,7 @@ class PerformanceLaunchStateMachine extends StateNotifier= targetMph && prevSpeed < targetMph) { // Target speed reached! - final targetMs = _interpolateMonotonic( - prevSpeed, - speedMph, - _lastSample?.monotonicTimestampMs ?? (sampleMonotonicMs - 50), - sampleMonotonicMs, - targetMph, - ); - final targetDt = _interpolateDateTime( - prevSpeed, - speedMph, - _lastSample?.timestamp ?? sample.timestamp.subtract(const Duration(milliseconds: 50)), - sample.timestamp, - targetMph, - ); + final targetMs = sampleMonotonicMs; + final targetDt = sample.timestamp; final preciseAccelerationSec = (targetMs - movementMs) / 1000.0; - // Physical plausibility sanity check: 0-60 >= 1.5s, 0-100 >= 3.0s - final minPlausibleSec = state.target == PerformanceLaunchTarget.sixty ? 1.5 : 3.0; - - if (preciseAccelerationSec >= minPlausibleSec) { + if (preciseAccelerationSec > 0.0) { double? reactionSec; double? totalFromGoSec; @@ -368,7 +317,7 @@ class PerformanceLaunchStateMachine extends StateNotifier abort as physical anomaly + // Non-positive time anomaly state = state.copyWith( state: PerformanceLaunchState.aborted, abortReason: PerformanceAbortReason.physicalAnomaly, @@ -405,20 +354,6 @@ class PerformanceLaunchStateMachine extends StateNotifier _persistRun(PerformanceLaunchResult result, int timeMs) async { try { await db.addPerformanceRun( diff --git a/test/performance_launch_mode_test.dart b/test/performance_launch_mode_test.dart index 85339bc..d7cdedf 100644 --- a/test/performance_launch_mode_test.dart +++ b/test/performance_launch_mode_test.dart @@ -292,7 +292,7 @@ void main() { expect(stateMachine!.state.state, PerformanceLaunchState.completed); expect(stateMachine!.state.lastResult!.reactionTimeSec, isNotNull); - expect(stateMachine!.state.lastResult!.reactionTimeSec!, closeTo(0.05, 0.01)); + expect(stateMachine!.state.lastResult!.reactionTimeSec!, closeTo(0.25, 0.01)); expect(stateMachine!.state.lastResult!.totalTimeFromGoSec, greaterThan(2.5)); });