update method
Update the state with new detection data.
Uses two complementary signals:
- YOLO action labels: "jump-shot" or "layup" on the player detection indicates a shot attempt is in progress (high confidence signal).
- Ball-hoop trajectory: traditional ball position tracking to confirm whether the shot was made or missed.
Implementation
ShotStatus update(DetectedObjects? objects) {
if (objects == null) return _currentStatus;
// Track player action from YOLO model
_lastPlayerAction = objects.player?.action;
// If no ball or hoop, we can't track the shot trajectory
if (objects.basketball == null || objects.hoop == null) {
// But if YOLO detects a shot action, we can still note the attempt
if (_currentStatus == ShotStatus.none &&
(_lastPlayerAction == 'jump-shot' || _lastPlayerAction == 'layup')) {
debugPrint('DEBUG_SHOT: YOLO détecte un tir ($_lastPlayerAction) mais pas de ballon/arceau visible');
}
return _currentStatus;
}
final ball = objects.basketball!.rect;
final hoop = objects.hoop!.rect;
final ballCenter = Point(ball.center.dx, ball.center.dy);
_ballHistory.add(ballCenter);
if (_ballHistory.length > _maxHistory) {
_ballHistory.removeAt(0);
}
// Shot Detection Logic:
// Signal 1: YOLO detects player-jump-shot or player-layup → strong shot intent
// Signal 2: Ball is above the hoop → trajectory-based shot detection
//
// Both signals can trigger the shot detection pipeline.
if (_currentStatus == ShotStatus.none) {
// Check YOLO action signal first (more reliable)
bool yoloShotDetected = _lastPlayerAction == 'jump-shot' ||
_lastPlayerAction == 'layup';
// Check trajectory signal (ball above hoop)
bool trajectorySignal = ballCenter.y < hoop.top;
if (yoloShotDetected || trajectorySignal) {
_currentStatus = ShotStatus.ballAboveHoop;
debugPrint('DEBUG_SHOT: Tir détecté! '
'YOLO=$yoloShotDetected(${_lastPlayerAction ?? "none"}), '
'Trajectory=$trajectorySignal');
}
}
// If ball center is inside hoop horizontal bounds and passes the rim Y
if (_currentStatus == ShotStatus.ballAboveHoop) {
bool isInsideHoopX = ballCenter.x > hoop.left && ballCenter.x < hoop.right;
bool isPassingHoopY = ballCenter.y > hoop.top && ballCenter.y < hoop.bottom;
if (isInsideHoopX && isPassingHoopY) {
_currentStatus = ShotStatus.ballInHoop;
debugPrint('DEBUG_SHOT: Ballon DANS l\'arceau !');
}
}
// Completion of the shot
if (_currentStatus == ShotStatus.ballInHoop) {
// If it goes below the hoop after being inside
if (ballCenter.y > hoop.bottom) {
_currentStatus = ShotStatus.made;
_score++;
_attempts++;
debugPrint('DEBUG_SHOT: PANIER RÉUSSI ! Score: $_score/$_attempts');
// Reset status after a short delay or next frame
Future.delayed(const Duration(seconds: 1), () {
_currentStatus = ShotStatus.none;
});
return ShotStatus.made;
}
}
return _currentStatus;
}