Skip to main content

Jason Slade

IIoT Director | SCADA | MQTT | Controls Engineering

Feet Per Standard Deviation: Building a Disc Golf Coach for My Dad

The pro-field comparison table, showing the player's session averages placed against both the regular tournament field and the distance-competition field

My dad got a TechDisc — a disc golf disc with an IMU in the middle of it. Throw it, and a few seconds later your phone has fifty-three fields describing what just happened: speed, spin, nose angle, hyzer angle, off-axis wobble, an estimated distance, the raw inertial trace, the disc you used, the time to the millisecond.

Fifty-three fields, and not one sentence about what to do differently.

That gap is the whole project. A number is not feedback. “Nose angle 2.4°” only becomes coaching once you know whether 2.4° is good, whether it is good for you, and whether it is the thing costing you the most distance right now. Answering those three questions turned into a couple of months of evenings, sixty-one commits, and a small pile of statistics I had to argue with before I trusted any of it.

Getting the data out

TechDisc has no documented public read API. The only thing they advertise as an API is a game-controller token for pushing throws into partner games — it does not let you read your own data back out.

Underneath, the app is Firebase. The Firestore project is throw-log, your throws live at users/{uid}/throw-summary, and each document id is the throw’s time in unix seconds. That would be easy, except App Check is enforced with reCAPTCHA: a headless script that logs in correctly with the right email and password still gets 403 PERMISSION_DENIED, because the token that satisfies App Check is only minted inside the genuine, logged-in web app.

So both exporters run inside a real browser context and reuse the token that is already there — one as a paste-into-DevTools console script with nothing to install, one as Playwright driving a real Chromium. Neither reads pixels off the screen. Both page through the structured Firestore documents and write JSON. Everything downstream is offline.

Compared to what?

Once the throws are local, the interesting question is the comparison. There are three honest answers, and the app uses all three.

Against yourself. Throws are grouped into practice sessions by a time gap — any break longer than sixty minutes starts a new session — and each session’s averages are compared to every throw you have ever recorded before it. The comparison is in standard deviations of your own baseline, not raw units, so a change only registers if it is large relative to your normal scatter. The threshold is 0.3 SD, and a finding needs at least ten throws behind it: the standard error of a session mean is σ/√n, so a 0.3 SD move of a four-throw average is noise, while the same move over thirty throws is signal.

Against absolute skill tiers. Plain Beginner-to-Pro bands for distance, speed and spin. Crude, but it works on day one, before you have any history at all.

Against the pro field. This is the one that took actual work.

TechDisc broadcasts live throw data during major events through a public “TD Live” account. I did a one-time read of that account and sliced it by event date window: 24,860 throws pulled, 18,527 of them inside a real event window, across ten catalogued events — nine with usable data. USDGC, PDGA Worlds, OTB Open, Champions Cup, and the distance competitions.

What gets stored is percentiles, never raw pro throws — p10, p25, p50, p75, p90, p95 per metric, per shot type, per field. The entire pro comparison ships inside the app as 8 KB of JSON. That mattered more than it sounds: the app had to work on my dad’s PC, and the pro comparison had to work there without an internet connection, an account, or a scraper he would have to keep alive.

Two benchmarks that are never merged

The pro data splits cleanly into two populations that measure different things: regular tournament rounds (six events, ~13.4k backhand throws) and max-effort distance competitions (three events, 399 throws). Tournament backhands have a median distance of 290 ft. Distance competition backhands median 419 ft, with p90 at 537 ft.

Blending those into one benchmark produces a number that means neither. So the report shows both, side by side, and never averages them:

The pro-field comparison table, showing the player's session averages placed against both the regular tournament field and the distance-competition field
The same session is p11 against tournament play and p1 against distance specialists. Merging the two benchmarks would have produced one number meaning neither.

