Ball-speed cosine correction + opt-in calculated spin - #1
Closed
johnpacino wants to merge 51 commits into
Closed
Conversation
Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 7.0.1 to 7.1.1. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/eslint-plugin-react-hooks@7.1.1/packages/eslint-plugin-react-hooks) --- updated-dependencies: - dependency-name: eslint-plugin-react-hooks dependency-version: 7.1.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [globals](https://github.com/sindresorhus/globals) from 17.5.0 to 17.6.0. - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](sindresorhus/globals@v17.5.0...v17.6.0) --- updated-dependencies: - dependency-name: globals dependency-version: 17.6.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Adds `ballistics.py` with a deterministic drag + Magnus ODE simulator (RK4, 500 Hz) and a `resolve_launch()` helper that substitutes club-typical spin when measured spin confidence is below the "high" threshold. Output is a single committed trajectory — no probabilistic range — so it composes cleanly with future simulator handoffs. Aerodynamic model uses Hill-type Cl(Sp) and linear Cd(Sp) fits consistent with Bearman & Harvey (1976) and Kensrud & Smith (2018) for dimpled balls past the drag crisis. Spin decays exponentially at 4%/s per Kiratidis & Leinweber (2018). No UI or server changes — module is standalone. Tests cover launch-resolution policy and end-to-end trajectory sanity across driver, iron, fade/draw, and launch-angle sweeps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update base drag and saturation coefficients for modern dimpled balls as measured by Kensrud & Smith (2018)
scripts/analysis/validate_ballistics.py runs the model on TrackMan inputs (physics-only check) and on OpenFlight inputs (end-to-end check), comparing each prediction to TrackMan's normalized flat carry. Air density defaults to 1.184 kg/m^3 to match TM's 77F/sea-level normalization. Per-shot CSV, per-club + per-session stats, and a scatter plot per source. scripts/analysis/sweep_ballistic_coeffs.py fits the four ballistics.py aero constants (CD_BASE, CD_SPIN_COEFF, CL_SATURATION, CL_HALF_SP) via differential-evolution + Nelder-Mead refinement against TrackMan flat carry. Optional --loso flag does leave-one-session-out cross-validation so we can see whether the fit generalizes. Both scripts accept multiple --trackman / --comparison files with matching --session-label values; shots are concatenated, tagged with their origin, and broken out per-session in the reports and scatters (club -> color, session -> marker).
server.py: new --no-ballistics CLI flag (default off, i.e. ballistics on). When set, on_shot_detected skips resolve_launch/simulate entirely and routes every non-mock shot through the table-based estimator. The table-path log message now identifies which condition triggered the fallback (no launch angle vs ballistics disabled), and the chosen mode is announced at server startup. start-kiosk.sh: parses --no-ballistics, forwards it to the server command, and logs the mode alongside the existing camera/trigger lines. tests/test_server.py: new test_carry_skips_ballistic_when_ballistics_disabled that pins the contract — when the flag is off, resolve_launch and simulate must not run even with a valid launch angle present.
…ops-sync Fix OPS trigger timing with per-shot clock sync
The K-LD7 angle radar failed on clean installs with "kld7 package not
installed" because kld7 was an optional extra that no install path
actually pulled in: setup.sh installs .[ui,analysis], `uv sync` and CI
don't install extras, and only start-kiosk.sh's error hint mentioned
.[ui,kld7].
Move kld7>=0.2.1 into base dependencies. It's a tiny pure-Python wheel
whose only requirement (pyserial) is already a base dep, and the --kld7
runtime flag still gates actual hardware use, so it can't be forgotten
by any install path again. The defensive find_spec("kld7") guards remain.
- Remove the now-redundant kld7 optional extra
- Update start-kiosk.sh hint to .[ui]
- Update stale `pip install kld7` / `uv add kld7` messages in the kld7
driver and helper scripts to point at the project install
- Add a regression test asserting kld7 is a base dependency
…arn/ui/globals-17.6.0 build(deps-dev): bump globals from 17.5.0 to 17.6.0 in /ui
…arn/ui/eslint-plugin-react-hooks-7.1.1 build(deps-dev): bump eslint-plugin-react-hooks from 7.0.1 to 7.1.1 in /ui
The script sourced .venv/bin/activate, which bakes in an absolute VIRTUAL_ENV path at creation time. Moving the project directory left activate pointing at a stale path, so openflight-server resolved to a non-existent bin and startup failed with "command not found". Resolve the environment dynamically instead: require uv, run uv sync to reconcile against uv.lock, and launch with `uv run`. A moved, stale, or missing venv now repairs itself rather than failing cryptically.
Document quick start, scripts, socket connection model, project layout, and troubleshooting in place of the default Vite boilerplate.
docs(ui): replace Vite template README with OpenFlight UI docs
…-uv-env fix(kiosk): launch server via uv run so a moved venv self-heals
Flip ballistics_enabled default to False so all shots use the legacy table estimator unless the user opts in with --ballistics (CLI) or --ballistics (start-kiosk.sh). Keeps the simulator available for A/B work while Cd/Cl coefficients are still being validated against TM. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dS/openflight into feat/ballistics-physics-model
…cs-model # Conflicts: # scripts/start-kiosk.sh
…odel Feat/ballistics physics model
…tation, cleanup old session logs
wait_for_hardware_trigger bounded TOTAL elapsed time, so a sound
trigger firing in the last ~5s of the 30s wait window had its ~46KB
rolling-buffer dump cut off mid-stream at the deadline ("Incomplete
capture (missing: Q)"), silently losing the shot. At a 30s re-arm
cadence that's roughly 1 in 6 random impacts.
Once the first byte arrives the trigger has fired and the dump is in
flight: extend the deadline to first_byte + dump_grace (8s, vs the
~4-5s a dump actually takes). The existing completeness and inactivity
exits still return as soon as the Q array closes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…meout-truncation
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.6.0 to 25.9.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.9.3 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Add openflight-cloud session uploader
…yarn/ui/types/node-25.9.3 Bump @types/node from 25.6.0 to 25.9.3 in /ui
Two opt-in launch-monitor accuracy features, both off by default. --ball-speed-cosine-correction: the OPS243 reads the radial component of a ball departing at the launch angle; correct_ball_speed() divides out cos(launch − ball elevation from radar) using the measured VLA + radar geometry (--kld7-ball-distance, new --kld7-radar-height-inches). Works with any launch-angle source. --calculated-spin: the 24 GHz return carries no usable spin line, so when the vertical launch angle was radar/camera-measured, spin_rpm is rewritten with the kinematic estimate 170·v·sin(LA)^1.2 (spin_estimate.py); the displaced radar value is kept in spin_rpm_measured. Runs after the cosine correction (the model is calibrated on true ball speed). Adds spin_source / ball_speed_raw_mph provenance to the shot payload and the kiosk flag passthroughs. Tests: test_speed_correction, test_spin_estimate, and TestApplyCalculatedSpin (radar/camera gate, model range, rewrite). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Spin Rate card shows an "estimated" subtext when spin came from the calculated model (spin_source === 'calculated') and "radar" when measured, mirroring the H. Launch card's source subtext. Adds spin_source to the Shot type (and the DisplayMode test mock). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
Closing — this targeted the fork's own |
johnpacino
force-pushed
the
feat/ball-speed-spin
branch
from
June 16, 2026 04:20
15984dd to
51d6206
Compare
johnpacino
pushed a commit
that referenced
this pull request
Jun 17, 2026
…ht#115 review #1) server._forward_shot_to_simulators guards each send with except OSError and documents it never raises into on_shot_detected. But TcpSimClient.send_raw raised RuntimeError when _sock was None, and there is a real TOCTOU window: is_connected() can return True, then _close_socket() nulls _sock before the next send acquires the lock. That RuntimeError slipped past the OSError guard into the shot thread. ConnectionError is an OSError subclass, so the existing guard now catches the raced disconnect (and send_shot's 'Raises OSError' docstring becomes accurate). Tests: bug-first -- send_raw while disconnected now raises an OSError subclass (was RuntimeError), failing before the fix. Guarantee -- _forward_shot_to_simulators swallows a connector ConnectionError, emits sim_send_failed, and still delivers to other connectors. Also reset the server-wiring fixture's sim globals at teardown so fakes don't leak into other test modules. Addresses PR open-flight#115 review comment #1 (merge blocker). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two opt-in launch-monitor accuracy features (both off by default) plus a UI provenance indicator. The shot-detection and K-LD7 angle pipeline are untouched — these consume the launch angle the existing estimator produces, so they're independent of the in-progress two-ray angle work.
Ball-speed cosine correction —
--ball-speed-cosine-correctionThe OPS243 reads the radial component of a ball departing at the launch angle, so the raw reading is compressed by
cos(launch − ball-elevation-from-radar).correct_ball_speed()divides that out using the measured VLA + radar geometry (--kld7-ball-distance, new--kld7-radar-height-inches). Works with any launch-angle source (naive / geometry / two_ray). TrackMan-validated at ~0.9 mph MAE across 7-iron / PW / driver.Calculated spin —
--calculated-spinThe 24 GHz return has no usable spin line. When the vertical launch angle was radar/camera-measured,
spin_rpmis rewritten with the kinematic estimate170·v·sin(LA)^1.2(spin_estimate.py); the displaced radar value is preserved inspin_rpm_measured. Runs after the cosine correction (the model is calibrated on true ball speed). Falls through untouched when the launch angle is a club-table estimate or outside the calibrated 2°–60° range.UI
The Spin Rate card shows an "estimated" subtext when spin came from the model, "radar" when measured — mirroring the H. Launch card's source subtext.
spin_sourceadded to the shot payload +Shottype.Testing
ruffclean,pylint10.00/10 on the new modules.build+lintclean, 13 tests pass.test_speed_correction,test_spin_estimate,TestApplyCalculatedSpin(radar/camera gate, model range, rewrite-and-preserve).🤖 Generated with Claude Code