Ballistics & Carry¶
Carry is not measured — it is simulated. OpenFlight integrates the ball's trajectory from the launch conditions it measured, accounting for drag and the Magnus force from spin.
Implemented in src/openflight/ballistics.py.
The model¶
Three forces act on the ball: gravity, aerodynamic drag opposing motion, and the Magnus lift generated by backspin.
The coefficients depend on the spin parameter \(S_p = r\omega / v\) — the ratio of surface speed to translational speed:
Drag rises linearly with spin. Lift follows a Hill-type saturating form: it approaches a ceiling as spin increases rather than growing without bound, which is what the wind-tunnel data shows.
These parametric forms are consistent with Bearman & Harvey (1976) and Kensrud & Smith (2018) for dimpled balls past the drag crisis (Re ≈ 5×10⁴–2×10⁵), which covers the full range of realistic golf shots.
Spin decays exponentially over the flight:
at roughly 4 %/s per Kiratidis & Leinweber (2018) — small per second, but it matters across a six-second flight.
Constants¶
| Constant | Value | Note |
|---|---|---|
BALL_MASS_KG |
0.04593 | USGA maximum-conforming ball, 45.93 g |
BALL_RADIUS_M |
0.02135 | 42.7 mm diameter |
AIR_DENSITY_STD |
1.225 kg/m³ | Sea level, 15 °C ISA |
CD_BASE |
0.205 | Drag at zero spin |
CD_SPIN_COEFF |
0.18 | Linear drag rise with \(S_p\) |
CL_SATURATION |
0.32 | Lift ceiling |
CL_HALF_SP |
0.15 | \(S_p\) at half the lift ceiling |
SPIN_DECAY_RATE |
0.04 /s | ≈4 %/s |
GRAVITY |
9.81 m/s² | |
DT_SECONDS |
0.002 | 500 Hz integration |
MAX_FLIGHT_SECONDS |
15.0 | Safety cap |
SAMPLE_INTERVAL_S |
0.05 | Cadence of returned trajectory points |
Why the maximum-conforming ball
Mass and radius use the USGA maximum rather than an average, so carry estimates are upper-bounded by the rules rather than by a guess about which specific ball is in play.
Integration¶
Fourth-order Runge-Kutta at 500 Hz. RK4 error is \(O(\Delta t^5)\), so at this step size the integration is effectively exact for the timescales involved; noticeably larger steps begin to shorten long drives.
The solver returns points every 50 ms rather than every step — integration still runs at the full rate, but the sampled list keeps WebSocket payloads and session logs a reasonable size.
Real shots terminate in 5–9 seconds. The 15-second cap exists so a solver instability or pathological input fails loudly instead of spinning.
Inputs and fallbacks¶
The simulator needs ball speed, launch angle, and spin. Ball speed always comes from the OPS243. The other two may be missing:
| Input | When present | When missing |
|---|---|---|
| Launch angle | IWR6843 measurement | Falls back to the legacy carry table estimator |
| Spin | OPS243, if confidence is high | Club-typical spin from TrackMan PGA Tour averages, by ClubType |
Measured spin is only trusted above SPIN_CONFIDENCE_HIGH (0.7). Below that,
the club-typical value is used — see
rolling buffer & spin detection for why.
--calculated-spin forces the kinematic estimate
\(170 \cdot v \cdot \sin(\text{LA})^{1.2}\) even when a measured value exists,
keeping the measured number in spin_rpm_measured for offline scoring.
Disabling it¶
Uses the legacy carry-table estimator for every shot. The simulator is the default; shots without a vertical launch angle fall back to the table anyway.
Related¶
- Rolling buffer & spin detection — where spin comes from
- Constants — the full constant set
- TrackMan test process — validation