Look at the distance row. Against tournament play this thrower is at p11; against distance specialists, p1. Those are two different, individually meaningful facts — a developing player near the bottom of normal tournament play is nowhere against people whose entire event is throwing as hard as they physically can. One merged percentile would have hidden that. The same session is at p50 for speed and p20 for wobble, which is the actual story: the power is unremarkable, the release is genuinely clean, the distance is not there yet.

Percentiles come from piecewise-linear interpolation between the stored percentile points, with mild anchors past p10 and p95 so values off the end of the table still map somewhere sensible instead of clamping. Spin is compared by magnitude, because forehand spin is stored negative. Bars are coloured by direction, not by value — a high percentile is green for speed and red for wobble.

What actually moves your distance

The headline model. “What should I work on to throw further?” gets answered three ways — which are deliberately not given equal weight.

  1. Pearson correlation of distance against each metric.
  2. Longest 20% versus the rest — the standardized gap in each metric between your bombs and everything else.
  3. Standardized multiple regression, distance ~ z(speed, spin, nose, wobble, hyzer).

The third is the one that matters. Speed and spin move together on almost every throw, so correlation double-counts them; the regression reports the partial effect of each metric while holding the others fixed, in feet gained per one standard deviation.

The first two methods are not independent witnesses — they both measure marginal association, so they agree by construction. Treating them as three equal votes would let the two correlated views outvote the only partialled estimate. The combined ranking weights the regression double instead. Predictors with poor coverage get dropped automatically (launch angle is usually null in TechDisc’s data, and leaving it in would silently shrink the sample to the rows where it exists).

The distance-driver table, showing correlation and partial regression coefficients for the player next to the pro field's
Partial effect of each release metric on distance, in feet per standard deviation, next to the pro field’s same model. The speed row is not what it looks like — see below.

The unit trap

Now look at the speed row in that table: +9.3 ft per SD for this thrower, +76.3 ft per SD for the pro field. Eight times worse. The report even labels it — “you convert this to distance less than pros do.”

That conclusion is wrong, and it is wrong in an instructive way.

A coefficient in feet-per-standard-deviation is only comparable across two populations if the standard deviations are comparable. They are not. The pro field’s speed SD is 10.5 mph, because it pools everyone from a 43 mph control shot to an 85 mph distance attempt. This thrower’s speed SD, recoverable from the report’s own what-if row, is about 1.3 mph — one person, one shot, one afternoon.

Convert both to the same unit and the gap evaporates:

βspeed SDft per mph
This thrower+9.3 ft/SD~1.3 mph~7.0
Pro field+76.3 ft/SD10.5 mph~7.3

He converts arm speed into distance at essentially the pro rate. What he does not have is the range — his speed barely varies, so a standard deviation of it is a small thing. The eightfold difference was a unit artifact, not a mechanical deficiency, and telling a developing player his speed doesn’t convert would have been actively bad coaching. That comparison is getting reworked into per-mph units before this feature keeps that label.

I am leaving this in the write-up rather than quietly fixing it because it is the most useful thing in the project: the model was right, the arithmetic was right, and the sentence on top of it was still false.

Does the release break down under power?

The other big claim the app can make is an interaction: does wobble cost more distance when you throw hard? That is tested three ways — a continuous speed × wobble term, the wobble-to-distance slope computed separately within low, mid and high speed terciles, and a 2,000-replicate bootstrap confidence interval on the high-power slope. The bootstrap resamples by session, not by throw, because throws in one session share a day, a wind, and a warm-up state, and a throw-level bootstrap would report a confidence interval far too narrow.

The power-band table, showing wobble's distance cost is flat across low, mid and high power bands
The wobble-under-power test coming back negative, and the report saying so instead of reaching for the more interesting story.

For this player, it came back flat: about −2 ft per SD of wobble at low power, −2 at mid, −1 at high. No breakdown under power. The report says so plainly — “wobble’s distance cost looks similar across your power bands” — rather than reaching for the more interesting story. A model that can only find effects is not a model.

