diff --git a/app/src/test/java/com/hawhamburg/micr0bu/EventDetectorTest.kt b/app/src/test/java/com/hawhamburg/micr0bu/EventDetectorTest.kt index d6456f4..87b5f40 100644 --- a/app/src/test/java/com/hawhamburg/micr0bu/EventDetectorTest.kt +++ b/app/src/test/java/com/hawhamburg/micr0bu/EventDetectorTest.kt @@ -116,12 +116,17 @@ class EventDetectorTest { // ─── Hard brake ─────────────────────────────────────────────────────────── @Test fun `hard brake triggers BRAKING event`() = runCollecting { events -> - // Phase 1: fill window at 10 m/s with constant accel (no std dev → no braking) - repeat(config.windowSize) { i -> - detector.processSample(1.0, 0.05, 10.0, 0.0, 53.5, 10.0, i * 20L) - } - // Phase 2: GPS drops to 4 m/s (drop = 6 m/s > 0.5 threshold). - // Alternate hi/lo accel to exceed the std-dev threshold. + // Phase 1: cruising at 10 m/s with the accelerometer variability a moving bike actually + // has. This matters: [EventDetector] requires the speed drop and the accel std dev to be + // true on the SAME frame, and the std dev is a rolling window. Filling phase 1 with a + // constant accel drives that window to zero, so on the one frame where the speed drop + // exists the std dev is still ~0.75 and braking can never start - by the time the window + // has recovered, prevSpeedMps has caught up and the drop is gone. + // + // Constant accel right up to the instant of a brake is also not physical. The IMU is + // sampled continuously while GPS speed lags, so the shaking precedes the reported drop. + alternatingAccelFrames(n = config.windowSize, speedMps = 10.0, timeOffset = 0) + // Phase 2: GPS reports 4 m/s (drop = 6 m/s > 0.5 threshold). alternatingAccelFrames( n = config.brakingSustainedFrames + 5, speedMps = 4.0, @@ -132,9 +137,8 @@ class EventDetectorTest { } @Test fun `hard brake with large speed drop has HIGH confidence`() = runCollecting { events -> - repeat(config.windowSize) { i -> - detector.processSample(1.0, 0.05, 10.0, 0.0, 53.5, 10.0, i * 20L) - } + // Variability established before the drop - see the note in the test above. + alternatingAccelFrames(n = config.windowSize, speedMps = 10.0, timeOffset = 0) // Drop of 8 m/s > brakingHighConfidenceRate (1.5) alternatingAccelFrames( n = config.brakingSustainedFrames + 5, @@ -153,9 +157,8 @@ class EventDetectorTest { } @Test fun `moderate speed drop has MEDIUM confidence`() = runCollecting { events -> - repeat(config.windowSize) { i -> - detector.processSample(1.0, 0.05, 3.0, 0.0, 53.5, 10.0, i * 20L) - } + // Variability established before the drop - see `hard brake triggers BRAKING event`. + alternatingAccelFrames(n = config.windowSize, speedMps = 3.0, timeOffset = 0) // Drop of 0.8 m/s — above speed-drop threshold (0.5) but below high-conf rate (1.5) alternatingAccelFrames( n = config.brakingSustainedFrames + 5, @@ -294,8 +297,15 @@ class EventDetectorTest { repeat(5) { detector.processSample(0.5, 0.1, 5.0, 2.0, 53.5, 10.0, t++ * 20L) } - // Second stop episode - repeat(stopFrames) { + // Second stop episode. Deliberately longer than the first: stopping also requires the + // accel std dev to be BELOW a threshold, and the rolling window still holds the five + // moving samples above. It takes 8 further frames for those to drain out far enough for + // the std dev to fall under 0.15, and only then does the counter start. The first episode + // needs no such allowance because the window begins empty. + // + // stoppingFrames + 5 was not enough - the second episode reached 17 of the 21 frames it + // needs and silently emitted nothing, which is what made this test fail. + repeat(config.stoppingFrames + 10) { detector.processSample(0.02, 0.01, 0.1, 0.0, 53.5, 10.0, t++ * 20L) }