Evidence

One serve, measured end to end — and the clip it came from.

A scan does this to every swing it finds. Here it is once, in full, on one clip — 61 frames at 15 fps, filmed side-on — run through the shipped pipeline: every number it produced, the two that are close enough to the line to be coin-flips, and the paths to check all of it. Nobody is using this yet, so rather than other people’s results you get one clip and the arithmetic. Every figure on this page was computed when the page was built.

  • Contact, 2,133 ms in — the bottom of the arc, which is where the app puts it.
  • The wrist, from the start of the downswing to the end of the follow-through.
  • Bones vanish under 0.3 confidence, the same threshold the app draws at: 18 of 1,037 tracked points across these 61 frames. Nothing is drawn in.

01 The swing, in five instants

What the segmenter decided happened, and when.

0.00s
Ready — the clip starts
1.47s
Takeback — the paddle starts going back
1.93s
Downswing — the arm starts forward
2.13s
Contact — the wrist is at its lowest
2.60s
Follow-through ends — the paddle has finished

Contact is the instant the wrist sits lowest, not the instant it is moving fastest. On an underhand serve the arm keeps accelerating through the strike, so the speed peak lands about 133 milliseconds late — far enough that grading against it flagged good serves as faults. There is exactly one hand-labelled contact in the repository, at 2,167 ms, and against it the wrist-low rule lands 34 ms early. That is n=1, and it is the only thing here that can adjudicate a contact rule at all — every expected snapshot is written by the code under test.

The reading

Serve: 4.2

≈3.8–4.2 on the DUPR scale

Form-based estimate, not an official DUPR rating

That band can only ever land between 2.0 and 4.5 — the whole range this estimate produces — and the map from form score to a DUPR number is a placeholder that has never been fitted against a single real rating. Read it as “your mechanics moved”, not “your rating is”.

Measured from golden-clips/serve/keypoints/real-serve-sample.json — 61 frames, 15 fps, tracking quality 0.84.

02 9 readings

Here is everything it measured on that serve.

3 of these 9 came back at 100, and 100 is not a compliment. The score scale is a straight line between two anchors and it clamps at the ends, so a 100 means the measurement left the range anyone calibrated — not that it was perfect. Those 3 are marked off scale here, with the mark shown where it actually fell.

With them excluded the way the fix intends, this same clip reads 3.8, not 4.2. The app still counts them as 100s. Fixing that is the top item on the list.

  1. Contact height off scale
    above the waist at the waist

    How high you met the ball relative to your hips.

  2. Contact out front not measured
    anchors withheld

    How far ahead of your hips you made contact.

  3. Arm extension 178° · off scale
    120° 170°

    Elbow angle at contact — straighter means more reach.

  4. Body rotation 99/100
    all arm full turn

    How much your shoulders turned through the swing.

  5. Weight transfer 60/100
    flat-footed full transfer

    How much you moved forward into the shot.

  6. Knee bend 18° · 52/100
    30°

    How much you loaded your legs before swinging.

  7. Swing tempo 0.67s · 93/100
    0.20s 0.70s

    Time from takeback to contact.

  8. Follow-through off scale
    punched full release

    How far your paddle travelled after contact.

  9. Head stability 44/100 · flagged
    head moving head still

    How still your head stayed through the swing.

The hairline across every lane is 50, the score under which a measurement becomes the thing to work on. The calibrated range is the middle of the lane; a mark out in a gutter left it.

9 measurements. 5 produced a grade. 1 was flagged.

The one it declined to grade is deliberate. The reading exists — the paddle met the ball a little behind the wrist — but the anchors that would turn it into a score were fitted when “contact” meant the wrist’s speed peak, which on a serve sits about 130 milliseconds into the follow-through. Grading against a scale calibrated on a different instant would flag late contact on every serve, including the good ones. So the value is recorded, printed, and left out of the rating.

03 Two coin-flips

Two of these 9 sit close enough to the line to be noise.

A measurement is flagged as something to work on when it scores under 50. These landed near it:

  • Knee bend scored 52, against a threshold of 50. It is not flagged, and it is close enough to the line that a slightly different clip would have flagged it.
  • Head stability scored 44, against a threshold of 50. It is flagged, and it is close enough to the line that a slightly different clip would not have flagged it.

Neither of those anchors has ever been fitted against labelled footage. That is worth saying out loud on the page that prints the scores.

04 Coverage

I’d rather grade three swings honestly than 43 badly.

7%

of the swings found in the one full game scanned end to end.

The scanner found 43 swings in ten minutes of doubles, got 20 of them through the tracking filters, and graded 3. Most of that gap is the camera, not the classifier: the player is a small figure in that clip, and the pose model genuinely cannot find a wrist at that size, so 9 windows were dropped before grading ever ran. Another 8 couldn’t be named confidently, and a swing it can’t name doesn’t get graded on a guess — a drive and a drop want opposite things, so naming one wrong tells you to do the reverse of the right thing.

A scan that grades three swings is still a finished scan, and it costs 2 credits. Below three there is no report at all — that one is a failure, and failures are free.

Coverage is the number I’m working on. When it moves it moves here, and it moves because the fixture it is computed from changed.

05 Check it

You can run this yourself.

The clip’s keypoints, the expected output, and the maths are all in the repository. Nothing about this page requires taking my word for it.

golden-clips/serve/keypoints/real-serve-sample.json
golden-clips/serve/expected/real-serve-sample.json
packages/analysis/src/features/serve.ts

The anchors each measurement is scored against live in that last file, one scoreLinear(value, bad, good) call per feature. This page reads them at build time, so if they move here, they moved there.

06 Provenance

The exact files this page was built from.

Not a policy — a check that runs on every deploy. If any of these change without the page changing with them, the build fails before it can publish a stale number.

  • apps/mobile/src/constants/theme.ts b0c77612ee9dbaf0
  • apps/mobile/src/features/shots/feature-labels.ts 51cbf135d4643b3d
  • apps/mobile/src/features/shots/labels.ts 86c547610d96558a
  • packages/coaching/src/flaw-codes.ts 7d1fabb3e36a9ee6
  • packages/analysis/src/rubric.ts 0db426bdf92698d3
  • packages/analysis/src/geometry.ts a0a2a396d47be35c
  • packages/analysis/src/features/serve.ts 0163d295f1778687
  • packages/analysis/src/features/drive.ts c73c564a466b75cf
  • packages/analysis/src/features/dink.ts de5fa69eac4141ee
  • packages/analysis/src/features/drop.ts bf3c5b83d110ed60
  • packages/analysis/src/features/common.ts 4a6c7f6a85317ccf
  • golden-clips/serve/keypoints/real-serve-sample.json 22b2c81d54c34499
  • golden-clips/serve/expected/real-serve-sample.json c22d8710038fe9a9
  • scripts/fixtures/tracks/game-10min-after-scan.json cb01036ed4a7db69
  • scripts/fixtures/tracks/game-10min-after-measure-diag.json 3162a1447dac978c
  • scripts/fixtures/tracks/doubles-76s-scan.json e9db6462cf2f5cd2
  • scripts/fixtures/tracks/doubles-76s-measure-diag-after.json 869196ff2e44a761
  • scripts/build-hero-frames.mjs 133576e31803ff83

What this page does not prove.

One clip. n=1. Two of the 9 anchors it is scored against have never been fitted to labelled footage, and there is exactly one hand-labelled contact instant in the whole repository. This page shows that the pipeline does what it says it does on this clip. It does not show that the scale is right.