Both of these claims were also re-derived and attacked by three separate analyses each, stress-testing multicollinearity, outliers and leverage, alternative binning, robust regression, and confounds. The saved verdicts record what survived and the caveats — the power-wobble gradient is robust in direction but its exact magnitude is estimator-dependent, so it gets reported as a range.

One number for form

Percentiles are demoralising when you are at p11. So there is a second score that has nothing to do with power:

The form score card: 94 out of 100, broken into nose control, clean release and repeatability, with the full formula printed underneath
Form score deliberately excludes raw power, so it stays improvable at any skill level. The whole formula is printed on the card.

Thirty percent nose control, thirty percent clean release, forty percent repeatability. The anchors come from the bundled pro distributions and every one of them is printed on the card — nothing about the number is hidden. Raw distance is deliberately excluded, which is the entire design: form score is improvable at any skill level, so the 124-foot thrower at p11 against the pro field can still be at 94 and still see it climb. Both numbers are true. Only one of them is motivating on a Tuesday.

From statistics to a next session

The report opens by checking the goal the previous report set, and closes by setting the next one, so sessions chain into a loop rather than each producing an isolated verdict. Between those, the findings are quantified and specific:

The findings list, showing the previous session's goal outcome, three new personal bests, and quantified callouts on distance, wobble, nose consistency, speed and spin
Findings are quantified against the player’s own baseline or the bundled pro tables — never a generic coaching platitude.

Everything on that list is either a number from the player’s own history or a number from the bundled pro tables. There is a scripted 15-throw assessment too — the Combine, five flat, five hyzer, five anhyzer at full effort — because free practice mixes shot types and session totals are not strictly comparable across days.

And underneath it all, the session as it actually happened:

Session detail charts: distance versus speed coloured by nose angle, a warm-up and fatigue trace, and nose and wobble distributions against baseline
The session as thrown: distance against speed coloured by nose angle, a warm-up and fatigue trace, and this session’s nose and wobble distributions over the baseline.

Shipping to someone without a terminal

None of this is worth anything if the person it was built for cannot run it. My dad is not going to pip install anything.

The TechDisc Coach desktop app on the Analyze tab, showing date-range selection, benchmark choice, and the two numbered buttons
The desktop app. Two numbered buttons, because the person it was built for is not going to open a terminal.

Double-click run.bat. It builds its own environment, fetches the sign-in browser, and opens the app. Sign in once with button ①, generate a report with button ②. The date-range selector exists for an entirely unglamorous reason: someone else threw with his disc while he was signed in, and those throws had to come out of the analysis without deleting them.

The written coaching is rule-based by default — deterministic, offline, no key, always works. There is an optional upgrade to the Nous Research inference API (hermes-4-405b by default) which receives the computed statistics only, never raw data, with instructions to use those numbers and invent nothing. If the key is missing or the call fails it falls back to the rules and prints the error. Identical stats reuse a cached answer, and there is an optional monthly token cap. The default path costs nothing and works on a plane.

What review did to it

The thing that changed this project most was not a feature. It was reviewing it from four lenses at once — engineer, competitive landscape, product manager, and end user — and writing the findings down before fixing any of them. The engineer’s list found spin-sign and exclusion bugs. The user’s list found that the first-run experience was hostile. The PM’s list killed features I wanted to build.

Every finding that survived became a commit, and the ones that were bugs became tests: 215 tests across 24 files, with past review findings encoded as regressions so they cannot come back. The statistical soundness fixes have their own file.

What it is not

This is observational, single-player data. TechDisc’s distance is a model estimate, not a tape measure. Every finding here is an association inside one person’s throws, and the pro comparison is a snapshot of nine broadcast events, not the sport.

What it is: a disc that records fifty-three numbers, and a report that turns them into one thing to work on this week.

The repository is private — it carries one person’s throw history — so this write-up is the public version of it. If any of the above is useful to you, the methods are all here: the percentile interpolation, the standardized regression, the session-clustered bootstrap, and the habit of converting to a common unit before believing a comparison.

← All posts

→ Subscribe by RSS