ICE · Team C

The last half‑second before you shoot

Elite shooters hold their gaze still on the target just before release. Sports science calls it the quiet eye, and until now measuring it meant a lab. Shootbuddy measures it on an outdoor court from a pair of smart glasses and a phone on a tripod — every shot, every session, entirely on your own machine.

Two sessions, on a real court

14 minutes 14 seconds of footage, 71 clap markers, 33 shots with a complete quiet-eye window. Everything below is measured from those recordings — nothing here is a projection.

Shots analysed
33
of 71 clap markers recorded
Accuracy
40%
12 made · 18 miss · 3 undetermined
Median dispersion
2.50°
angular SD, all shots pooled
Median quiet eye
800ms
arm-raise → release
Clock drift
21ms
over 5 min — under one phone frame

Four stages, each one checkable

No stage is trusted because the stage before it looked confident. Every one writes files to disk that you can open, and two of them stop the run outright if the evidence is thin.

01

Record

Meta Aria Gen 2 glasses on the shooter, a phone on a tripod side-on. Clap three times, then shoot.

  • Glasses give RGB, 8-mic audio and eye gaze
  • Phone gives the side view the pipeline scores shots from
  • 1–6 GB per VRS recording
02

Sync

The three claps line up two clocks that never agreed. One global offset, ~21 ms of drift over five minutes.

  • Wall-clock overlap checked before audio is trusted
  • Per-shot windows carved from the aligned timeline
  • Worst-case error under one phone frame
03

Detect

YOLO11 pose finds the arm raise. YOLO11 detection finds the ball, the release, and whether it went in.

  • Every marker burned onto the video to judge by eye
  • Thin margins return uncertain rather than guessing
  • Wrong outcomes are correctable from the dashboard
04

Measure

Slice the gaze samples between raise and release, and measure how far they wandered.

  • Per-axis SD — which way the gaze drifted
  • Combined SD — one number to rank shots
  • BCEA — the 68% confidence ellipse area

The metric, and what it actually showed

Dispersion is the headline instead of where-on-the-hoop-they-looked for a measured reason: the gaze is uncalibrated, so it carries a fixed per-wearer bias. Variance is translation-invariant — a constant offset cancels out of it completely, and does not cancel out of absolute position.

Every analysed shot, pooled

One mark per shot, placed by the gaze dispersion measured inside its quiet-eye window. The heavy tick is each row's median.

Made Miss Undetermined 2.0° placeholder threshold
0.0 1.0 2.0 3.0 4.0 5.0 2.0° placeholder threshold Made 12 shots median 2.53° Miss 18 shots median 2.65° Unknown 3 shots median 0.84°

Gaze dispersion — combined angular SD (degrees)

The honest reading: these two rows overlap. Made shots sit at a median 2.53° and misses at 2.65° — a gap far too small to mean anything across 33 shots from one shooter. Shootbuddy's contribution so far is that the measurement is trustworthy enough to show a null result rather than a flattering one. A larger sample is the next step, not a louder claim.

Why medians, not means

With roughly ten shots per group, one mis-detected 2.5 second window at 12.8° is enough on its own to flip a made-vs-miss comparison. Medians survive that; means do not.

Windows outside 350–1800 ms are flagged as detection failures rather than reported as remarkable shots, and the run stops if too many appear.

The 2.0° threshold is a placeholder

The dashed line marks where a shot is labelled quiet. That cutoff is not calibrated — one session cannot establish a defensible one, and pretending otherwise would be the single easiest way to manufacture a result.

So it is flagged as a placeholder in the data, in the API, and in every view that renders it, all the way to this page.

Three gates that stop confident nonsense

Each of these exists because it caught a run that would otherwise have produced numbers that looked completely fine.

Gate 01

Are these the same event?

A VRS and a phone clip from two different takes once synced together with a confident-looking score of z=61 and produced a meaningless result. Every three-clap marker looks like every other three-clap marker, so audio correlation can "prove" a match that never happened. Wall-clock overlap is now checked first: two recordings that never coexisted in real time cannot be of the same event, whatever the audio says.

Gate 02

Do the claps agree with each other?

Per-shot clap scatter is measured across the session. If the individual markers disagree about the offset, the alignment is not one clock offset plus noise — it is wrong, and the run stops rather than carrying a plausible-looking error through four more stages.

Gate 03

Is this window physically possible?

A quiet-eye window of 2.5 seconds is not an extraordinary shot, it is a broken detection. Windows outside a physiologically plausible range are flagged as failures, never averaged in — and the overlay renderer burns every marker onto the video first, so the detections are judged by eye before any number is believed.

What the numbers claim — and what they don't

Published in full, because a measurement tool that hides its caveats is not a measurement tool. This table ships inside the repo and is reproduced here unedited.

Claims made by the Shootbuddy pipeline and their status
ClaimStatus
Gaze dispersion within a window Solid
Variance is immune to the uncalibrated per-wearer bias.
Gaze absolute position on the hoop Flagged
Carries a constant unknown bias — the board is drawn relative to the session's own mean fixation, not to the real rim.
Quiet-eye duration Solid
Where the overlay confirms the markers. Windows outside 350–1800 ms are treated as detection failures, not remarkable shots.
Quiet / not quiet at 2.0° Placeholder
One session cannot calibrate a defensible cutoff. Flagged as provisional everywhere it appears.
Made / miss outcome Geometric
Side-on, no net. Thin margins and detection gaps return uncertain and are correctable by hand from the dashboard.
Clock sync Solid
One global offset, ~21 ms drift over 5 minutes — under one phone frame of worst-case error.

It never leaves the machine

No cloud

Footage of a person's face and eye movements is about as personal as recorded data gets. None of it is uploaded anywhere.

No database

Every stage reads files off disk and writes files back, under data/sessions/<id>/. Delete the folder and the session is gone.

No queue, no workers

One Python process, one FastAPI server on port 8770, one React bundle. You can read the whole path end to end.

Inspectable at every step

Intermediate JSON, overlay sheets and per-shot clips are all left on disk on purpose — so a wrong number can be traced to the stage that made it.

Run it

Python 3.12 — projectaria-tools and the CV stack have no 3.14 wheels. Drop the VRS and the phone clip into input/, then:

# setup
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -r requirements.txt
brew install ffmpeg
cd web && npm install && npm run build && cd ..

# match → extract → sync → detect → quiet_eye
.venv/bin/python run_pipeline.py

# dashboard on :8770 (8000 is taken by Aria Studio)
.venv/bin/uvicorn app.main:app --reload --port 8770

The gates are not optional

run_pipeline.py stops on all three. Each one has already caught a run that otherwise produced confident, meaningless numbers. Don't route around them.