← All writing

Reading the Shooter, Not the Shot

By Luke Blommesteyn, Nathan Feng and Yuvraj Sharma • SPL x UTSPAN 2026 Data Challenge finalist, presented at OUSAC • Paper and code

The task sounds simple. You get 3D body keypoints of a person shooting a free throw, captured at 60 frames per second, and you predict where the ball lands: the entry angle, the depth, and how far left or right. There is no ball in the data. Only the body that throws it.

Before picking a model, we asked a different question first: how much information about the landing is actually in these keypoints? The answer shaped everything else we did, and it is the part of the project I think about most.

Five frames of a 3D skeleton shooting a free throw, with the shooting arm highlighted
One shot from stance to recovery. The shooting arm (shoulder, elbow, wrist) is in red. Release is frame 109.

The floor comes first

The wrist snap that decides a free throw lasts two or three frames at 60 FPS. That alone sets a limit. If you can only place the release instant to within about 8.3 ms, and the forearm is turning at roughly 800 degrees per second, the release angle is uncertain by about 6.7 degrees before any model touches it.

The other limits stack on top. The arm chain we can see accounts for under 5% of the ball's velocity; the rest comes from the wrist and fingers, which the keypoints barely resolve. Triangulation jitter of 1 to 2 cm turns into about 0.5 m/s of velocity noise after smoothing.

Putting numbers on it: predicting each shooter's average shot scores about 0.012 MSE. A good kinematic model gets to about 0.008. We estimated the floor for body pose alone at roughly 0.005 to 0.006. Everything after that is work on the gap between 0.008 and the floor. Knowing this early told us where effort would pay off.

Variance attribution across sources of irreducible uncertainty, and score progression toward the estimated floor
Left: where the irreducible error comes from. Right: our score at each stage against the estimated floor.

Every shooter is their own problem

The first thing the data showed was how different the five shooters are. The spread between people is far larger than the spread between one person's shots. Mean launch angles alone run from 40.6 degrees for one shooter to 52.3 for another. Who is shooting is the strongest single piece of information you have.

Box plots of angle, depth and left-right outcomes for each participant
Landing outcomes per shooter. Between-shooter differences dominate shot-to-shot variation for all three targets.

Stage 1: hard math upstream, simple model downstream

We find the release frame from the kinematics alone, using peak wrist height together with the moment the hands separate. From there we take smoothed velocities, accelerations and jerk, release-window joint angles, timing and coordination lags between joints, and how much velocity each arm segment contributes.

One feature came from running physics backwards. For each training shot we know where the ball landed, so projectile equations tell us what release velocity was needed. Compare that with the velocity the visible arm chain implies, and the difference is a velocity deficit. It turned out to be stable for each shooter but different from shot to shot, which fits the idea that the missing energy comes from wrist and finger motion we cannot see.

Wrist speed near release for each shooter, colored by landing angle
Wrist speed near release, colored by landing angle. Peaks range from about 6 m/s to over 12 m/s and last only 10 to 15 frames, which is the resolution problem in one picture.

All of that went into a per-target LightGBM, cross-validated by leaving one shooter out at a time. It scored about 0.008, a 35% improvement over the per-shooter average, and it is the anchor every later stage adjusts.

Why not simulate the ball?

We tried. Estimate the release from the arm, correct it with each shooter's deficit, and fly the ball forward with drag and Magnus lift. It scored about 0.012, no better than guessing each shooter's average, and the left/right predictions fell apart completely. Even a shooter with very consistent mechanics showed physics correlations running the wrong way, because spin and contact at the fingertips vary and we never observe them. When the ball is invisible, calibrating statistics beats simulating flight.

Case study showing inverted physics correlations for a consistent shooter
The forward-physics failure for one shooter: consistent release mechanics, inverted correlations.

Stage 2: calibrate each shooter, and each target differently

The raw predictions were good but miscalibrated in a specific way. We pull each prediction toward that shooter's mean, or push it away, with a separate factor per target:

\[ \hat{y}'_{i,t} = \mu_{p,t} + \gamma_t\,(\hat{y}_{i,t} - \mu_{p,t}) \]

The factors were not all the same, and that was the useful finding. Angle needed shrinking (\(\gamma < 1\)): the model was over-confident because angle depends on fast, noisy wrist timing. Depth and left/right needed stretching (\(\gamma > 1\)): the model was too timid on outcomes that follow slower whole-body motion. That took us to 0.0073.

Calibration diagnostics per target showing shrinkage for angle and expansion for depth and left-right
Angle wants shrinkage; depth and left/right want their variance back.

Stage 3: the models disagree in only a few ways

We trained many variants that emphasize different features. Each one produces 339 numbers (113 test shots, 3 targets). Treating each model as a difference from the calibrated anchor and running PCA over those differences, 7 directions explained 89% of how the models disagree. The first direction is the same angle-versus-depth tradeoff from Stage 2. The others line up with choices like which body segments to trust and how much to lean on the shooter's prior.

That made choosing a final blend a 7-dimensional search instead of a 339-dimensional one. A regularized quadratic surface over those coordinates ranked candidates and kept the predictions inside the space real models actually produce. Final score: 0.00689.

Explained variance by principal component for model disagreements
Seven components carry 89% of the variation between model variants.
StageMethodMSE
BaselineEach shooter's average0.0122
1Physics-informed LightGBM0.0080
2+ per-target calibration0.0073
3+ low-rank ensemble selection0.00689
Estimated floor for body pose alone0.005 to 0.006

What didn't work

With 345 shots from 5 people, more data was the obvious idea. We tried matching bone ratios to borrow shots from another year, self-supervised pretraining, and ballistic priors from an outside dataset. They helped us understand the features and added some variety to the ensemble, but none beat the core pipeline. The other data came from a different distribution, and it ran into the same measurement limit anyway. The next real improvement would come from a faster camera, or one that can see the ball.