diff --git a/shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/ActiveSessionEngine.kt b/shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/ActiveSessionEngine.kt index ae53ef692..ac3ac1185 100644 --- a/shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/ActiveSessionEngine.kt +++ b/shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/manager/ActiveSessionEngine.kt @@ -540,18 +540,18 @@ class ActiveSessionEngine( /** * Standard set stall guard: - * - Ignore fail/stall detection before first confirmed working rep. + * - Defer stall detection only when no working reps are confirmed AND no rep is pending. * - * Issue #256: Removed hasPendingRep guard. Previously, stall detection was deferred - * while a rep was pending at TOP (to avoid false triggers during brief lockout pauses). - * However, a stalled pending rep IS the failure scenario (e.g., failed bench press). - * The velocity hysteresis band (STALL_VELOCITY_LOW=2.5, STALL_VELOCITY_HIGH=10.0 mm/s) - * already provides adequate protection against false triggers during brief pauses. + * Issue #256: Removed hasPendingRep guard from deferral. A stalled pending rep IS the + * failure scenario (e.g., failed bench press at TOP of first rep). The velocity hysteresis + * band (STALL_VELOCITY_LOW=2.5, STALL_VELOCITY_HIGH=10.0 mm/s) already provides adequate + * protection against false triggers during brief pauses. When workingReps == 0 but + * hasPendingRep is true, the user is mid-first-rep and must be protected. */ private fun shouldDeferStandardSetStall(params: WorkoutParameters, repCount: RepCount): Boolean { val isStandardSet = !params.isJustLift && !params.isAMRAP && !coordinator.isCurrentTimedCableExercise if (!isStandardSet) return false - return repCount.workingReps == 0 + return repCount.workingReps == 0 && !repCount.hasPendingRep } /** diff --git a/shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/manager/DWSMWorkoutLifecycleTest.kt b/shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/manager/DWSMWorkoutLifecycleTest.kt index 90428cead..3ed6d4215 100644 --- a/shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/manager/DWSMWorkoutLifecycleTest.kt +++ b/shared/src/commonTest/kotlin/com/devil/phoenixproject/presentation/manager/DWSMWorkoutLifecycleTest.kt @@ -510,21 +510,20 @@ class DWSMWorkoutLifecycleTest { advanceUntilIdle() assertIs(harness.dwsm.coordinator.workoutState.value) - // Complete warmup and first working rep + // Complete warmup completeWarmupReps(harness, warmupTarget = 3, workingTarget = 8) - completeFirstWorkingRep(harness, warmupTarget = 3, workingTarget = 8) advanceUntilIdle() assertTrue(harness.dwsm.coordinator.repCount.value.isWarmupComplete) - assertEquals(1, harness.dwsm.coordinator.repCount.value.workingReps) + assertEquals(0, harness.dwsm.coordinator.repCount.value.workingReps) - // Simulate starting a second rep (pending at TOP = failed bench press scenario) + // Simulate starting the first rep (pending at TOP = failed bench press scenario) harness.fakeBleRepo.emitRepNotification( RepNotification( - topCounter = 5, // warmup(3) + working(2) = 5th up counter - completeCounter = 4, // Only 4 downs (second working rep not completed) + topCounter = 4, // warmup(3) + working(1) = 4th up counter + completeCounter = 3, // Only 3 downs (first working rep not completed) repsRomCount = 3, repsRomTotal = 3, - repsSetCount = 1, // Still 1 completed working rep + repsSetCount = 0, // Still 0 completed working reps repsSetTotal = 8, rangeTop = 800f, rangeBottom = 0f, @@ -571,17 +570,16 @@ class DWSMWorkoutLifecycleTest { assertIs(harness.dwsm.coordinator.workoutState.value) completeWarmupReps(harness, warmupTarget = 3, workingTarget = 8) - completeFirstWorkingRep(harness, warmupTarget = 3, workingTarget = 8) advanceUntilIdle() - // Simulate pending rep (stalled mid-concentric) + // Simulate pending first rep (stalled mid-concentric) harness.fakeBleRepo.emitRepNotification( RepNotification( - topCounter = 5, - completeCounter = 4, + topCounter = 4, + completeCounter = 3, repsRomCount = 3, repsRomTotal = 3, - repsSetCount = 1, + repsSetCount = 0, repsSetTotal = 8, rangeTop = 800f, rangeBottom = 0f,