From 8e9e3084a6c1ef35f68273836fc628a7c1c8afc0 Mon Sep 17 00:00:00 2001 From: Austin1 Date: Sun, 23 Aug 2026 12:46:00 -0500 Subject: [PATCH 01/10] docs: add Zensical docs site migration plan Surveys the 47 Markdown files, 3 HTML pages and 6 PDFs under docs/, proposes a seven-section information architecture, and lays out 12 numbered issues (config layout, internal plans leaking into the site, HTML conversion, README duplication, broken cross-links, workflow placement, dependency pinning, gitignore, site_url, branding, troubleshooting sprawl, rolling-buffer duplication) with options and recommendations, plus a five-phase execution plan. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W8y3SgA2agfyojFeiiz1xd --- .../2026-08-23-zensical-docs-site-plan.md | 628 ++++++++++++++++++ 1 file changed, 628 insertions(+) create mode 100644 docs/plans/2026-08-23-zensical-docs-site-plan.md diff --git a/docs/plans/2026-08-23-zensical-docs-site-plan.md b/docs/plans/2026-08-23-zensical-docs-site-plan.md new file mode 100644 index 000000000..ea491972d --- /dev/null +++ b/docs/plans/2026-08-23-zensical-docs-site-plan.md @@ -0,0 +1,628 @@ +# Zensical Docs Site — Migration Plan + +**Date:** 2026-08-23 +**Status:** Proposed — awaiting decisions on Issues 1–4 +**Scope:** Convert `docs/` into a published Zensical site with a coherent +information architecture, without losing content or breaking existing links. + +--- + +## 1. What exists today + +### 1.1 The Zensical scaffold (untracked, unmodified) + +`zensical new` was run *inside* `docs/`, producing: + +| Path | State | +|---|---| +| `docs/zensical.toml` | Default scaffold. `site_url` points at a GitHub repo URL, not a Pages URL. `nav` lists only the two starter pages. | +| `docs/docs/index.md` | Zensical's "Get started" demo page — feature showcase, zero OpenFlight content. | +| `docs/docs/markdown.md` | Zensical's "Markdown in 5min" demo page. | +| `docs/docs/assets/images/` | `openflight-logo.png`, `favicon.svg` — real assets, not yet referenced by the theme config. | +| `docs/site/` | Built output of the two demo pages. Untracked, not gitignored. | +| `docs/.cache/` | Zensical build cache. Untracked, not gitignored. | +| `docs/.github/workflows/docs.yml` | Deploy workflow in the **wrong location** — GitHub only reads `.github/workflows/` at the repo root. | + +Zensical `0.0.57` is installed globally (mise Python 3.11), **not** in the +project venv and **not** in `pyproject.toml`. `uv run zensical` currently works +only by falling through to the global binary. + +Nothing from the real documentation set has been migrated. The scaffold is a +blank slate. + +### 1.2 The real documentation set + +**47 real Markdown files** live under `docs/` (49 including the two scaffold demo +pages), plus 3 standalone HTML pages, 6 PDFs, and ~15 images/SVGs. Another 6 +Markdown files sit outside `docs/` but are linked from it. + +Grouped by what they actually are: + +**Build & setup guides (9 files, 15,602 words) — the highest-value content** + +- `PARTS.md` — bill of materials, cost summary, IWR6843 vs deprecated K-LD7 +- `sound-trigger-wiring.md` — SEN-14262 → OPS243 `HOST_INT`, R17 solder step +- `ops243-uart-migration.md` — USB → Pi GPIO UART, prerequisite for the IWR6843 +- `iwr6843/README.md` — **5,647 words**, the single largest doc: wire, flash, + mount, aim, measure geometry, verify first capture, club path +- `iwr6843/low-confidence-recovery.md` — OPS-guided vertical recovery policy +- `inclinometer/README.md` — LIS3DH tilt compensation wiring + calibration +- `battery/README.md` — battery provider architecture, UI indicator states +- `battery/geekworm.md` — X1202/X1206 operator guide +- `raspberry-pi-setup.md` — full Pi setup, auto-start, kiosk mode + +**Operating guides (7 files, ~5k words)** + +- `simulator/README.md`, `simulator/gspro.md`, `simulator/opengolfsim.md` +- `cloud-sync.md` +- `observability.md` — Grafana Cloud / Alloy log shipping +- `swing-speed-training.md` +- `rolling_buffer_spin_detection.md` + +**Theory & reference (4 files + 3 HTML)** + +- `openflight-cloud-uploader-spec.md` — wire contract, versioned API +- `spin-dechirp-replay.md` — next-gen spin estimator test bench +- `trackman-test-process.md` — validation methodology +- `yolo-performance-tuning.md` — camera experiments (explicitly non-production) +- `iwr6843_field_report_2026-07.html` — **75 KB**, self-styled dark-theme page. + Plain-language explanation of the OPS243 + IWR6843 pipeline and LCMF-v1. +- `kld7-launch-angle-explained.html` — **57 KB**, same treatment for K-LD7 +- `color_palette.html` — UI colour swatch page referenced from `CONTRIBUTING.md` + +**Deprecated K-LD7 hardware (5 files, ~10k words)** + +`kld7.md`, `kld7-troubleshooting.md`, `kld7-ball-detection-theory.md`, +`kld7-session-review.md`, plus the two K-LD7 datasheet PDFs. + +**Internal development artifacts (22 files, 39,228 words) — should not publish** + +- `docs/plans/` (11 files) — dated design + implementation plans, many + containing full source listings and test bodies +- `docs/superpowers/plans/` (5) and `docs/superpowers/specs/` (5) +- `docs/prs/` (1) — a single PR write-up + +These are engineering history. They are valuable, but as *repository* history, +not as pages on a user-facing site. + +**Docs outside `docs/` that are linked from inside it** + +- `firmware/README.md` — IWR6843 firmware build guide (linked twice from + `iwr6843/README.md`, once from root `README.md`) +- `scripts/analysis/kld7_analysis_tooling.md` (linked from `kld7.md`) +- `cad/IARC_case/README.md` (linked from `PARTS.md`) +- `CONTRIBUTING.md`, `ui/README.md`, `src/analysis/README.md` +- `archive/golf-launch-monitor-wiring-guide.md` — superseded + +**Root `README.md` (15.7 KB)** duplicates a large share of the above: overview, +what it measures, hardware table, 5-step getting started, TV display mode, swing +speed, system architecture, radar positioning, configuration, Python API, +limitations, hardware diagnostic, project structure, and a 21-item hand-maintained +documentation index. + +--- + +## 2. Proposed information architecture + +Seven top-level sections, organised by **what the reader is trying to do**, not +by which subsystem the file happens to describe. `navigation.tabs` should be +enabled — eight top-level entries in a sidebar is a wall. + +``` +Home index.md (new — what it is, what it measures, + accuracy claims, pick your path) + +Get started + Overview (new — how a shot becomes numbers) + Parts list PARTS.md + Build order (new — the sequence, with prerequisites called out) + Quick start (new — from assembled hardware to first shot) + +Build + Sound trigger wiring sound-trigger-wiring.md + OPS243 → GPIO UART ops243-uart-migration.md + IWR6843 angle radar + Overview iwr6843/index.md (split from iwr6843/README.md) + Wiring iwr6843/wiring.md + Flashing firmware iwr6843/flashing.md + Mounting & aiming iwr6843/mounting.md + Geometry & calibration iwr6843/geometry.md + First capture iwr6843/verify.md + Club path iwr6843/club-path.md + Inclinometer (LIS3DH) inclinometer/index.md + Battery (Geekworm) battery/geekworm.md + Enclosure & CAD (new — imported from cad/IARC_case/README.md) + +Setup + Raspberry Pi setup raspberry-pi-setup.md + Rolling buffer setup (extracted — the one-time flash-persist procedure, + currently duplicated in 3 places) + Auto-start & kiosk mode (extracted from raspberry-pi-setup.md) + Hardware diagnostic (new — from superpowers/specs/…hardware-diagnostic) + +Using OpenFlight + Running & modes (new — every start-kiosk.sh flag, one table) + TV display mode (extracted from README.md) + Swing speed training swing-speed-training.md + Simulator connectors + Overview simulator/index.md + GSPro simulator/gspro.md + OpenGolfSim simulator/opengolfsim.md + Cloud sync cloud-sync.md + Battery monitoring battery/index.md + Observability observability.md + +How it works + Measurement pipeline (new — architecture diagram, mermaid) + Rolling buffer & spin rolling_buffer_spin_detection.md + IWR6843 launch angle (converted from iwr6843_field_report_2026-07.html) + Ballistics & carry (new — extracted from README + ballistics.py) + Radar positioning (extracted from README.md) + +Reference + CLI flags (new) + Configuration files (new — sim.json, cloud.json, config/) + Session log schema (new — consolidated from observability.md + CLAUDE.md) + Cloud uploader contract openflight-cloud-uploader-spec.md + Constants (new) + Datasheets (index page linking the 6 PDFs) + +Troubleshooting + Symptom index (new — routes by symptom to the right guide section) + …plus the existing per-guide troubleshooting sections, left in place + +Development + Contributing CONTRIBUTING.md (imported) + Architecture (new) + Testing (extracted from CONTRIBUTING.md) + Firmware build firmware/README.md (imported) + Analysis tooling scripts/analysis/kld7_analysis_tooling.md (imported) + Spin replay bench spin-dechirp-replay.md + TrackMan test process trackman-test-process.md + Camera / YOLO yolo-performance-tuning.md + UI colour palette color_palette.html (verbatim asset) + +Legacy (K-LD7) — collapsed section, deprecation banner on every page + Overview kld7.md + Troubleshooting kld7-troubleshooting.md + Ball detection theory kld7-ball-detection-theory.md + Session review kld7-session-review.md + Launch angle explained kld7-launch-angle-explained.html (verbatim asset) + +Changelog CHANGELOG.md +``` + +Accounting for all 25 publishable files: **23 move as-is** (plus 3 imported from +outside `docs/` — firmware, analysis tooling, IARC case), **2 are split** +(`iwr6843/README.md` → 7 pages, `raspberry-pi-setup.md` → 3), **1 is converted +from HTML**, and **13 are newly authored**. The 22 internal plan/spec/PR files +leave `docs/` entirely (Issue 2). + +--- + +## Issue 1 — `docs_dir` layout: the scaffold nests `docs/docs/` + +**Problem.** `docs/zensical.toml` uses the default `docs_dir = "docs"`, resolved +relative to the config file. Content root is therefore `docs/docs/`. Every one of +the ~40 existing relative links (`docs/PARTS.md` from the README, +`../sound-trigger-wiring.md` from `iwr6843/README.md`) assumes content lives at +`docs/`, and `docs/docs/` is an awkward path to type, link, and explain. + +`config.py:390` confirms `docs_dir` and `site_dir` are both configurable, both +resolved relative to the project root (the directory holding `zensical.toml`), +and both validated to be distinct and inside the project root. + +**Option 1A — Move `zensical.toml` to the repo root, `docs_dir = "docs"`.** +Delete `docs/docs/`, keeping only its `assets/images/` (move to `docs/assets/`). +Content root becomes `docs/`. `site_dir = "site"` at the repo root. + +- Effort: 20 minutes. +- Risk: low. +- Impact: **every existing relative link and every `docs/…` README link keeps + working unchanged.** `edit_uri = "edit/main/docs/"` becomes correct. The + workflow's `zensical build --clean` from the repo root works with no + `working-directory` hack. +- Maintenance: config sits with `pyproject.toml` / `Makefile` where a + contributor expects it. + +**Option 1B — Keep `docs/zensical.toml`, set `docs_dir = "."`.** + +- Effort: 5 minutes. +- Risk: **high.** `site_dir` would then be nested inside `docs_dir`, and the + build cache in `docs/.cache/` sits inside the content root. Zensical only + validates that the two paths differ, not that they do not nest. +- Recommend against. + +**Option 1C — Move all content into `docs/docs/`.** + +- Effort: 2 hours plus link rewriting. +- Risk: medium — breaks every inbound link from the README, `CONTRIBUTING.md`, + and any external bookmark or GitHub permalink. +- Recommend against. + +> **Recommendation: 1A.** It is the only option that preserves the existing link +> graph for free, and it puts the config where the rest of the project's tooling +> config lives. Nothing else is close. + +--- + +## Issue 2 — 22 internal plan/spec documents would be published + +**Problem.** With `docs_dir = "docs"`, everything under `docs/plans/`, +`docs/superpowers/`, and `docs/prs/` builds into the site. That is 39,228 words of +dated implementation plans containing full source listings and test bodies — +**53% of the site's word count and 47% of its pages** (39,228 of 73,743 words; +22 of 47 files), all of it noise for anyone trying to build or run a launch +monitor, and all of it indexed by search. + +Zensical exposes no `exclude_docs` / `not_in_nav` key (verified against +`config.py`), so leaving these files in the content root and omitting them from +`nav` does **not** stop them being built and indexed — it only orphans them. + +**Option 2A — Move to a repo-root `design/` directory.** +`docs/plans/` → `design/plans/`, `docs/superpowers/` → `design/`, +`docs/prs/` → `design/prs/`. + +- Effort: 30 minutes (`git mv`, plus fixing ~4 inbound links). +- Risk: low. These files have almost no inbound links from published docs. +- Impact: content root contains only publishable pages. History preserved and + browsable on GitHub. A single "Design notes" reference page in the site can + link to `design/` on GitHub for anyone who wants the archaeology. +- Maintenance: creates an obvious home for future design docs, and a clear rule: + *`docs/` is published, `design/` is not.* + +**Option 2B — Publish them under a collapsed "Archive" section.** + +- Effort: 1 hour (needs a deprecation banner on each). +- Risk: low technically, high for signal-to-noise. Search results for "spin" + would surface a 2026-04 implementation plan above the operating guide. +- Do this only if the design history is considered part of the public value + proposition of the project. + +**Option 2C — Do nothing; leave them orphaned in the content root.** + +- Effort: zero. +- Risk: medium. Orphan pages are still built, still in `sitemap.xml`, still in + `search.json`, still indexed by Google. Worst of both worlds. +- Recommend against. + +> **Recommendation: 2A.** The DRY/signal argument is decisive: these documents +> describe work that has already shipped, and the shipped behaviour is documented +> in the operating guides. Keeping two descriptions of the same subsystem — +> one current, one a snapshot from five months ago — is exactly the drift the +> existing `2026-08-09-documentation-audit.md` plan was written to fix. + +--- + +## Issue 3 — Three self-styled HTML pages + +**Problem.** `iwr6843_field_report_2026-07.html` (75 KB), +`kld7-launch-angle-explained.html` (57 KB), and `color_palette.html` (8.7 KB) +each ship their own ` + + + + + +floor / turf + + +radar + + +boresight — radar aim ≈ 12.5° + + +tee + + + + + +frame 1 · 25 ms +frame 2 · 54 ms + + + +launch angle ≈ 16° + + +net / screen +10 ft from ball +15 ft from radar +radar setting: 5 m + + +16.4 ft — range wraps here + + + + + + + +5 ft +10 ft + diff --git a/docs/assets/legacy/kld7-angle-02.svg b/docs/assets/legacy/kld7-angle-02.svg new file mode 100644 index 000000000..fae0ee58d --- /dev/null +++ b/docs/assets/legacy/kld7-angle-02.svg @@ -0,0 +1,75 @@ + + + + + + +range (ft) +milliseconds after impact +5 +10 +15 +0 +20 +40 +60 + +16.4 ft — range wraps above here + + + +frame 1 · ~29 ms +frame 2 · ~29 ms +↑ frames touch + + + + +tee ≈ 5 ft = impact (t=0) +fit + run back to tee + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +range — ~13 samples / frame +angle — one value / frame + diff --git a/docs/assets/legacy/kld7-angle-03.svg b/docs/assets/legacy/kld7-angle-03.svg new file mode 100644 index 000000000..6825f3464 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-03.svg @@ -0,0 +1,52 @@ + + + + + +Best case — 2 good frames + +clean-view window (ball clear of the tee, not yet at the net) + + +impact + + +25 ms +54 ms + +29 ms apart + +→ Tier 1 ✓ + +Common case — a frame fires at contact + + + +impact + + + +3 ms +32 ms +61 ms +wasted · ball at tee +too late · past net + +→ Tier 2 · 1 frame + +Same radar, two different shots — the only difference is where impact happened to fall in the 29 ms frame clock. + diff --git a/docs/assets/legacy/kld7-angle-04.svg b/docs/assets/legacy/kld7-angle-04.svg new file mode 100644 index 000000000..b1e9d0912 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-04.svg @@ -0,0 +1,37 @@ + + + + + + +TRUE range + +016.4 ft23 ft + + + +real ball + + + + +what radar REPORTS + +"6.6 ft" + + + diff --git a/docs/assets/legacy/kld7-angle-05.svg b/docs/assets/legacy/kld7-angle-05.svg new file mode 100644 index 000000000..e3621f6dc --- /dev/null +++ b/docs/assets/legacy/kld7-angle-05.svg @@ -0,0 +1,47 @@ + + + + + + +floor (mirror) + + +radar + + +real ball + + +"image" ball (reflection) + + +direct ray + + + + +reflected ray (bounces off floor) + + + + + +θ ball (up) + +θ image (down) + diff --git a/docs/assets/legacy/kld7-angle-06.svg b/docs/assets/legacy/kld7-angle-06.svg new file mode 100644 index 000000000..5446c24c4 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-06.svg @@ -0,0 +1,31 @@ + + + + + + +tee + + +true 17° + + +measured 9° + + +suppression gap + diff --git a/docs/assets/legacy/kld7-angle-07.svg b/docs/assets/legacy/kld7-angle-07.svg new file mode 100644 index 000000000..7b702ccb1 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-07.svg @@ -0,0 +1,40 @@ + + + + + +what the raw radar gives + + + +one blended angle + + +two-ray fit + + + +after separation + + + +ball + +image + +maxsep + diff --git a/docs/assets/legacy/kld7-angle-08.svg b/docs/assets/legacy/kld7-angle-08.svg new file mode 100644 index 000000000..acc063b81 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-08.svg @@ -0,0 +1,54 @@ + + + + + + + +INDOOR · hard floor + +smooth → mirror-like (specular) + + + + + + + + + +sharp image +maxsep ≈ 11° ✓ separable + +OUTDOOR · grass + +rough → scattered (diffuse) + + + + + + + + + + + + +smeared +maxsep ≈ 2.8° ✗ collapses + diff --git a/docs/assets/legacy/kld7-angle-09.svg b/docs/assets/legacy/kld7-angle-09.svg new file mode 100644 index 000000000..71c2d8c0a --- /dev/null +++ b/docs/assets/legacy/kld7-angle-09.svg @@ -0,0 +1,39 @@ + + + + + + + +tee (anchor) + + + +frame 1 · 25 ms +frame 2 · 54 ms + + + +floor image (far below → big maxsep) + + +line through tee + 2 frames += launch angle + + +maxsep + diff --git a/docs/assets/legacy/kld7-angle-10.svg b/docs/assets/legacy/kld7-angle-10.svg new file mode 100644 index 000000000..652d18817 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-10.svg @@ -0,0 +1,42 @@ + + + + + +the boost + + + +measured (suppressed) + +after +boost ≈ tour avg + ++4° + + + +the honest catch + + + + +same low reading… + + +…both boosted here +a suppressed 17° and a genuinely-thin 9°look identical to the radar + diff --git a/docs/assets/legacy/kld7-angle-11.svg b/docs/assets/legacy/kld7-angle-11.svg new file mode 100644 index 000000000..7f333ba01 --- /dev/null +++ b/docs/assets/legacy/kld7-angle-11.svg @@ -0,0 +1,57 @@ + + + + + + + + + + +two-ray estimate +ball elevation per frame + + + +Tier-1 gate? +nval≥2 · maxsep≥9 +maxel ok · pos fit + + +yes + +TIER 1 · 0.85 +"measured" + + +no + +reads suppressed? +maxel < 0.43 × tour + + +yes + +TIER 2 · 0.35 ++ boost → tour avg + + +no + +TIER 2 · 0.65 +as-measured + diff --git a/docs/color_palette.html b/docs/color_palette.html deleted file mode 100644 index 1029d0e7a..000000000 --- a/docs/color_palette.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - OpenFlight Color Palette - - - -
-
OpenFlight Palette
- - - -
-
-
- Deep Background - #0A0A0F - --color-bg-deep -
-
- -
-
-
- Card Background - #12121A - --color-bg-card -
-
- -
-
-
- Elevated - #1A1A24 - --color-bg-elevated -
-
- -
-
-
- Hover State - #222230 - --color-bg-hover -
-
- -
- - -
-
-
- Gold - #D4AF37 - --color-gold -
-
- -
-
-
- Gold Bright - #F4CF47 - --color-gold-bright -
-
- -
-
-
- Gold Dim - #A68B2A - --color-gold-dim -
-
- -
- - -
-
-
- Cream - #F5F0E6 - --color-cream -
-
- -
-
-
- Cream Dim - rgba(245, 240, 230, 0.7) - --color-cream-dim -
-
- -
-
-
- Cream Muted - rgba(245, 240, 230, 0.5) - --color-cream-muted -
-
- -
- - -
-
-
- Success - #4ADE80 - --color-success -
-
- -
-
-
- Info - #60A5FA - --color-info -
-
- -
-
-
- Warning - #FBBF24 - --color-warning -
-
- -
-
-
- Danger - #F87171 - --color-danger -
-
- -
- - diff --git a/docs/development/color-palette.md b/docs/development/color-palette.md new file mode 100644 index 000000000..2fd1c8ab0 --- /dev/null +++ b/docs/development/color-palette.md @@ -0,0 +1,56 @@ +--- +icon: lucide/palette +--- + +# UI Colour Palette + +The canonical OpenFlight interface palette. Use these tokens when adding or +changing UI, rather than introducing new values. + +Referenced from +[`CONTRIBUTING.md`](https://github.com/jewbetcha/openflight/blob/main/CONTRIBUTING.md). + + +## Backgrounds + +| | Name | Hex | CSS variable | +| --- | --- | --- | --- | +| | Deep Background | `#0A0A0F` | `--color-bg-deep` | +| | Card Background | `#12121A` | `--color-bg-card` | +| | Elevated | `#1A1A24` | `--color-bg-elevated` | +| | Hover State | `#222230` | `--color-bg-hover` | + +## Gold (Primary) + +| | Name | Hex | CSS variable | +| --- | --- | --- | --- | +| | Gold | `#D4AF37` | `--color-gold` | +| | Gold Bright | `#F4CF47` | `--color-gold-bright` | +| | Gold Dim | `#A68B2A` | `--color-gold-dim` | + +## Cream (Text) + +| | Name | Hex | CSS variable | +| --- | --- | --- | --- | +| | Cream | `#F5F0E6` | `--color-cream` | +| | Cream Dim | `rgba(245, 240, 230, 0.7)` | `--color-cream-dim` | +| | Cream Muted | `rgba(245, 240, 230, 0.5)` | `--color-cream-muted` | + +## Accents + +| | Name | Hex | CSS variable | +| --- | --- | --- | --- | +| | Success | `#4ADE80` | `--color-success` | +| | Info | `#60A5FA` | `--color-info` | +| | Warning | `#FBBF24` | `--color-warning` | +| | Danger | `#F87171` | `--color-danger` | + +## Using these + +The same palette drives the docs site — see +`docs/stylesheets/extra.css`, where the gold, cream, and near-black values +are mapped onto the theme's tokens. + +When adding UI, prefer an existing token over a new hex value. If a genuinely +new colour is needed, add it here in the same commit so this page stays the +single reference. diff --git a/docs/development/firmware.md b/docs/development/firmware.md index 300a47c35..474edcc6a 100644 --- a/docs/development/firmware.md +++ b/docs/development/firmware.md @@ -494,7 +494,7 @@ Also check: ## Historical Context -The [IWR6843 field report](../iwr6843_field_report_2026-07.html) explains +The [IWR6843 field report](../how-it-works/launch-angle.md) explains why the project moved capture into on-chip L3 and how the estimator evolved. The implementation has since advanced from full raw ADC rings to HWA-generated, dynamically windowed complex range snapshots; this README is the authoritative diff --git a/docs/how-it-works/index.md b/docs/how-it-works/index.md index 3873958a4..b8a4d1b0f 100644 --- a/docs/how-it-works/index.md +++ b/docs/how-it-works/index.md @@ -18,7 +18,7 @@ each stage. What is inside a capture, how ball and club speed are separated, and why spin is still experimental. -- :material-angle-acute: **[Launch angle](../iwr6843_field_report_2026-07.html)** +- :material-angle-acute: **[Launch angle](launch-angle.md)** LCMF-v1 over the IWR6843 raw radar cube, and the July 2026 TrackMan baseline. diff --git a/docs/how-it-works/launch-angle.md b/docs/how-it-works/launch-angle.md new file mode 100644 index 000000000..f7893e2ee --- /dev/null +++ b/docs/how-it-works/launch-angle.md @@ -0,0 +1,531 @@ +--- +icon: lucide/angle +--- + +# Launch Angle + +*Field report — updated 22 July 2026.* + +**How we measure launch angle with a 60 GHz radar.** A plain-language guide to +the OPS243 + TI IWR6843 pipeline, the late-flight algorithm we call LCMF-v1, +what three TrackMan sessions taught us, and how we plan to improve coverage +without hiding quality. + +| | | +| --- | --- | +| Angle sensor | **TI IWR6843** | +| Speed sensor | **OPS243** | +| Estimator | **LCMF-v1** | +| Validation | **Three indoor TrackMan sessions** | + +**Explain it like I’m five** + +## We watch the ball several times, keep the clearest pictures, and ask five judges where it is going. + +The OPS radar tells us how fast the ball is moving. The TI radar takes a short movie made of radio echoes. We find the little streak that moves away from the tee, keep the clearest moments from each frame, and pay extra attention to the later part of the flight because the ball is farther from the club, golfer, and impact mess. + +Five slightly different physics models each estimate launch angle. We average their answers and use the measured mount geometry directly. If the radar movie is strong, the UI shows the measured radar angle. If the movie is weaker but still plausible, we want to show it as a lower-confidence two-dot radar read. If the radar cannot honestly follow the ball, the UI labels a normal club-based estimate instead. + +!!! tip "The important idea" + + Many looks + multiple models + honest confidence, never a hidden adjustment that quietly fits one golfer or one club. + +**Where we are** + +## The short version + +| Value | Meaning | +| --- | --- | +| **0.83°** | combined Iron/Wedge MAE across two indoor TrackMan validation sessions | +| **87.4%** | strict LCMF-v1 coverage on Iron/Wedge shots, 76 of 87 | +| **0.67°** | p50 absolute error; half of covered Iron/Wedge shots were inside this | +| **-0.04°** | bias on covered Iron/Wedge shots, effectively centered | + +For Iron/Wedge shots, the current pipeline is inside the 1° target on the shots where the radar has enough clean evidence. Driver and Mis Hits are tracked separately because they expose different engineering problems. The next job is widening coverage and hardening setup inputs so the same result travels to different builders, mats, rooms, and ranges. + +**State the problem** + +## We need ball and club direction in a brutally short window + +OpenFlight has to measure vertical launch, horizontal launch, and eventually useful club-delivery signals while the ball is only a few feet from the radar. Indoors, a fast driver can hit a net or screen tens of milliseconds after impact. That leaves very little clean flight, and the earliest echoes are exactly where the club, hands, tee, ball, floor reflection, and impact noise overlap. + +The K-LD7 taught us the core lesson: one transmitter, a slower frame cadence, coarse range separation, and one or two useful post-impact looks were not enough to consistently separate the real ball path from multipath and blind-zone behavior. It could look good on selected shots, but it did not create a robust 1° path against TrackMan, the gold-standard source of truth for launch-monitor validation. + +!!! note + + **The product problem is not just “detect a ball.”** It is detecting the right moving echo, proving it is the ball, modeling the ground-reflected copy, and reporting confidence honestly when the evidence is thin. + +**Why this radar** + +## The IWR6843 gives us enough raw evidence to model the mess + +We selected TI’s IWR6843 because it can capture coherent complex radar data across multiple receive channels at a much faster cadence than the K-LD7 setup. The IWR6843LEVM board was the practical evaluation platform: available hardware, known antenna geometry, TI tooling, and enough on-chip L3 memory to hold a compact radar cube from the shot. + +We created and uploaded custom firmware that turns the TI board into a short “radar movie” recorder. Instead of asking the radar to make a decision live, the board saves 12 tightly spaced snapshots of the ball leaving the tee. Each snapshot keeps detailed antenna information, so our software can later follow the ball moving away and separate it from the floor reflection, club, and impact noise. + +| Capability | Why it matters | Current use | +| --- | --- | --- | +| Fast frame cadence | More looks before the net or screen contaminates the track. | 12 snapshots over roughly 72 ms in the current custom firmware. | +| Fine range bins | Direct and floor-reflected paths can separate in range as the ball leaves the tee. | 3.2 GHz sweep, about 4.7 cm bins. | +| Complex antenna channels | Phase across the array carries angle information even when amplitude is messy. | Eight vertical virtual channels from TX1 + TX3 and four RX. | +| L3 rolling buffer | We can keep raw shot evidence and improve offline without reflashing for every idea. | 786,452-byte dump per trigger, drained by the Pi. | + +**OPS + IWR6843** + +## Two radars, each doing the job it is best at + +The one-chip goal was useful while learning the TI sensor, but it is no longer the product direction. OPS speed has been highly consistent and does not need to be replaced. The TI board is now focused on the measurements its antenna array can add: vertical launch angle today, and eventually aim direction and club path. + +### OPS243 + +Measures ball and club radial speed from its rolling buffer. This remains the speed authority. + +### TI IWR6843 + +Stores a 72 ms coherent radar movie across eight virtual vertical antenna channels. + +### OpenFlight UI + +Combines OPS speed with TI launch angle. If TI has no read, the shot still appears with an estimated angle. + +### Why the TI chip changed the problem + +The K-LD7 could usually see only one or two useful frames before the indoor net stopped the ball. It also had coarse range resolution and only a two-element angle view, so a clean ball echo and a floor reflection could blend into one believable but wrong angle. + +The custom firmware saves a compact, high-detail radar movie: 12 snapshots of the ball leaving the tee, spaced about 6 ms apart. Each snapshot uses multiple antenna views and fine distance slices, which gives the software enough evidence to model the floor reflection instead of pretending it is not there. + +!!! note + + **The rolling buffer is the enabling trick.** This is similar to how OPS keeps a rolling speed buffer: the radar is always recording, and impact tells the system which recent slice matters. A sound trigger connected to the Pi freezes the TI radar movie after impact, preserving the last 72 ms of raw antenna data. The Pi then drains the 786,452-byte dump over UART in about 7.6 seconds. We accept the delay because we keep the raw evidence for every shot. + +![Timeline showing a twelve-frame IWR6843 rolling-buffer capture with impact clutter, early ball frames, cleaner late-flight frames, and a net boundary.](../assets/iwr6843-rolling-buffer-timeline.svg) + +*Rolling buffer mental model.* The radar is not trying to decide launch angle from one echo. It stores a short radio movie, then LCMF looks for the portion where the ball has separated from the impact mess but has not yet reached the net or screen. + +**LCMF-v1** + +## What the algorithm actually does + +**LCMF** means **Late-Flight Complex Multipath Fusion**. “Late-flight” means it favors the cleaner second half of the captured ball flight. “Complex” means it keeps both amplitude and phase from every antenna. “Multipath” means direct and floor-reflected echoes are modeled together. “Fusion” means no single model gets to decide the answer. + +### Freeze both buffers + +The same impact edge timestamps the OPS shot and starts the TI dump. Matching is normally within a few milliseconds. + +### Find the outward streak + +Static clutter is removed. The tracker follows a target moving outward through range over time rather than trusting aliased Doppler speed. + +### Balance the frames + +Keep snapshots with strength score ≥ 8 and range ≤ 4.7 m, then retain at most the strongest four from each frame. One noisy frame cannot dominate. + +### Use OPS speed as the guide + +The independently measured OPS ball speed defines the candidate trajectory. TI’s local range-walk velocity handles the small timing correction between transmitters. + +### Ask five physics models + +Two models compare the eight antenna channels across all balanced snapshots. Three inspect the direct and reflected range structure in the chronological late half. + +### Fuse and report + +Each model receives exactly 20% weight. Their mean is the LCMF angle. The production path favors measured setup geometry and the same estimator rules for every club. + +| Model family | Plain-language question | Data used | +| --- | --- | --- | +| Two channel models | Which launch trajectory best explains the phase pattern across the antenna array when direct and floor paths are allowed? | All balanced snapshots | +| Three fast-time models | Which trajectory best explains the small range separation and mixture of direct and reflected echoes around the tracked ball? | Chronological second half | +| Equal fusion | What answer survives five different assumptions instead of winning one hand-picked model? | 20% per component | + +**Real captured shots** + +## Which frames are selected, and why late flight helps + +The gray marks below are usable snapshots along three real TrackMan-paired ball tracks. Orange circles are the strongest four retained from each frame. Teal dots are the chronological late half used by the three fast-time models. Frame numbers wrap because the radar memory is a ring; the horizontal time axis is the true order. + +![Three TrackMan-paired shots showing tracked radar snapshots, strongest four snapshots selected per frame, and the late half used by LCMF fast-time models.](../assets/iwr6843-lcmf-frame-selection.png) + +*Actual July 14 captures.* The driver has only three clean frames and is therefore the hardest case. The 7-iron and 9-iron offer more looks across the flight. Late snapshots are not automatically “correct”; they are simply less contaminated by impact, club, hands, and tee while providing more direct-versus-ground path separation. + +This selection is deliberately boring: no club-specific timing window, no TrackMan input, and no hand-picked frame number. The same strength, range, per-frame balancing, and chronological-half rules run on every shot. + +**Setup calibration** + +## The radar is accurate only if the setup geometry is honest + +The biggest lesson from the first two validation sessions is simple: the algorithm can follow the ball, but it needs the real-world setup described correctly. Mount tilt, radar height, tee distance, ball height, mat height, and net distance all affect where the radar expects the direct and floor-reflected echoes to appear. + +When those inputs are right, the same LCMF-v1 estimator produces a centered result across two indoor TrackMan sessions without per-club tuning. When those inputs are wrong, the error can look like a radar problem even though the underlying ball track is still present. + +| Setup input | Why it matters | Product plan | +| --- | --- | --- | +| Mount tilt | Defines how the antenna frame maps into the golfer's launch frame. | Measured setting first; later add an on-rig level sensor. | +| Tee distance | Changes the expected direct/reflected path geometry during the first few feet of flight. | Support measured distance and radar-assisted setup warnings. | +| Mat and ball height | An elevated mat changes the ball height relative to the radar and the floor reflection. | Store ball height and mat/surface height separately. | +| Net or screen distance | Defines how much clean late flight exists before impact with the screen or net. | Log it per session and use it when selecting late frames. | + +### What remains true + +- learnedThe estimator is extracting stable ball-angle information from the TI radar movie. +- not solvedWe still need to make these setup measurements easy enough for normal builders, not just people who lived inside the test sessions. + +**TrackMan validation** + +## Two TrackMan sessions are the current accuracy baseline + +The headline below combines two indoor TrackMan validation sessions. Driver and Mis Hits are separated so the main number describes Iron/Wedge launch-angle performance rather than hiding known edge cases. + +| Group | Shots | Covered | Coverage | MAE | p50 | p75 | p90 | Bias | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Iron/Wedge headline | 87 | 76 | **87.4%** | **0.83°** | 0.67° | 1.20° | 1.80° | -0.04° | +| First validation session Iron/Wedge | 41 | 39 | 95.1% | **0.83°** | 0.78° | 1.25° | 1.81° | -0.10° | +| Second validation session Iron/Wedge | 46 | 37 | 80.4% | **0.84°** | 0.59° | 1.19° | 1.70° | +0.02° | + +!!! note + + **How to read absolute error:** MAE is the average distance from TrackMan, ignoring sign. p50 means half the covered shots were closer than that error. Bias keeps the sign and tells us whether the whole group is systematically high or low. + +### Current 18-frame firmware confirmation + +A third indoor TrackMan session on July 22 tested the production firmware in this report: 3 TX, 12 loops, 18 frames, 4 ms spacing, and moving 53-bin windows. Twenty shots had matched OpenFlight and TI captures and therefore formed the valid radar denominator; five additional TrackMan swings had no corresponding OpenFlight capture and were not counted as estimator misses. + +Using the physically measured 12.4° mount geometry, the matched group produced approximately **0.68° launch-angle MAE**. An earlier block in the same session independently suggested about 12.3°; applying that geometry to the later good-shot block produced approximately 0.47° MAE. That temporal result is encouraging, but it remains diagnostic rather than the headline because the tilt candidate was inferred inside the same session. The conservative conclusion is that denser, cropped firmware preserved sub-1° vertical accuracy while adding more frames and the third transmitter. + +### Iron/Wedge breakdown by club + +The 9-iron table excludes a same-day experimental transmitter-order test from the headline. That experiment is useful firmware evidence, but it should not be mixed into the normal production score. + +| Club | Good shots | Covered | Coverage | MAE | p50 | p75 | p90 | Bias | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Sand wedge | 17 | 15 | 88.2% | **0.67°** | 0.46° | 1.06° | 1.58° | -0.22° | +| 9-iron | 27 | 25 | 92.6% | **0.89°** | 0.81° | 1.18° | 1.73° | +0.24° | +| 7-iron | 21 | 18 | 85.7% | **0.91°** | 0.49° | 1.15° | 1.88° | -0.06° | +| 5-iron | 22 | 18 | 81.8% | **0.82°** | 0.69° | 1.31° | 1.84° | -0.25° | + +### Driver separated + +| Group | Shots | Covered | Coverage | MAE | p50 | p75 | p90 | Bias | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Driver | 22 | 18 | 81.8% | **3.55°** | 1.31° | 1.82° | 15.57° | +3.39° | +| First validation session driver | 9 | 5 | 55.6% | **1.58°** | 1.37° | 1.85° | 2.77° | +1.08° | +| Second validation session driver | 13 | 13 | 100.0% | **4.31°** | 1.21° | 1.71° | 15.68° | +4.27° | + +!!! note + + **Driver is not the same failure as irons.** The second-session driver misses were mostly false acceptance of slow/ghost tracks. Raw replay showed the real fast ball in the frames, so the immediate fix is an OPS-vs-TI speed gate and a low-confidence fast-track recovery path. + +### Mis Hits separated + +| Group | Shots | Covered | Coverage | MAE | p50 | p75 | p90 | Bias | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Mis Hits | 19 | 13 | 68.4% | **2.20°** | 1.16° | 3.07° | 5.10° | -0.54° | +| First validation session Mis Hits | 8 | 7 | 87.5% | **2.40°** | 1.29° | 3.81° | 4.83° | -0.15° | +| Second validation session Mis Hits | 11 | 6 | 54.5% | **1.98°** | 0.78° | 1.43° | 4.54° | -1.00° | +| Skulls / very low launch | 5 | 5 | 100.0% | **6.00°** | 2.07° | 3.51° | 15.47° | +4.31° | + +These groups are still product-critical because real golfers hit them. They are separated here because they require different engineering: TX2 aim for directional Mis Hits, low-launch/impact-clutter logic, and OPS speed agreement for driver. + +**Next steps** + +## Increase coverage, add club data, and harden the setup inputs + +The strict LCMF-v1 gate is doing the right thing for accuracy, but it leaves some real ball flights unreported. The first validation session already had strong strict coverage after the setup geometry was cleaned up. The second session showed the more practical product problem: a relaxed pass can recover no-reads, but those reads should enter the UI as measured lower-confidence angles rather than being mixed into the high-confidence lane. + +**RMS** is a “how messy was the fit?” score. Lower RMS means the radar snapshots line up neatly with one clean ball path. Higher RMS means the ball is probably there, but the evidence is noisier, weaker, or more mixed with reflections. Relaxing the RMS limit lets us accept more of those imperfect radar tracks, which increases coverage, but it also increases the chance that a recovered angle is a little farther from TrackMan. + +The product answer should not be “lower the bar and call everything high confidence.” The product answer should be a second lane: **measured, but lower confidence**. + +| Mode | Iron/Wedge coverage | MAE | p50 | p75 | p90 | New recovered reads | Recommended UI | +| --- | --- | --- | --- | --- | --- | --- | --- | +| Combined strict LCMF-v1 | **76 / 87 · 87.4%** | **0.83°** | 0.67° | 1.20° | 1.80° | 0 | 3 dots | +| First-session strict | **39 / 41 · 95.1%** | **0.83°** | 0.78° | 1.25° | 1.81° | 0 | 3 dots | +| Second-session strict | **37 / 46 · 80.4%** | **0.84°** | 0.59° | 1.19° | 1.70° | 0 | 3 dots | +| Second-session relaxed RMS ≤ 0.58 | **42 / 49 · 85.7%** | **1.00°** | 0.71° | 1.32° | 2.06° | 5 | 2 dots | +| Second-session relaxed RMS ≤ 0.70 | **45 / 49 · 91.8%** | **1.09°** | 0.78° | 1.56° | 2.50° | 8 | 2 dots, lab-only first | + +### Recommended confidence contract + +1. **Strict pass:** if normal LCMF-v1 accepts, show the radar launch angle as the primary measured value with full confidence. +2. **Relaxed pass:** if strict LCMF no-reads but a relaxed replay passes RMS, snapshot, frame, component-spread, and OPS-speed checks, show the radar angle with a two-dot confidence marker. +3. **Estimate:** if both radar passes fail, show the club/speed estimate exactly as we do today. + +!!! note + + **Why this is honest:** a two-dot radar read is still better than hiding useful evidence, but it tells the golfer and the engineering team that the ball track was recovered under looser rules. That preserves trust while improving coverage. + +### Driver-specific guardrail + +Driver needs an additional speed sanity check before any confidence badge. The worst driver misses were accepted TI tracks around 55–57 mph while OPS measured roughly 152–158 mph. Raw-frame replay showed the fast ball was present, so the immediate fix is to withhold any TI angle when its tracked speed is far below OPS speed, then optionally try an OPS-guided fast-track recovery as a low-confidence read. + +| Driver policy | Why | Expected effect | +| --- | --- | --- | +| Reject TI track speed below about 65–70% of OPS ball speed | Catches obvious slow ghost tracks before they reach the UI. | Turns bad measured angles into estimates instead of false confidence. | +| Try OPS-guided fast-track recovery after rejection | Offline replay recovered 3 of 4 bad driver shots with about 2.6° MAE. | Potential two-dot driver reads, but needs more truth data before shipping as normal confidence. | + +### Club data and setup hardening + +- club**Keep club speed with OPS, but start collecting club-delivery evidence.** A split pre-impact/post-impact firmware mode may expose attack angle and club path research signals without pretending the TI board is the club-speed authority. +- aim**Add TX2 for horizontal launch and shank classification.** Far-right shots should eventually be recognized as far-right shots, not forced through a purely vertical interpretation. +- inputs**Harden tee distance, mount tilt, radar height, ball height, mat height, and net distance.** The app should support measured settings, defaults, and radar sanity checks that warn when the actual session appears to drift. + +**DIY setup variables** + +## Ball placement is a product variable, not just a measurement chore + +Commercial radar systems usually solve tee placement with a prescribed setup window and alignment aids. Photometric systems solve it by forcing the ball into a camera-observed hitting zone. OpenFlight sits in the DIY middle: we want the accuracy of a measured geometry, but builders may move between a marked home mat, an unmarked simulator bay, and a range mat where the ball can drift shot to shot. + +!!! note + + **July 17 replay result:** when ball placement is disciplined, fixed tee distance is best. When placement wanders more than about 6–9 inches, radar-computed or blended tee distance starts beating a stale fixed setting. + +### How sensitive is tee distance? + +We reran second-session Iron/Wedge shots with tee distance shifted by ±6 inches. On the clean Iron/Wedge group, the original fixed tee distance scored **0.84° MAE**. A six-inch mistake roughly doubled or tripled the error. + +| Tee distance used by LCMF | Covered | MAE | p50 | p75 | p90 | Bias | +| --- | --- | --- | --- | --- | --- | --- | +| Saved distance − 6 in | 37 / 46 | **1.61°** | 1.34° | 2.23° | 2.89° | -1.54° | +| Saved distance | 37 / 46 | **0.84°** | 0.59° | 1.18° | 1.69° | +0.01° | +| Saved distance + 6 in | 37 / 46 | **2.05°** | 1.98° | 2.87° | 3.57° | +1.81° | + +### Radar-estimated tee range + +The TI track can be extrapolated backward toward impact to estimate where the ball started in range. On July 17, using an effective impact point near 12.5 ms inside the stored radar cube, the radar-derived start range centered very close to the measured tee distance: + +| Group | Median estimated distance error | Mean estimated distance error | Middle 50% | Middle 80% | +| --- | --- | --- | --- | --- | +| All Iron/Wedge | +0.81 in | -0.06 in | -2.65 to +2.63 in | -7.46 to +7.22 in | +| Clean validation subset | +0.06 in | -0.66 in | -2.83 to +2.41 in | -8.27 to +6.12 in | + +That is good enough for a **setup sanity check**. It is not yet good enough to blindly replace the tee distance shot by shot. On the clean group, per-shot radar tee range increased MAE from **0.84°** to **1.22°**. The per-shot estimate is useful evidence; the raw value is still noisy. + +### Wild-placement simulation + +To model unmarked ranges and multi-golfer use, we simulated true ball placement wandering around the saved tee distance. The comparison below uses the clean Iron/Wedge validation subset. + +| Placement pattern | Best tee mode | Best MAE | Fixed-distance MAE | Per-shot radar MAE | +| --- | --- | --- | --- | --- | +| Random ±3 in | Fixed | **0.97°** | 0.97° | 1.30° | +| Random ±6 in | 50/50 blend | **1.14°** | 1.22° | 1.28° | +| Random ±9 in | 50/50 blend | **1.23°** | 1.50° | 1.29° | +| Random ±12 in | Per-shot radar | **1.30°** | 1.84° | 1.30° | +| Random ±18 in | Per-shot radar | **1.30°** | 2.59° | 1.30° | +| Constant setup error ±6–12 in | Rolling 20-shot median | **0.90–1.00°** | 1.64–3.35° | 1.05–1.42° | + +### Recommended app settings + +| Mode | Use when | Behavior | +| --- | --- | --- | +| Fixed distance | Marked home mat, repeatable tee dot, single golfer. | Use the saved measured distance for every shot. Highest accuracy when placement is controlled. | +| Radar assisted | Recommended default for DIY setups. | Use saved distance for LCMF, but warn when the rolling radar estimate says the ball is consistently closer or farther. | +| Radar computed | Range bay, no marker, multiple golfers, or intentionally flexible hitting area. | Use a gated blend of per-shot radar estimate and rolling median. Prefer rolling median for stable setup errors; prefer per-shot only when placement is clearly moving. | + +### Other rig and user variables to test next + +- tilt**Mount angle.** The validation data strongly suggests tilt accuracy is one of the most important setup inputs. We need deliberate TrackMan A/B runs at 10.4°, 11.2°, and 12.0°. +- height**Mat height above radar floor.** A one-inch elevated mat can move early-flight geometry by multiple degrees and late-flight geometry by roughly 0.5–1.0°. The app should store surface offset separately from ball/tee height. +- net**Net or screen distance.** If the ball reaches the net quickly, late frames can become contaminated. Test 4.0 m, 4.6 m, and 5.2 m with driver and wedges. +- floor**Ground material.** Hardwood, turf, concrete, carpet, and range mats change the floor reflection. We should log surface type and compare component spread and no-read rate. +- aim**Horizontal ball position and shanks.** Launch direction changes the vertical fit slightly and shanks are a coverage/classification problem. TX2 aim is the right long-term fix. +- alignment**Radar yaw to target line.** Small yaw errors mostly affect speed projection and future aim, but they can also change which multipath track wins. Add yaw/alignment to setup QA. +- occlusion**Golfer stance and handedness.** Left/right-handed setup, foot position, and club path may alter early clutter. Track handedness and run a small lefty/righty A/B when possible. +- ball**Ball type and markings.** Different balls should not change range-walk geometry much, but spin markings, metallic tape experiments, and range balls can change RCS and no-read rate. + +**Why it is working** + +## Four improvements compound + +- time**More frames.** The ball is observed repeatedly instead of asking one or two moments to carry the whole answer. +- range**A real trajectory.** Fine range bins let us follow the ball moving outward and use never-aliasing range walk instead of trusting Doppler alone. +- physics**Multipath is part of the model.** Direct and floor-reflected paths are allowed to coexist; the algorithm does not force their mixture into one fake point angle. +- independence**OPS anchors speed.** Angle fitting is not allowed to improve itself by changing the speed assumption, and the product does not depend on TI club-speed calibration. +- diversity**Five models must agree in aggregate.** Different models fail differently, so equal fusion is more stable than selecting whichever model happened to look best on this session. + +**Limits and next experiment** + +## What could still prevent a 1° product + +- validation**More independent data.** July 17 was a strong step, but confidence thresholds and driver recovery still need another truth session before we call them production behavior. +- coverage**Strict no-reads.** The UI fallback makes the product complete, but 87.4% strict radar coverage on Iron/Wedge shots is not the finish line. A two-dot relaxed lane can recover useful reads without pretending they are equal to strict reads. +- driver**Late flight can be short.** A driver may hit a close net around 40 ms after impact. The firmware budget may need denser frames or a driver-specific capture allocation without changing estimator rules. +- confidence**No calibrated confidence yet.** Component spread and frame coverage are promising quality features, but thresholds must be learned on independent truth rather than invented. +- latency**UART takes about 7.6 seconds.** On-chip range gating or compression can reduce the blind time and buy more frames without changing RF hardware. +- 2D**Aim and club path are not in this capture.** Bringing in the third transmitter requires a new memory allocation and firmware experiment. + +**Technical deep dive** + +## How the current TI angle pipeline works + +This section is for contributors who want the engineering map without reading every replay script. The public story is “record a short radar movie and let five models vote.” The technical story is a synchronized OPS + TI capture, a ring-buffer unwrap, a range-walk tracker, balanced snapshot selection, five independent angle estimators, and strict quality gates before the value reaches the shot record. + +The key hardware unlock is the IWR6843’s on-chip **L3 RAM**. Instead of streaming every chirp over a slow UART connection in real time, the firmware writes the radar cube into local memory while the shot is happening. That lets the board preserve high-rate complex antenna data during the tiny post-impact window, then drain it slowly after the ball is gone. In plain English: L3 RAM lets us capture the important 72 ms at radar speed, then analyze it at Pi speed. + +### End-to-end signal chain + +| Stage | What happens | Why it exists | +| --- | --- | --- | +| Impact trigger | The sound trigger fires into the Pi. OPS and TI both preserve their recent rolling-buffer evidence around that impact. | Gives both radars the same shot reference instead of waiting for software to notice the ball. | +| L3 capture | The custom TI firmware freezes the recent radar cube in on-chip L3 RAM before anything is sent to the Pi. | Keeps the full-speed antenna movie intact even though the serial dump takes several seconds afterward. | +| OPS processing | OPS produces ball speed, club speed, impact timing, and the primary shot record. | OPS remains the speed authority because it is consistent and already product-integrated. | +| TI dump | The IWR6843 dump is drained after the shot and associated with the OPS shot by trigger timing. | Preserves raw complex antenna evidence for measured launch angle and offline replay. | +| LCMF replay | Static clutter is removed, the outward ball track is found, snapshots are selected, and five angle models are fused. | Separates the ball from impact clutter, floor reflection, and wrong tracks. | +| Shot merge | The server adds TI launch angle when strict gates pass. Otherwise the UI can still show the normal estimate. | Keeps the product usable while making measured radar reads auditable. | + +### Current custom firmware capture + +The current firmware is optimized for vertical launch angle. Internally we still call this the Variant B baseline, but externally it is simply the custom rolling-buffer firmware. + +| Setting | Current value | Engineering tradeoff | +| --- | --- | --- | +| Frame count | 12 radar snapshots | Enough time history for irons/wedges; driver may need denser or more intentional post-impact timing. | +| On-chip L3 RAM | 768 KB rolling storage for the radar cube | The unlock: capture first at radar speed, transfer later at UART speed. | +| Frame spacing | About 6 ms | Fast enough to follow early flight, but a close net can still limit driver late-flight evidence. | +| Range resolution | About 4.7 cm bins from a 3.2 GHz sweep | Fine enough to separate direct and floor-reflected structure better than the K-LD7 path. | +| Vertical antenna view | TX1 + TX3 with four RX, forming eight vertical virtual channels | Preserves vertical phase diversity for launch angle. TX2 is reserved for future horizontal aim work. | +| Per-frame evidence | 16 chirp pairs per frame | More chirps improve per-frame stability; fewer chirps could buy more frames for faster balls. | +| Payload | 786,452 bytes per shot, drained in about 7.6 seconds | Large enough for rich offline evidence; slow enough that future compression/range gating matters. | + +### What the five LCMF models do + +LCMF does not trust a single angle estimate. It asks five models with different failure modes, then gives each model equal weight. That keeps the estimator explicit and reduces the temptation to choose whichever model happened to win one session. + +| Model | Uses | Plain-English role | +| --- | --- | --- | +| Channel model A | Complex phase and amplitude across the eight vertical antenna channels | Finds the launch trajectory that best explains the antenna pattern when a floor path is allowed. | +| Channel model B | The same antenna evidence with a slightly different manifold assumption | Checks whether the answer survives a different view of the direct/reflected mixture. | +| Fast-time model A | Late chronological snapshots and fine range-bin structure | Looks for the direct and floor-reflected range signature as the ball gets farther from the tee. | +| Fast-time model B | Range-walk consistency through the late half of flight | Rewards trajectories that explain the ball moving outward at the OPS-guided speed. | +| Fast-time model C | Snapshot strength, range shape, and late-frame consistency | Provides a third range-domain vote so one noisy frame cannot dominate the result. | +| Fusion | 20% weight per model | Produces the final LCMF launch angle and exposes model spread as a quality signal. | + +### Deeper model notes + +All five models sweep candidate launch angles through the same geometry: measured tee range, radar height, ball height, mount tilt, OPS ball speed, and the tracked TI range samples. For each candidate angle, the software predicts where the direct ball echo and the floor-reflected “image ball” echo should appear. The models differ in which part of the raw radar evidence they trust most. + +- channel_two8**Two-source vertical-array model.** This is the simplest complex antenna model. It treats each selected snapshot as a mixture of two steering vectors across the eight virtual vertical channels: the direct path from the real ball and the image path from the floor reflection. The nuisance coefficients are complex, so amplitude and phase of each path are allowed to float. The candidate angle wins when those two columns predict the observed eight-channel vector with low leave-one-channel-out error. This is useful because it asks, “does the array phase look like this launch angle?” without needing the range-bin shape to be perfect. +- channel_four4_path_tdm**Four-path transmit/receive manifold model.** The floor reflection can happen on transmit, receive, or both, so the full dictionary has four path products: direct-direct, direct-ground, ground-direct, and ground-ground. Because the IWR6843 is time-division multiplexed, the later TX block sees a slightly different path phase when direct and reflected components have different radial velocities. This model includes that TDM residual phase. It is more physically complete than the two-source model, but also has more nuisance freedom, so it is paired with leave-one-channel-out scoring to avoid simply overfitting noise. +- fast_direct1**Late-flight direct-path range model.** This model ignores the floor image and asks whether the local FFT range window around the tracked ball can be explained by one direct path. It is intentionally under-modeled. When it agrees with the multipath models, that is a strong sign the direct echo is dominant and clean. When it disagrees, that disagreement is useful evidence that the shot is reflection-heavy or range-window contaminated. +- fast_two2**Direct plus ground-ground range model.** This model keeps the direct path and the strongest image-ball term. It predicts the small range-bin separation between the real ball path length and the reflected path length, then fits complex coefficients inside a local FFT window around the track. This is the first fast-time model that directly asks, “does the range shape look like direct plus floor reflection?” +- fast_four4**Full local range-shape multipath model.** This uses the same four DD/DG/GD/GG physical path products as the richer channel model, but now it also models where each path should fall inside the local range FFT. It is the most complete range-domain model: spatial steering across antennas multiplied by predicted fast-time tone responses around the ball bin. It is powerful when the late snapshots have enough SNR and path separation, but it can become sensitive to weak late frames, net contamination, or a wrong range track. + +The fusion step is deliberately boring: take the five component launch angles and average them with equal 20% weights. We do not let a single model “win” because each one fails differently. The channel models can be fooled by array manifold errors or calibration drift. The fast-time models can be fooled by range-window contamination or weak late frames. Agreement across both families is the useful signal. + +### Quality gates and confidence ingredients + +- tracker**Outward range track.** The ball candidate must move away from the radar in a physically plausible way. +- speed**OPS agreement.** OPS ball speed guides the expected trajectory and should reject slow ghost tracks, especially for driver. +- frames**Balanced snapshots.** LCMF keeps only a few strong snapshots per frame so one frame with clutter cannot overwhelm the vote. +- RMS**Fit messiness.** RMS measures how cleanly the selected radar evidence fits the candidate path. Relaxed RMS recovers coverage but belongs in a lower-confidence lane. +- spread**Component disagreement.** If the five models disagree too much, the shot should be withheld or shown as low confidence. +- metadata**Session geometry.** Tilt, radar height, tee distance, ball height, mat height, net distance, firmware identity, and TX order must be stored with the shot. + +### How it connects to OpenFlight + +The server flag --iwr6843 enables the TI capture monitor alongside the existing OPS rolling-buffer monitor. OPS still creates the shot and owns speed/carry inputs. The TI monitor captures and processes the raw IWR6843 dump, then the server merges a measured launch angle into the shot when LCMF passes quality gates. When TI does not pass, the shot still appears using the existing estimated launch angle path. + +This split is intentional: OPS provides the stable product backbone, while TI adds measured ball-angle evidence without forcing the whole launch monitor to depend on one chip. + +**Firmware roadmap** + +## We stopped saving the empty parts of the radar movie + +The original roadmap asked whether the chip could calculate range on-board, retain only the useful evidence, add the third transmitter for aim, and still preserve a rolling history around impact. The answer is now mostly yes. The latest firmware computes the complete range picture for every chirp, then stores a smaller moving crop that follows the part of the hitting area where the club and ball can physically appear. + +!!! note + + **The movie analogy is literal enough to be useful:** imagine every radar frame as a wide picture from the radar to the net. We still develop the whole picture, but save only a horizontal crop. The crop slides away from the golfer as time advances. Unlike resizing or video compression, the complex antenna values inside the crop are unchanged. + +!!! note + + **Antenna-name clarification:** TI-labeled **TX1 + TX3** form the eight-element vertical array. The sideways **TX2** supplies the second axis for horizontal launch direction. The new capture now records all three transmitters; vertical launch still uses the proven TX1/TX3 pair while TX2 remains an experimental aim channel. + +![Eighteen radar movie frames showing a 53-bin stored range crop moving from the tee toward late ball flight, followed by an L3 memory budget bar.](../assets/iwr6843-moving-range-crop.svg) + +*The planned 18-frame capture.* Six dense pre-impact frames keep the near-tee corridor, six early-flight frames shift outward, and six late-flight frames finish just beyond the home net distance. One frame may already be armed when impact arrives, so firmware records the actual crop used by every frame rather than asking the Pi to assume the schedule. + +### Develop the full range picture + +The Hardware Accelerator, or HWA, performs a 128-point range FFT for every chirp. That preserves the existing 4.7 cm range resolution while moving repetitive math off the CPU. + +Real ADC tests, corner reflectors, and ball captures confirmed that the HWA can process the live signal and preserve the complex phase and amplitude needed for angle estimation. + +### Store a moving 53-bin crop + +Instead of saving all 128 bins, EDMA copies 53 complex bins from the HWA into L3. The target crop moves through bins 20–72, 32–84, and 47–99 as the ball leaves the tee. + +A geometry replay of 196 TrackMan-recorded trajectories, from sand wedge through driver, retained every modeled ball point that the previous fixed 80-bin capture could have retained. Hardware ball validation is next. + +### Freeze on a clean frame boundary + +The ring continuously overwrites old history, like the OPS speed buffer. Impact tells the Pi to request a freeze; firmware then records a deliberate number of post-impact frames before stopping on a completed HWA/EDMA boundary. + +Repeated clap, corner-reflector, and ball tests proved that the ring can freeze, dump, rearm, and capture again without relying on a seven-second host-side delay. + +### Reinvest the saved memory + +The next candidate records **3 TX, 12 loops, 18 frames, and 4 ms spacing**. That is 20% more per-transmitter looks, 50% more frames, and a denser view of the club approaching impact. + +The linker proves it uses 549,504 of 786,432 L3 bytes, leaving 236,928 bytes for safety and future capture modes. Repeated home captures proved freeze, transfer, decode, and rearm; the July 22 TrackMan session confirmed approximately 0.68° vertical MAE on the matched capture group. + +### Where the L3 budget went + +The first reduction proved the idea with a fixed 80-bin crop. The moving 53-bin version goes further, then spends part of the savings on better time resolution rather than merely producing the smallest file. + +| Capture design | Geometry | L3 ring | Meaning | +| --- | --- | --- | --- | +| Original vertical baseline | 2 TX · 16 loops · 12 frames · 128 samples | 786,432 B | Proven TrackMan evidence, but no room for TX2 or more frames. | +| Raw three-TX proof | 3 TX · 10 loops · 12 frames · 128 samples | 737,280 B | Added aim, but spent almost the entire ring. | +| Fixed range snapshot | 3 TX · 10 loops · 12 frames · 80 bins | 460,800 B | Hardware proof that the chip can store FFT output instead of raw ADC. | +| Moving range crop | 3 TX · 10 loops · 12 frames · 53 bins | 305,280 B | Smallest current ring while retaining the modeled flight corridor. | +| Production capture | 3 TX · 12 loops · 18 frames · 53 bins | 549,504 B | Reinvests memory in stronger, denser club and ball evidence while preserving sub-1° vertical MAE. | + +### Four words that make the firmware easier to follow + +| Word | Plain-language meaning | +| --- | --- | +| Frame | One radar movie frame containing a short burst of measurements from every active antenna. | +| Loop | One pass through TX1, TX2, and TX3. More loops provide more looks inside one frame. | +| Range bin | One approximately 4.7 cm distance slice between the radar and the net. | +| Complex I/Q | The amplitude and phase evidence retained for tracking speed and angle. The crop preserves both. | + +### What is proven, and what is not + +| Finding | Status | Evidence | +| --- | --- | --- | +| On-chip range FFT preserves usable complex antenna evidence | Hardware proven | HWA self-tests, real ADC tests, corner reflectors, and ball captures. | +| Selected bins can continuously fill and rearm a compact L3 ring | Hardware proven | Repeated boundary-frozen captures without short dumps. | +| All three transmitters retain detectable vertical and horizontal motion | Outdoor proven | Vertical launch remained plausible and intentional left/right groups separated. Horizontal accuracy still lacks TrackMan truth. | +| The 53-bin schedule covers normal launch-monitor trajectories | Replay supported | 196 TrackMan trajectories modeled through the proposed early, middle, and late windows. | +| 12 loops and 18 four-millisecond frames fit and decode correctly | Hardware proven | Firmware linker map, exact-geometry host regression, repeated home shots, and matched TrackMan captures. | +| The new firmware maintains sub-1° launch-angle MAE | TrackMan confirmed | Approximately 0.68° MAE on 20 matched July 22 captures using the measured 12.4° mount geometry. | +| Denser frames improve driver, club path, and attack angle | TrackMan pending | The extra pre-impact points are physically promising, but improvement has not been scored. | + +!!! note + + **What cropping could miss:** a badly entered tee or net distance, unusual trigger timing, an extreme mishit, or an unexpected reflection could place useful energy outside the saved corridor. That is why the three windows are 2.48 m wide, overlap heavily, and record their actual starting bin in every frame. Runtime tee/net-aware presets come only after the fixed schedule passes TrackMan. + +### What remains + +| Work | Question it answers | Next proof | +| --- | --- | --- | +| Independent firmware holdout | Does the 12-loop/18-frame result repeat after moving the rig and measuring geometry from scratch? | Repeat the production capture in another bay without fitting tilt from the scored block. | +| Capture coverage | Why did five TrackMan swings lack a corresponding OpenFlight/TI capture? | Separate trigger, OPS-shot, UART, and estimator denominators in the next truth session. | +| Driver recovery | Do 4 ms frames expose the real fast ball before a close net? | Compare tracked TI speed with OPS and TrackMan; reject slow ghost tracks. | +| Horizontal launch | Does TX2 measure degrees, not merely left/right sign? | TrackMan launch-direction MAE, bias, and coverage. | +| Club delivery | Do six dense pre-impact frames improve club path and attack angle? | Score against TrackMan club data without changing the ball estimator. | +| Sparse swing history | Can a second low-cost ring retain club-parallel-to-club-parallel motion? | First prove the dense frames consistently identify the club head. | +| Club and room presets | Should driver favor cadence while wedges favor deeper evidence, and should the crop follow net distance? | Only after one global configuration establishes an unbiased baseline. | +| Production calibration | Can multiple boards share one estimator? | Per-unit phase/gain calibration, enclosure tests, and eventual custom-PCB validation. | + +### Recommended validation order + +1. Repeat the 549,542-byte production capture in an independent bay with geometry measured before scoring. +2. Separate trigger coverage, matched-capture coverage, estimator coverage, and accuracy in the session report. +3. Score horizontal coverage, bias, MAE, P50, P75, and P90 before changing thresholds. +4. Test 14 loops only if 12 loops show a quality or coverage limitation worth spending another 91,584 bytes. +5. Use the remaining L3 budget for club-aware presets or sparse swing history only after ball-angle performance is protected. + +!!! note + + **Bottom line:** the chip did not need more memory; it needed a better editor. On-chip HWA processing and frame-aware cropping let us keep the radar evidence that can affect the answer, discard distance slices the ball cannot occupy, and spend the recovered budget on a denser club-and-ball movie. TrackMan now decides whether that engineering improvement becomes a product improvement. diff --git a/docs/iwr6843/index.md b/docs/iwr6843/index.md index 4d4fcbd35..3e46109a1 100644 --- a/docs/iwr6843/index.md +++ b/docs/iwr6843/index.md @@ -20,7 +20,7 @@ frame ring. For firmware development, architecture, and build instructions, see [firmware developer guide](../development/firmware.md). For a plain-language explanation and the July 2026 TrackMan baseline, see the -[IWR6843 launch-angle field report](../iwr6843_field_report_2026-07.html). +[IWR6843 launch-angle field report](../how-it-works/launch-angle.md). ## Current Configuration diff --git a/docs/iwr6843_field_report_2026-07.html b/docs/iwr6843_field_report_2026-07.html deleted file mode 100644 index 89025acc9..000000000 --- a/docs/iwr6843_field_report_2026-07.html +++ /dev/null @@ -1,924 +0,0 @@ - - - - - - - OpenFlight — IWR6843 Launch-Angle Field Report - - - -
-
-

OpenFlight · Field report · Updated July 22, 2026

-

How we measure launch angle with a 60 GHz radar

-

A plain-language guide to the OPS243 + TI IWR6843 pipeline, the late-flight algorithm we call LCMF-v1, what three TrackMan sessions taught us, and how we plan to improve coverage without hiding quality.

-
- Angle sensor TI IWR6843 - Speed sensor OPS243 - Estimator LCMF-v1 - Validation Three indoor TrackMan sessions -
-
- - - -
-
-
- -

We watch the ball several times, keep the clearest pictures, and ask five judges where it is going.

-

The OPS radar tells us how fast the ball is moving. The TI radar takes a short movie made of radio echoes. We find the little streak that moves away from the tee, keep the clearest moments from each frame, and pay extra attention to the later part of the flight because the ball is farther from the club, golfer, and impact mess.

-

Five slightly different physics models each estimate launch angle. We average their answers and use the measured mount geometry directly. If the radar movie is strong, the UI shows the measured radar angle. If the movie is weaker but still plausible, we want to show it as a lower-confidence two-dot radar read. If the radar cannot honestly follow the ball, the UI labels a normal club-based estimate instead.

-

The important idea: many looks + multiple models + honest confidence, never a hidden adjustment that quietly fits one golfer or one club.

-
-
- -
- -

The short version

-
-
0.83°
combined Iron/Wedge MAE across two indoor TrackMan validation sessions
-
87.4%
strict LCMF-v1 coverage on Iron/Wedge shots, 76 of 87
-
0.67°
p50 absolute error; half of covered Iron/Wedge shots were inside this
-
-0.04°
bias on covered Iron/Wedge shots, effectively centered
-
-

For Iron/Wedge shots, the current pipeline is inside the 1° target on the shots where the radar has enough clean evidence. Driver and Mis Hits are tracked separately because they expose different engineering problems. The next job is widening coverage and hardening setup inputs so the same result travels to different builders, mats, rooms, and ranges.

-
- -
-
- -

We need ball and club direction in a brutally short window

-

OpenFlight has to measure vertical launch, horizontal launch, and eventually useful club-delivery signals while the ball is only a few feet from the radar. Indoors, a fast driver can hit a net or screen tens of milliseconds after impact. That leaves very little clean flight, and the earliest echoes are exactly where the club, hands, tee, ball, floor reflection, and impact noise overlap.

-

The K-LD7 taught us the core lesson: one transmitter, a slower frame cadence, coarse range separation, and one or two useful post-impact looks were not enough to consistently separate the real ball path from multipath and blind-zone behavior. It could look good on selected shots, but it did not create a robust 1° path against TrackMan, the gold-standard source of truth for launch-monitor validation.

-
-

The product problem is not just “detect a ball.” It is detecting the right moving echo, proving it is the ball, modeling the ground-reflected copy, and reporting confidence honestly when the evidence is thin.

-
-
-
- -
-
- -

The IWR6843 gives us enough raw evidence to model the mess

-

We selected TI’s IWR6843 because it can capture coherent complex radar data across multiple receive channels at a much faster cadence than the K-LD7 setup. The IWR6843LEVM board was the practical evaluation platform: available hardware, known antenna geometry, TI tooling, and enough on-chip L3 memory to hold a compact radar cube from the shot.

-

We created and uploaded custom firmware that turns the TI board into a short “radar movie” recorder. Instead of asking the radar to make a decision live, the board saves 12 tightly spaced snapshots of the ball leaving the tee. Each snapshot keeps detailed antenna information, so our software can later follow the ball moving away and separate it from the floor reflection, club, and impact noise.

-
- - - - - - - -
CapabilityWhy it mattersCurrent use
Fast frame cadenceMore looks before the net or screen contaminates the track.12 snapshots over roughly 72 ms in the current custom firmware.
Fine range binsDirect and floor-reflected paths can separate in range as the ball leaves the tee.3.2 GHz sweep, about 4.7 cm bins.
Complex antenna channelsPhase across the array carries angle information even when amplitude is messy.Eight vertical virtual channels from TX1 + TX3 and four RX.
L3 rolling bufferWe can keep raw shot evidence and improve offline without reflashing for every idea.786,452-byte dump per trigger, drained by the Pi.
-
-
- -
-
- -

Two radars, each doing the job it is best at

-

The one-chip goal was useful while learning the TI sensor, but it is no longer the product direction. OPS speed has been highly consistent and does not need to be replaced. The TI board is now focused on the measurements its antenna array can add: vertical launch angle today, and eventually aim direction and club path.

-
-
-
- Independent speed -

OPS243

-

Measures ball and club radial speed from its rolling buffer. This remains the speed authority.

-
-
- Raw angle data -

TI IWR6843

-

Stores a 72 ms coherent radar movie across eight virtual vertical antenna channels.

-
-
- One shot record -

OpenFlight UI

-

Combines OPS speed with TI launch angle. If TI has no read, the shot still appears with an estimated angle.

-
-
-
-

Why the TI chip changed the problem

-

The K-LD7 could usually see only one or two useful frames before the indoor net stopped the ball. It also had coarse range resolution and only a two-element angle view, so a clean ball echo and a floor reflection could blend into one believable but wrong angle.

-

The custom firmware saves a compact, high-detail radar movie: 12 snapshots of the ball leaving the tee, spaced about 6 ms apart. Each snapshot uses multiple antenna views and fine distance slices, which gives the software enough evidence to model the floor reflection instead of pretending it is not there.

-
-

The rolling buffer is the enabling trick. This is similar to how OPS keeps a rolling speed buffer: the radar is always recording, and impact tells the system which recent slice matters. A sound trigger connected to the Pi freezes the TI radar movie after impact, preserving the last 72 ms of raw antenna data. The Pi then drains the 786,452-byte dump over UART in about 7.6 seconds. We accept the delay because we keep the raw evidence for every shot.

-
-
-
- Timeline showing a twelve-frame IWR6843 rolling-buffer capture with impact clutter, early ball frames, cleaner late-flight frames, and a net boundary. -
Rolling buffer mental model. The radar is not trying to decide launch angle from one echo. It stores a short radio movie, then LCMF looks for the portion where the ball has separated from the impact mess but has not yet reached the net or screen.
-
-
- -
-
- -

What the algorithm actually does

-

LCMF means Late-Flight Complex Multipath Fusion. “Late-flight” means it favors the cleaner second half of the captured ball flight. “Complex” means it keeps both amplitude and phase from every antenna. “Multipath” means direct and floor-reflected echoes are modeled together. “Fusion” means no single model gets to decide the answer.

-
-
-
1

Freeze both buffers

The same impact edge timestamps the OPS shot and starts the TI dump. Matching is normally within a few milliseconds.

-
2

Find the outward streak

Static clutter is removed. The tracker follows a target moving outward through range over time rather than trusting aliased Doppler speed.

-
3

Balance the frames

Keep snapshots with strength score ≥ 8 and range ≤ 4.7 m, then retain at most the strongest four from each frame. One noisy frame cannot dominate.

-
4

Use OPS speed as the guide

The independently measured OPS ball speed defines the candidate trajectory. TI’s local range-walk velocity handles the small timing correction between transmitters.

-
5

Ask five physics models

Two models compare the eight antenna channels across all balanced snapshots. Three inspect the direct and reflected range structure in the chronological late half.

-
6

Fuse and report

Each model receives exactly 20% weight. Their mean is the LCMF angle. The production path favors measured setup geometry and the same estimator rules for every club.

-
-
-
- - - - - - -
Model familyPlain-language questionData used
Two channel modelsWhich launch trajectory best explains the phase pattern across the antenna array when direct and floor paths are allowed?All balanced snapshots
Three fast-time modelsWhich trajectory best explains the small range separation and mixture of direct and reflected echoes around the tracked ball?Chronological second half
Equal fusionWhat answer survives five different assumptions instead of winning one hand-picked model?20% per component
-
-
- -
-
- -

Which frames are selected, and why late flight helps

-

The gray marks below are usable snapshots along three real TrackMan-paired ball tracks. Orange circles are the strongest four retained from each frame. Teal dots are the chronological late half used by the three fast-time models. Frame numbers wrap because the radar memory is a ring; the horizontal time axis is the true order.

-
-
- Three TrackMan-paired shots showing tracked radar snapshots, strongest four snapshots selected per frame, and the late half used by LCMF fast-time models. -
Actual July 14 captures. The driver has only three clean frames and is therefore the hardest case. The 7-iron and 9-iron offer more looks across the flight. Late snapshots are not automatically “correct”; they are simply less contaminated by impact, club, hands, and tee while providing more direct-versus-ground path separation.
-
-
-

This selection is deliberately boring: no club-specific timing window, no TrackMan input, and no hand-picked frame number. The same strength, range, per-frame balancing, and chronological-half rules run on every shot.

-
-
- -
-
- -

The radar is accurate only if the setup geometry is honest

-

The biggest lesson from the first two validation sessions is simple: the algorithm can follow the ball, but it needs the real-world setup described correctly. Mount tilt, radar height, tee distance, ball height, mat height, and net distance all affect where the radar expects the direct and floor-reflected echoes to appear.

-

When those inputs are right, the same LCMF-v1 estimator produces a centered result across two indoor TrackMan sessions without per-club tuning. When those inputs are wrong, the error can look like a radar problem even though the underlying ball track is still present.

-
- - - - - - - -
Setup inputWhy it mattersProduct plan
Mount tiltDefines how the antenna frame maps into the golfer's launch frame.Measured setting first; later add an on-rig level sensor.
Tee distanceChanges the expected direct/reflected path geometry during the first few feet of flight.Support measured distance and radar-assisted setup warnings.
Mat and ball heightAn elevated mat changes the ball height relative to the radar and the floor reflection.Store ball height and mat/surface height separately.
Net or screen distanceDefines how much clean late flight exists before impact with the screen or net.Log it per session and use it when selecting late frames.
-

What remains true

-
    -
  • learnedThe estimator is extracting stable ball-angle information from the TI radar movie.
  • -
  • not solvedWe still need to make these setup measurements easy enough for normal builders, not just people who lived inside the test sessions.
  • -
-
-
- -
-
- -

Two TrackMan sessions are the current accuracy baseline

-
-
-

The headline below combines two indoor TrackMan validation sessions. Driver and Mis Hits are separated so the main number describes Iron/Wedge launch-angle performance rather than hiding known edge cases.

-
- - - - - - -
GroupShotsCoveredCoverageMAEp50p75p90Bias
Iron/Wedge headline877687.4%0.83°0.67°1.20°1.80°-0.04°
First validation session Iron/Wedge413995.1%0.83°0.78°1.25°1.81°-0.10°
Second validation session Iron/Wedge463780.4%0.84°0.59°1.19°1.70°+0.02°
-
-

How to read absolute error: MAE is the average distance from TrackMan, ignoring sign. p50 means half the covered shots were closer than that error. Bias keeps the sign and tells us whether the whole group is systematically high or low.

-
- -

Current 18-frame firmware confirmation

-

A third indoor TrackMan session on July 22 tested the production firmware in this report: 3 TX, 12 loops, 18 frames, 4 ms spacing, and moving 53-bin windows. Twenty shots had matched OpenFlight and TI captures and therefore formed the valid radar denominator; five additional TrackMan swings had no corresponding OpenFlight capture and were not counted as estimator misses.

-

Using the physically measured 12.4° mount geometry, the matched group produced approximately 0.68° launch-angle MAE. An earlier block in the same session independently suggested about 12.3°; applying that geometry to the later good-shot block produced approximately 0.47° MAE. That temporal result is encouraging, but it remains diagnostic rather than the headline because the tilt candidate was inferred inside the same session. The conservative conclusion is that denser, cropped firmware preserved sub-1° vertical accuracy while adding more frames and the third transmitter.

- -

Iron/Wedge breakdown by club

-

The 9-iron table excludes a same-day experimental transmitter-order test from the headline. That experiment is useful firmware evidence, but it should not be mixed into the normal production score.

-
- - - - - - - -
ClubGood shotsCoveredCoverageMAEp50p75p90Bias
Sand wedge171588.2%0.67°0.46°1.06°1.58°-0.22°
9-iron272592.6%0.89°0.81°1.18°1.73°+0.24°
7-iron211885.7%0.91°0.49°1.15°1.88°-0.06°
5-iron221881.8%0.82°0.69°1.31°1.84°-0.25°
- -

Driver separated

-
- - - - - - -
GroupShotsCoveredCoverageMAEp50p75p90Bias
Driver221881.8%3.55°1.31°1.82°15.57°+3.39°
First validation session driver9555.6%1.58°1.37°1.85°2.77°+1.08°
Second validation session driver1313100.0%4.31°1.21°1.71°15.68°+4.27°
- -
-

Driver is not the same failure as irons. The second-session driver misses were mostly false acceptance of slow/ghost tracks. Raw replay showed the real fast ball in the frames, so the immediate fix is an OPS-vs-TI speed gate and a low-confidence fast-track recovery path.

-
- -

Mis Hits separated

-
- - - - - - - -
GroupShotsCoveredCoverageMAEp50p75p90Bias
Mis Hits191368.4%2.20°1.16°3.07°5.10°-0.54°
First validation session Mis Hits8787.5%2.40°1.29°3.81°4.83°-0.15°
Second validation session Mis Hits11654.5%1.98°0.78°1.43°4.54°-1.00°
Skulls / very low launch55100.0%6.00°2.07°3.51°15.47°+4.31°
-

These groups are still product-critical because real golfers hit them. They are separated here because they require different engineering: TX2 aim for directional Mis Hits, low-launch/impact-clutter logic, and OPS speed agreement for driver.

-
-
- -
-
- -

Increase coverage, add club data, and harden the setup inputs

-

The strict LCMF-v1 gate is doing the right thing for accuracy, but it leaves some real ball flights unreported. The first validation session already had strong strict coverage after the setup geometry was cleaned up. The second session showed the more practical product problem: a relaxed pass can recover no-reads, but those reads should enter the UI as measured lower-confidence angles rather than being mixed into the high-confidence lane.

-

RMS is a “how messy was the fit?” score. Lower RMS means the radar snapshots line up neatly with one clean ball path. Higher RMS means the ball is probably there, but the evidence is noisier, weaker, or more mixed with reflections. Relaxing the RMS limit lets us accept more of those imperfect radar tracks, which increases coverage, but it also increases the chance that a recovered angle is a little farther from TrackMan.

-

The product answer should not be “lower the bar and call everything high confidence.” The product answer should be a second lane: measured, but lower confidence.

- -
- - - - - - - - -
ModeIron/Wedge coverageMAEp50p75p90New recovered readsRecommended UI
Combined strict LCMF-v176 / 87 · 87.4%0.83°0.67°1.20°1.80°03 dots
First-session strict39 / 41 · 95.1%0.83°0.78°1.25°1.81°03 dots
Second-session strict37 / 46 · 80.4%0.84°0.59°1.19°1.70°03 dots
Second-session relaxed RMS ≤ 0.5842 / 49 · 85.7%1.00°0.71°1.32°2.06°52 dots
Second-session relaxed RMS ≤ 0.7045 / 49 · 91.8%1.09°0.78°1.56°2.50°82 dots, lab-only first
- -

Recommended confidence contract

-
    -
  1. Strict pass: if normal LCMF-v1 accepts, show the radar launch angle as the primary measured value with full confidence.
  2. -
  3. Relaxed pass: if strict LCMF no-reads but a relaxed replay passes RMS, snapshot, frame, component-spread, and OPS-speed checks, show the radar angle with a two-dot confidence marker.
  4. -
  5. Estimate: if both radar passes fail, show the club/speed estimate exactly as we do today.
  6. -
- -
-

Why this is honest: a two-dot radar read is still better than hiding useful evidence, but it tells the golfer and the engineering team that the ball track was recovered under looser rules. That preserves trust while improving coverage.

-
- -

Driver-specific guardrail

-

Driver needs an additional speed sanity check before any confidence badge. The worst driver misses were accepted TI tracks around 55–57 mph while OPS measured roughly 152–158 mph. Raw-frame replay showed the fast ball was present, so the immediate fix is to withhold any TI angle when its tracked speed is far below OPS speed, then optionally try an OPS-guided fast-track recovery as a low-confidence read.

-
- - - - - -
Driver policyWhyExpected effect
Reject TI track speed below about 65–70% of OPS ball speedCatches obvious slow ghost tracks before they reach the UI.Turns bad measured angles into estimates instead of false confidence.
Try OPS-guided fast-track recovery after rejectionOffline replay recovered 3 of 4 bad driver shots with about 2.6° MAE.Potential two-dot driver reads, but needs more truth data before shipping as normal confidence.
- -

Club data and setup hardening

-
    -
  • clubKeep club speed with OPS, but start collecting club-delivery evidence. A split pre-impact/post-impact firmware mode may expose attack angle and club path research signals without pretending the TI board is the club-speed authority.
  • -
  • aimAdd TX2 for horizontal launch and shank classification. Far-right shots should eventually be recognized as far-right shots, not forced through a purely vertical interpretation.
  • -
  • inputsHarden tee distance, mount tilt, radar height, ball height, mat height, and net distance. The app should support measured settings, defaults, and radar sanity checks that warn when the actual session appears to drift.
  • -
-
-
- -
-
- -

Ball placement is a product variable, not just a measurement chore

-

Commercial radar systems usually solve tee placement with a prescribed setup window and alignment aids. Photometric systems solve it by forcing the ball into a camera-observed hitting zone. OpenFlight sits in the DIY middle: we want the accuracy of a measured geometry, but builders may move between a marked home mat, an unmarked simulator bay, and a range mat where the ball can drift shot to shot.

- -
-

July 17 replay result: when ball placement is disciplined, fixed tee distance is best. When placement wanders more than about 6–9 inches, radar-computed or blended tee distance starts beating a stale fixed setting.

-
- -

How sensitive is tee distance?

-

We reran second-session Iron/Wedge shots with tee distance shifted by ±6 inches. On the clean Iron/Wedge group, the original fixed tee distance scored 0.84° MAE. A six-inch mistake roughly doubled or tripled the error.

-
- - - - - - -
Tee distance used by LCMFCoveredMAEp50p75p90Bias
Saved distance − 6 in37 / 461.61°1.34°2.23°2.89°-1.54°
Saved distance37 / 460.84°0.59°1.18°1.69°+0.01°
Saved distance + 6 in37 / 462.05°1.98°2.87°3.57°+1.81°
- -

Radar-estimated tee range

-

The TI track can be extrapolated backward toward impact to estimate where the ball started in range. On July 17, using an effective impact point near 12.5 ms inside the stored radar cube, the radar-derived start range centered very close to the measured tee distance:

-
- - - - - -
GroupMedian estimated distance errorMean estimated distance errorMiddle 50%Middle 80%
All Iron/Wedge+0.81 in-0.06 in-2.65 to +2.63 in-7.46 to +7.22 in
Clean validation subset+0.06 in-0.66 in-2.83 to +2.41 in-8.27 to +6.12 in
-

That is good enough for a setup sanity check. It is not yet good enough to blindly replace the tee distance shot by shot. On the clean group, per-shot radar tee range increased MAE from 0.84° to 1.22°. The per-shot estimate is useful evidence; the raw value is still noisy.

- -

Wild-placement simulation

-

To model unmarked ranges and multi-golfer use, we simulated true ball placement wandering around the saved tee distance. The comparison below uses the clean Iron/Wedge validation subset.

-
- - - - - - - - - -
Placement patternBest tee modeBest MAEFixed-distance MAEPer-shot radar MAE
Random ±3 inFixed0.97°0.97°1.30°
Random ±6 in50/50 blend1.14°1.22°1.28°
Random ±9 in50/50 blend1.23°1.50°1.29°
Random ±12 inPer-shot radar1.30°1.84°1.30°
Random ±18 inPer-shot radar1.30°2.59°1.30°
Constant setup error ±6–12 inRolling 20-shot median0.90–1.00°1.64–3.35°1.05–1.42°
- -

Recommended app settings

-
- - - - - - -
ModeUse whenBehavior
Fixed distanceMarked home mat, repeatable tee dot, single golfer.Use the saved measured distance for every shot. Highest accuracy when placement is controlled.
Radar assistedRecommended default for DIY setups.Use saved distance for LCMF, but warn when the rolling radar estimate says the ball is consistently closer or farther.
Radar computedRange bay, no marker, multiple golfers, or intentionally flexible hitting area.Use a gated blend of per-shot radar estimate and rolling median. Prefer rolling median for stable setup errors; prefer per-shot only when placement is clearly moving.
- -

Other rig and user variables to test next

-
    -
  • tiltMount angle. The validation data strongly suggests tilt accuracy is one of the most important setup inputs. We need deliberate TrackMan A/B runs at 10.4°, 11.2°, and 12.0°.
  • -
  • heightMat height above radar floor. A one-inch elevated mat can move early-flight geometry by multiple degrees and late-flight geometry by roughly 0.5–1.0°. The app should store surface offset separately from ball/tee height.
  • -
  • netNet or screen distance. If the ball reaches the net quickly, late frames can become contaminated. Test 4.0 m, 4.6 m, and 5.2 m with driver and wedges.
  • -
  • floorGround material. Hardwood, turf, concrete, carpet, and range mats change the floor reflection. We should log surface type and compare component spread and no-read rate.
  • -
  • aimHorizontal ball position and shanks. Launch direction changes the vertical fit slightly and shanks are a coverage/classification problem. TX2 aim is the right long-term fix.
  • -
  • alignmentRadar yaw to target line. Small yaw errors mostly affect speed projection and future aim, but they can also change which multipath track wins. Add yaw/alignment to setup QA.
  • -
  • occlusionGolfer stance and handedness. Left/right-handed setup, foot position, and club path may alter early clutter. Track handedness and run a small lefty/righty A/B when possible.
  • -
  • ballBall type and markings. Different balls should not change range-walk geometry much, but spin markings, metallic tape experiments, and range balls can change RCS and no-read rate.
  • -
-
-
- -
-
- -

Four improvements compound

-
    -
  • timeMore frames. The ball is observed repeatedly instead of asking one or two moments to carry the whole answer.
  • -
  • rangeA real trajectory. Fine range bins let us follow the ball moving outward and use never-aliasing range walk instead of trusting Doppler alone.
  • -
  • physicsMultipath is part of the model. Direct and floor-reflected paths are allowed to coexist; the algorithm does not force their mixture into one fake point angle.
  • -
  • independenceOPS anchors speed. Angle fitting is not allowed to improve itself by changing the speed assumption, and the product does not depend on TI club-speed calibration.
  • -
  • diversityFive models must agree in aggregate. Different models fail differently, so equal fusion is more stable than selecting whichever model happened to look best on this session.
  • -
-
-
- -
-
- -

What could still prevent a 1° product

-
    -
  • validationMore independent data. July 17 was a strong step, but confidence thresholds and driver recovery still need another truth session before we call them production behavior.
  • -
  • coverageStrict no-reads. The UI fallback makes the product complete, but 87.4% strict radar coverage on Iron/Wedge shots is not the finish line. A two-dot relaxed lane can recover useful reads without pretending they are equal to strict reads.
  • -
  • driverLate flight can be short. A driver may hit a close net around 40 ms after impact. The firmware budget may need denser frames or a driver-specific capture allocation without changing estimator rules.
  • -
  • confidenceNo calibrated confidence yet. Component spread and frame coverage are promising quality features, but thresholds must be learned on independent truth rather than invented.
  • -
  • latencyUART takes about 7.6 seconds. On-chip range gating or compression can reduce the blind time and buy more frames without changing RF hardware.
  • -
  • 2DAim and club path are not in this capture. Bringing in the third transmitter requires a new memory allocation and firmware experiment.
  • -
-
-
- -
-
- -

How the current TI angle pipeline works

-

This section is for contributors who want the engineering map without reading every replay script. The public story is “record a short radar movie and let five models vote.” The technical story is a synchronized OPS + TI capture, a ring-buffer unwrap, a range-walk tracker, balanced snapshot selection, five independent angle estimators, and strict quality gates before the value reaches the shot record.

-

The key hardware unlock is the IWR6843’s on-chip L3 RAM. Instead of streaming every chirp over a slow UART connection in real time, the firmware writes the radar cube into local memory while the shot is happening. That lets the board preserve high-rate complex antenna data during the tiny post-impact window, then drain it slowly after the ball is gone. In plain English: L3 RAM lets us capture the important 72 ms at radar speed, then analyze it at Pi speed.

- -

End-to-end signal chain

-
- - - - - - - - - -
StageWhat happensWhy it exists
Impact triggerThe sound trigger fires into the Pi. OPS and TI both preserve their recent rolling-buffer evidence around that impact.Gives both radars the same shot reference instead of waiting for software to notice the ball.
L3 captureThe custom TI firmware freezes the recent radar cube in on-chip L3 RAM before anything is sent to the Pi.Keeps the full-speed antenna movie intact even though the serial dump takes several seconds afterward.
OPS processingOPS produces ball speed, club speed, impact timing, and the primary shot record.OPS remains the speed authority because it is consistent and already product-integrated.
TI dumpThe IWR6843 dump is drained after the shot and associated with the OPS shot by trigger timing.Preserves raw complex antenna evidence for measured launch angle and offline replay.
LCMF replayStatic clutter is removed, the outward ball track is found, snapshots are selected, and five angle models are fused.Separates the ball from impact clutter, floor reflection, and wrong tracks.
Shot mergeThe server adds TI launch angle when strict gates pass. Otherwise the UI can still show the normal estimate.Keeps the product usable while making measured radar reads auditable.
- -

Current custom firmware capture

-

The current firmware is optimized for vertical launch angle. Internally we still call this the Variant B baseline, but externally it is simply the custom rolling-buffer firmware.

-
- - - - - - - - - - -
SettingCurrent valueEngineering tradeoff
Frame count12 radar snapshotsEnough time history for irons/wedges; driver may need denser or more intentional post-impact timing.
On-chip L3 RAM768 KB rolling storage for the radar cubeThe unlock: capture first at radar speed, transfer later at UART speed.
Frame spacingAbout 6 msFast enough to follow early flight, but a close net can still limit driver late-flight evidence.
Range resolutionAbout 4.7 cm bins from a 3.2 GHz sweepFine enough to separate direct and floor-reflected structure better than the K-LD7 path.
Vertical antenna viewTX1 + TX3 with four RX, forming eight vertical virtual channelsPreserves vertical phase diversity for launch angle. TX2 is reserved for future horizontal aim work.
Per-frame evidence16 chirp pairs per frameMore chirps improve per-frame stability; fewer chirps could buy more frames for faster balls.
Payload786,452 bytes per shot, drained in about 7.6 secondsLarge enough for rich offline evidence; slow enough that future compression/range gating matters.
- -

What the five LCMF models do

-

LCMF does not trust a single angle estimate. It asks five models with different failure modes, then gives each model equal weight. That keeps the estimator explicit and reduces the temptation to choose whichever model happened to win one session.

-
- - - - - - - - - -
ModelUsesPlain-English role
Channel model AComplex phase and amplitude across the eight vertical antenna channelsFinds the launch trajectory that best explains the antenna pattern when a floor path is allowed.
Channel model BThe same antenna evidence with a slightly different manifold assumptionChecks whether the answer survives a different view of the direct/reflected mixture.
Fast-time model ALate chronological snapshots and fine range-bin structureLooks for the direct and floor-reflected range signature as the ball gets farther from the tee.
Fast-time model BRange-walk consistency through the late half of flightRewards trajectories that explain the ball moving outward at the OPS-guided speed.
Fast-time model CSnapshot strength, range shape, and late-frame consistencyProvides a third range-domain vote so one noisy frame cannot dominate the result.
Fusion20% weight per modelProduces the final LCMF launch angle and exposes model spread as a quality signal.
- -

Deeper model notes

-

All five models sweep candidate launch angles through the same geometry: measured tee range, radar height, ball height, mount tilt, OPS ball speed, and the tracked TI range samples. For each candidate angle, the software predicts where the direct ball echo and the floor-reflected “image ball” echo should appear. The models differ in which part of the raw radar evidence they trust most.

-
    -
  • channel_two8Two-source vertical-array model. This is the simplest complex antenna model. It treats each selected snapshot as a mixture of two steering vectors across the eight virtual vertical channels: the direct path from the real ball and the image path from the floor reflection. The nuisance coefficients are complex, so amplitude and phase of each path are allowed to float. The candidate angle wins when those two columns predict the observed eight-channel vector with low leave-one-channel-out error. This is useful because it asks, “does the array phase look like this launch angle?” without needing the range-bin shape to be perfect.
  • -
  • channel_four4_path_tdmFour-path transmit/receive manifold model. The floor reflection can happen on transmit, receive, or both, so the full dictionary has four path products: direct-direct, direct-ground, ground-direct, and ground-ground. Because the IWR6843 is time-division multiplexed, the later TX block sees a slightly different path phase when direct and reflected components have different radial velocities. This model includes that TDM residual phase. It is more physically complete than the two-source model, but also has more nuisance freedom, so it is paired with leave-one-channel-out scoring to avoid simply overfitting noise.
  • -
  • fast_direct1Late-flight direct-path range model. This model ignores the floor image and asks whether the local FFT range window around the tracked ball can be explained by one direct path. It is intentionally under-modeled. When it agrees with the multipath models, that is a strong sign the direct echo is dominant and clean. When it disagrees, that disagreement is useful evidence that the shot is reflection-heavy or range-window contaminated.
  • -
  • fast_two2Direct plus ground-ground range model. This model keeps the direct path and the strongest image-ball term. It predicts the small range-bin separation between the real ball path length and the reflected path length, then fits complex coefficients inside a local FFT window around the track. This is the first fast-time model that directly asks, “does the range shape look like direct plus floor reflection?”
  • -
  • fast_four4Full local range-shape multipath model. This uses the same four DD/DG/GD/GG physical path products as the richer channel model, but now it also models where each path should fall inside the local range FFT. It is the most complete range-domain model: spatial steering across antennas multiplied by predicted fast-time tone responses around the ball bin. It is powerful when the late snapshots have enough SNR and path separation, but it can become sensitive to weak late frames, net contamination, or a wrong range track.
  • -
-

The fusion step is deliberately boring: take the five component launch angles and average them with equal 20% weights. We do not let a single model “win” because each one fails differently. The channel models can be fooled by array manifold errors or calibration drift. The fast-time models can be fooled by range-window contamination or weak late frames. Agreement across both families is the useful signal.

- -

Quality gates and confidence ingredients

-
    -
  • trackerOutward range track. The ball candidate must move away from the radar in a physically plausible way.
  • -
  • speedOPS agreement. OPS ball speed guides the expected trajectory and should reject slow ghost tracks, especially for driver.
  • -
  • framesBalanced snapshots. LCMF keeps only a few strong snapshots per frame so one frame with clutter cannot overwhelm the vote.
  • -
  • RMSFit messiness. RMS measures how cleanly the selected radar evidence fits the candidate path. Relaxed RMS recovers coverage but belongs in a lower-confidence lane.
  • -
  • spreadComponent disagreement. If the five models disagree too much, the shot should be withheld or shown as low confidence.
  • -
  • metadataSession geometry. Tilt, radar height, tee distance, ball height, mat height, net distance, firmware identity, and TX order must be stored with the shot.
  • -
- -

How it connects to OpenFlight

-

The server flag --iwr6843 enables the TI capture monitor alongside the existing OPS rolling-buffer monitor. OPS still creates the shot and owns speed/carry inputs. The TI monitor captures and processes the raw IWR6843 dump, then the server merges a measured launch angle into the shot when LCMF passes quality gates. When TI does not pass, the shot still appears using the existing estimated launch angle path.

-

This split is intentional: OPS provides the stable product backbone, while TI adds measured ball-angle evidence without forcing the whole launch monitor to depend on one chip.

-
-
- -
-
- -

We stopped saving the empty parts of the radar movie

-

The original roadmap asked whether the chip could calculate range on-board, retain only the useful evidence, add the third transmitter for aim, and still preserve a rolling history around impact. The answer is now mostly yes. The latest firmware computes the complete range picture for every chirp, then stores a smaller moving crop that follows the part of the hitting area where the club and ball can physically appear.

- -
-

The movie analogy is literal enough to be useful: imagine every radar frame as a wide picture from the radar to the net. We still develop the whole picture, but save only a horizontal crop. The crop slides away from the golfer as time advances. Unlike resizing or video compression, the complex antenna values inside the crop are unchanged.

-
- -
-

Antenna-name clarification: TI-labeled TX1 + TX3 form the eight-element vertical array. The sideways TX2 supplies the second axis for horizontal launch direction. The new capture now records all three transmitters; vertical launch still uses the proven TX1/TX3 pair while TX2 remains an experimental aim channel.

-
-
- -
- Eighteen radar movie frames showing a 53-bin stored range crop moving from the tee toward late ball flight, followed by an L3 memory budget bar. -
The planned 18-frame capture. Six dense pre-impact frames keep the near-tee corridor, six early-flight frames shift outward, and six late-flight frames finish just beyond the home net distance. One frame may already be armed when impact arrives, so firmware records the actual crop used by every frame rather than asking the Pi to assume the schedule.
-
- -
-
- Hardware proven -

Develop the full range picture

-

The Hardware Accelerator, or HWA, performs a 128-point range FFT for every chirp. That preserves the existing 4.7 cm range resolution while moving repetitive math off the CPU.

-

Real ADC tests, corner reflectors, and ball captures confirmed that the HWA can process the live signal and preserve the complex phase and amplitude needed for angle estimation.

-
- -
- Replay supported -

Store a moving 53-bin crop

-

Instead of saving all 128 bins, EDMA copies 53 complex bins from the HWA into L3. The target crop moves through bins 20–72, 32–84, and 47–99 as the ball leaves the tee.

-

A geometry replay of 196 TrackMan-recorded trajectories, from sand wedge through driver, retained every modeled ball point that the previous fixed 80-bin capture could have retained. Hardware ball validation is next.

-
- -
- Hardware proven -

Freeze on a clean frame boundary

-

The ring continuously overwrites old history, like the OPS speed buffer. Impact tells the Pi to request a freeze; firmware then records a deliberate number of post-impact frames before stopping on a completed HWA/EDMA boundary.

-

Repeated clap, corner-reflector, and ball tests proved that the ring can freeze, dump, rearm, and capture again without relying on a seven-second host-side delay.

-
- -
- Hardware + TrackMan proven -

Reinvest the saved memory

-

The next candidate records 3 TX, 12 loops, 18 frames, and 4 ms spacing. That is 20% more per-transmitter looks, 50% more frames, and a denser view of the club approaching impact.

-

The linker proves it uses 549,504 of 786,432 L3 bytes, leaving 236,928 bytes for safety and future capture modes. Repeated home captures proved freeze, transfer, decode, and rearm; the July 22 TrackMan session confirmed approximately 0.68° vertical MAE on the matched capture group.

-
-
- -
-

Where the L3 budget went

-

The first reduction proved the idea with a fixed 80-bin crop. The moving 53-bin version goes further, then spends part of the savings on better time resolution rather than merely producing the smallest file.

-
- - - - - - - - -
Capture designGeometryL3 ringMeaning
Original vertical baseline2 TX · 16 loops · 12 frames · 128 samples786,432 BProven TrackMan evidence, but no room for TX2 or more frames.
Raw three-TX proof3 TX · 10 loops · 12 frames · 128 samples737,280 BAdded aim, but spent almost the entire ring.
Fixed range snapshot3 TX · 10 loops · 12 frames · 80 bins460,800 BHardware proof that the chip can store FFT output instead of raw ADC.
Moving range crop3 TX · 10 loops · 12 frames · 53 bins305,280 BSmallest current ring while retaining the modeled flight corridor.
Production capture3 TX · 12 loops · 18 frames · 53 bins549,504 BReinvests memory in stronger, denser club and ball evidence while preserving sub-1° vertical MAE.
- -

Four words that make the firmware easier to follow

-
- - - - - - - -
WordPlain-language meaning
FrameOne radar movie frame containing a short burst of measurements from every active antenna.
LoopOne pass through TX1, TX2, and TX3. More loops provide more looks inside one frame.
Range binOne approximately 4.7 cm distance slice between the radar and the net.
Complex I/QThe amplitude and phase evidence retained for tracking speed and angle. The crop preserves both.
- -

What is proven, and what is not

-
- - - - - - - - - - -
FindingStatusEvidence
On-chip range FFT preserves usable complex antenna evidenceHardware provenHWA self-tests, real ADC tests, corner reflectors, and ball captures.
Selected bins can continuously fill and rearm a compact L3 ringHardware provenRepeated boundary-frozen captures without short dumps.
All three transmitters retain detectable vertical and horizontal motionOutdoor provenVertical launch remained plausible and intentional left/right groups separated. Horizontal accuracy still lacks TrackMan truth.
The 53-bin schedule covers normal launch-monitor trajectoriesReplay supported196 TrackMan trajectories modeled through the proposed early, middle, and late windows.
12 loops and 18 four-millisecond frames fit and decode correctlyHardware provenFirmware linker map, exact-geometry host regression, repeated home shots, and matched TrackMan captures.
The new firmware maintains sub-1° launch-angle MAETrackMan confirmedApproximately 0.68° MAE on 20 matched July 22 captures using the measured 12.4° mount geometry.
Denser frames improve driver, club path, and attack angleTrackMan pendingThe extra pre-impact points are physically promising, but improvement has not been scored.
- -
-

What cropping could miss: a badly entered tee or net distance, unusual trigger timing, an extreme mishit, or an unexpected reflection could place useful energy outside the saved corridor. That is why the three windows are 2.48 m wide, overlap heavily, and record their actual starting bin in every frame. Runtime tee/net-aware presets come only after the fixed schedule passes TrackMan.

-
- -

What remains

-
- - - - - - - - - - - -
WorkQuestion it answersNext proof
Independent firmware holdoutDoes the 12-loop/18-frame result repeat after moving the rig and measuring geometry from scratch?Repeat the production capture in another bay without fitting tilt from the scored block.
Capture coverageWhy did five TrackMan swings lack a corresponding OpenFlight/TI capture?Separate trigger, OPS-shot, UART, and estimator denominators in the next truth session.
Driver recoveryDo 4 ms frames expose the real fast ball before a close net?Compare tracked TI speed with OPS and TrackMan; reject slow ghost tracks.
Horizontal launchDoes TX2 measure degrees, not merely left/right sign?TrackMan launch-direction MAE, bias, and coverage.
Club deliveryDo six dense pre-impact frames improve club path and attack angle?Score against TrackMan club data without changing the ball estimator.
Sparse swing historyCan a second low-cost ring retain club-parallel-to-club-parallel motion?First prove the dense frames consistently identify the club head.
Club and room presetsShould driver favor cadence while wedges favor deeper evidence, and should the crop follow net distance?Only after one global configuration establishes an unbiased baseline.
Production calibrationCan multiple boards share one estimator?Per-unit phase/gain calibration, enclosure tests, and eventual custom-PCB validation.
- -

Recommended validation order

-
    -
  1. Repeat the 549,542-byte production capture in an independent bay with geometry measured before scoring.
  2. -
  3. Separate trigger coverage, matched-capture coverage, estimator coverage, and accuracy in the session report.
  4. -
  5. Score horizontal coverage, bias, MAE, P50, P75, and P90 before changing thresholds.
  6. -
  7. Test 14 loops only if 12 loops show a quality or coverage limitation worth spending another 91,584 bytes.
  8. -
  9. Use the remaining L3 budget for club-aware presets or sparse swing history only after ball-angle performance is protected.
  10. -
- -
-

Bottom line: the chip did not need more memory; it needed a better editor. On-chip HWA processing and frame-aware cropping let us keep the radar evidence that can affect the answer, discard distance slices the ball cannot occupy, and spend the recovered budget on a denser club-and-ball movie. TrackMan now decides whether that engineering improvement becomes a product improvement.

-
-
-
- -
- -
- OpenFlight · DIY launch monitor · report updated 2026-07-22
- Sources: three indoor TrackMan validation sessions, LCMF-v1 candidate, setup-variable replay, HWA range-snapshot firmware, and production server integration.
- Reproducible artifacts: artifacts/report_20260718/combined_lcmf_summary.csv, artifacts/report_20260718/combined_lcmf_results.csv, scripts/analysis/tm0714/render_lcmf_report_figures.py -
-
- - diff --git a/docs/legacy/index.md b/docs/legacy/index.md index 77e7fc8e9..df65581c4 100644 --- a/docs/legacy/index.md +++ b/docs/legacy/index.md @@ -22,7 +22,7 @@ flag. shot via the OPS impact timestamp. For how the estimator works internally, see -[kld7-launch-angle-explained.html](launch-angle-explained.html) and +[Launch angle explained](launch-angle-explained.md) and [kld7-ball-detection-theory.md](ball-detection-theory.md). ## Enabling it diff --git a/docs/legacy/launch-angle-explained.html b/docs/legacy/launch-angle-explained.html deleted file mode 100644 index 90ad6aaf5..000000000 --- a/docs/legacy/launch-angle-explained.html +++ /dev/null @@ -1,814 +0,0 @@ - - - - - -How OpenFlight Measures Launch Angle - - - -
- -
-
OpenFlight · K-LD7 Vertical Radar
-

How OpenFlight Measures Launch Angle

-

Why this is one of the hardest numbers to get out of a $40 radar, what the - ground does to the signal, and how the two-ray method plus a - two-tier confidence system turns a noisy reflection into a usable launch angle.

-

A plain-language explainer. No prior context needed. Physics terms are defined the first time they appear and collected in the glossary.

-
- -
-
⚡ The 60-second version
-

We point a small Doppler radar up the ball's flight to measure its launch angle (how steeply it leaves the face). The problem: the radar also sees the ball's reflection off the floor — a mirror-image ball that appears below the surface, the way a tree hangs upside-down in a still lake. Those two signals blend together and drag the measured angle too low.

-

The two-ray method mathematically pulls the real ball apart from its floor-reflection. When that separation is clean — which we expect over a hard, flat surface (an indoor floor, or a mat on concrete) — we trust the geometry directly (Tier 1). When it isn't — typically a soft, scattering surface like grass — we fall back to a lower-confidence estimate and, if the shot reads suspiciously flat, nudge it up toward what that club normally launches (Tier 2). The surface, not the venue, is what really decides it — though so far only the indoor case is measured.

-
- - - - -

1The job & the hardware

-

Launch angle is the vertical angle the ball leaves the clubface. A 7-iron tour -average is about 16°; a driver about 10°; a wedge can be 30°+. Get it wrong by a few -degrees and the simulated carry distance is off by yards.

- -

OpenFlight measures it with a K-LD7: a 24 GHz FSK Doppler radar the size of a matchbox. -It sits low, near the tee, aimed up the flight line. Every frame it gives us two independent -readings of the ball — an angle and a range — and the launch-angle pipeline leans on both.

- -
- - - - - floor / turf - - - radar - - - boresight — radar aim ≈ 12.5° - - - tee - - - - - - frame 1 · 25 ms - frame 2 · 54 ms - - - - launch angle ≈ 16° - - - net / screen - 10 ft from ball - 15 ft from radar - radar setting: 5 m - - - 16.4 ft — range wraps here - - - - - - - - 5 ft - 10 ft - -
The setup, to scale. Radar 5 ft behind the ball, net 10 ft in front of it (15 ft from the -radar). Over this short window the ball climbs in a near-straight line at its launch angle — the trajectory -only curves into an arc much farther downrange. The slope of that climb is what we measure, from the -two frames we typically get before the ball reaches the net (here at 25 ms and 54 ms — see limit ①). -The dashed line is the radar's aim (boresight); the ball flies below it through this whole window. (A separate -horizontal radar handles left/right aim; this document is only about the vertical launch-angle radar.)
-
- -

Reading 1 · the angle — where the ball sits in the beam

-

The radar has two receive antennas; the tiny phase difference between them encodes the ball's -elevation angle each frame (this is interferometry). Stack those angles over the first few feet and -the rise of the climb is the launch angle. It's the headline number — and the one a floor reflection -corrupts (sections 2–3).

- -

Reading 2 · the range — how far the ball is

-

Being an FSK radar, the K-LD7 also reports the ball's distance (slant range) every frame, from -the phase of a second transmit frequency. Three things make it the quiet workhorse of the whole method:

-
    -
  • It's accurate — and largely multipath-immune. Range is a distance (time-of-flight), which the - floor bounce barely shifts; the angle, by contrast, arrives off the floor from a completely different direction - and gets wrecked. So the range is the one clean, trustworthy clock in the signal.
  • -
  • It pins the moment of impact. The ball's range grows steadily as it flies — extend that line back to - the tee distance (~5 ft) and you've found when impact happened, the timing anchor everything else hangs - off. (Range + elevation each frame also fix the ball's actual position, which is what Tier 1 fits a line through.)
  • -
  • It comes in sub-frames. Each ~29 ms frame is sliced into about a dozen overlapping short windows, - each yielding its own range sample — finer timing than one reading per frame, which sharpens that impact anchor.
  • -
-
- - - - range (ft) - milliseconds after impact - 5 - 10 - 15 - 0 - 20 - 40 - 60 - - 16.4 ft — range wraps above here - - - - frame 1 · ~29 ms - frame 2 · ~29 ms - ↑ frames touch - - - - - tee ≈ 5 ft = impact (t=0) - fit + run back to tee - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - range — ~13 samples / frame - angle — one value / frame - -
One frame, many range samples. The radar steps 13 overlapping short windows across each -~29 ms frame, and the frames run back-to-back with no gap; every window yields its own range dot (gold). -That dense, climbing track is fit to a line and run back to the tee distance (~5 ft) to pin the moment of -impact, and it stays below the 16.4 ft wrap indoors. Only the range sub-divides like this — the -angle is read once per frame (the sub-frame fringe it carries is consumed by the two-ray fit in -section 4 rather than kept as a track).
-
-

Range has just one catch — it wraps past 16.4 ft — which is limit ② below.

- - -

2Three hard limits of the K-LD7

-

Everything downstream exists to work around these. None of them is a bug — they're the -physics of a cheap, short-range radar.

- -

① You get one or two usable frames — and can't choose when

-

The radar free-runs at about 35 frames per second — one frame every ~29 ms, on its -clock. Impact lands at a random point in that cycle, so we can't pick when frames arrive; "give me a -frame at 20 ms" isn't an option. Indoors, with a net ~10 ft away, the ball is in clean view for only a -fraction of a second — room for two frames at best.

-
    -
  • Best case — 2 frames. Two frames land inside the clean window, e.g. 25 ms and - 54 ms after impact (one 29 ms step apart). Two points plus the known tee define the climb — - the only road into Tier 1.
  • -
  • Common case — 1 frame. If a frame fires right at contact, the ball is still on the tee in - the radar's blind/clutter zone, so that slot is wasted — leaving a single usable frame. One frame can't - show a climb, so it routes to a single-frame Tier 2 estimate.
  • -
-

There's no "collect more data" lever: 35 Hz is the radar's ceiling, and the impact-to-frame timing is -luck of the draw.

- -
- - - Best case — 2 good frames - - clean-view window (ball clear of the tee, not yet at the net) - - - impact - - - 25 ms - 54 ms - - 29 ms apart - - → Tier 1 ✓ - - - Common case — a frame fires at contact - - - - impact - - - - 3 ms - 32 ms - 61 ms - wasted · ball at tee - too late · past net - - → Tier 2 · 1 frame - - - Same radar, two different shots — the only difference is where impact happened to fall in the 29 ms frame clock. - -
It's the luck of the timing. The 29 ms spacing is fixed by the radar, but where impact -lands within that cycle is random shot-to-shot. Catch two frames in the clean window and Tier 1 is on the -table; lose one to the moment of contact and you're left with a single-frame Tier 2 estimate. We can't -request a frame at a chosen time.
-
- -

② Range "wraps" past 16.4 ft

-

The radar measures distance using a phase that resets every cycle. At the 5 m range setting, -that cycle covers 16.4 ft. A ball truly at 23 ft reports as 23 − 16.4 = 6.6 ft — it -"wraps," like a clock hand passing 12. For close nets this never matters; for far nets or screens -it does, which is why OpenFlight can un-wrap these readings when you tell it the net distance.

- -
- - - - TRUE range - - 016.4 ft23 ft - - - - real ball - - - - - what radar REPORTS - - "6.6 ft" - - - -
The 16.4-ft clock. Anything past one full cycle folds back to the start. Knowing the -physical net distance lets OpenFlight add the cycle back ("de-aliasing") instead of believing the folded value.
-
- -

③ The ground corrupts the angle

-

That delicate interferometric phase — the angle reading from section 1 — is exactly what a reflection -off the floor corrupts. It's the central problem, and it earns the next section.

- - -

3The core problem: two rays

-

A radar aimed low at a ball over a floor never sees just the ball. It sees the ball -twice.

- -

One signal travels straight to the ball and back: the direct ray. A second bounces off the floor -on the way — the ground-reflected ray. To the radar, that reflected ray looks like it came from a -mirror-image ball below the floor, exactly the way a tree is mirrored beneath the surface of a still lake. -This is the classic two-ray ground-reflection situation.

- -
- - - - floor (mirror) - - - radar - - - real ball - - - "image" ball (reflection) - - - direct ray - - - - - reflected ray (bounces off floor) - - - - - - θ ball (up) - - θ image (down) - -
One ball, two echoes. The radar can't natively tell the direct ray from the floor-bounce. -It reports a blend of the two angles — and because the image sits below the floor, that blend is -pulled downward. The result: launch angles that read too flat.
-
- -

This shows up as "suppression"

-

When the two echoes blend, a ball that truly launched at 17° can be measured at . We call -this suppression — the floor image quietly suppresses the apparent launch angle. It's worst when -the ball is still low (early in flight), because that's when the real ball and its image are closest -together and hardest to separate.

- -
- - - - tee - - - true 17° - - - measured 9° - - - suppression gap - -
Suppression in one picture. This is the systematic error the rest of the pipeline is built to -detect and undo.
-
- - -

4Why "two-ray" demodulation

-

If the floor reflection is the problem, the fix is to model it on purpose and subtract it out — -rather than pretend the radar sees only the ball.

- -

The two-ray method treats each frame's signal as the sum of two pieces — a ball component and an -image component — and solves for both at once. Out of that fit we get the one number that matters: the -true ball elevation, with the floor image accounted for instead of contaminating it.

- -

The fit also hands us a quality signal we lean on constantly:

-
    -
  • maxsep — the angular separation between the ball and its image, at its largest across the - shot's frames. Big separation = the two rays are cleanly distinct = we can trust the decomposition. - Small separation = they're smeared together = don't trust it.
  • -
  • maxel — the highest elevation the ball was actually seen at. If even the ball's peak - reading is low, the shot probably reads suppressed.
  • -
- -
- - - what the raw radar gives - - - - one blended angle - - - two-ray fit - - - - after separation - - - - ball - - image - - maxsep - -
The whole idea. Split the blend into ball + image; keep the ball; measure how far apart they -were (maxsep) as a confidence signal. Large maxsep is what makes a trustworthy measurement possible.
-
- - -

5Why the same radar behaves differently indoors and outdoors

-

The two-ray method lives or dies on being able to separate the ball from its image. The -surface under the ball decides whether that's possible.

- -
- - - - - INDOOR · hard floor - - smooth → mirror-like (specular) - - - - - - - - - - sharp image - maxsep ≈ 11° ✓ separable - - - OUTDOOR · grass - - rough → scattered (diffuse) - - - - - - - - - - - - - smeared - maxsep ≈ 2.8° ✗ collapses - -
Surface decides everything. A hard indoor floor reflects like a mirror — one clean image, wide -separation, the two-ray fit succeeds. Grass scatters the reflection — the image smears out, separation -collapses, and the fit can no longer be trusted.
-
- -
-
🔬 Careful what we claim here
-

What we've measured: indoor shots show maxsep ≈ 11°, outdoor shots ≈ 2.8°, and - the outdoor fit error runs ~2.5× higher. Interestingly, the reflection's strength is about the - same in both (the image is ~88% as strong either way) — it's the angular separation we can resolve that - collapses outdoors, not the amount of reflected energy.

-

The specular-vs-diffuse story above is our working explanation for that, and it's - consistent with the data — but the exact mechanism hasn't been nailed down. The behavior split is solid; - the physics label on it is a hypothesis.

-
- - -

6Tier 1: a real measurement

-

When the separation is clean, we don't guess — we read the launch angle straight off the -geometry. That's Tier 1, and it's the only tier we call a true measurement.

- -

A shot earns Tier 1 only if all four of these hold:

- - - - - - - - -
GateMeaning7-iron value
la_position existsA range-based, timing-free angle could be fitrequired
nval ≥ 2At least 2 clean frames survived≥ 2
maxsep ≥ 9°Ball & image were well separated≥ 9°
maxel ≥ tour−7.3°Ball was seen at a believable height≥ 9.0°
- -

The angle itself comes from the position fit (la_position): draw the straight line from the -fixed tee through the clean (range, elevation) points the radar measured. The slope of that line is the -launch direction. No clock, no trajectory model, no extrapolation — just geometry anchored at a point we -know exactly. Tier 1 ships with confidence 0.85.

- -
- - - - - tee (anchor) - - - - frame 1 · 25 ms - frame 2 · 54 ms - - - - floor image (far below → big maxsep) - - - line through tee + 2 frames - = launch angle - - - maxsep - -
What a Tier-1 shot looks like. The realistic best case: two clean frames, the ball -well above its floor image (wide maxsep), both anchored to the known tee. The line through tee + the two -frames is the launch angle — confidence 0.85. (Two frames is as good as it gets — see limit ①.)
-
- - -

7Tier 2 & the boost

-

A shot lands in Tier 2 for one of two reasons: the ball/image separation collapsed -(usually grass), or there was only one good frame — most often because a frame burned at contact (limit ①), -so there's no second point to fit a climb. Either way we still want to show the player something, so Tier 2 -is a lower-confidence estimate.

- -

The three outcomes map straight to the UI's confidence dots — Tier 1 → 3 dots -(0.85), Tier 2 as-measured → 2 dots (0.65), Tier 2 -boosted → 1 dot (0.35) — an honest measured → estimated → corrected gradient. Tier 2 has two flavors:

- -
-
-
Tier 2 · as-measured
-

Reading looks plausible

-
confidence 0.65 · 2 dots · no boost
-

The ball reached a believable height (maxel is not suspiciously - low). We show the estimate as-is, just with lower confidence than Tier 1.

-
-
-
Tier 2 · boosted
-

Reading looks suppressed

-
confidence 0.35 · 1 dot · +boost applied
-

Even the ball's peak elevation is low (maxel < 0.43 × tour). - That's the fingerprint of suppression, so we add a fixed boost toward the club's tour-average launch.

-
-
- -

The boost is derived per club from its tour-average launch — no hand-tuning. For a 7-iron (tour 16.3°) the -trigger is maxel < 7° and the boost is +4.0°. It's a blunt, fixed nudge: enough to undo the -typical suppression, applied whenever the suppression fingerprint is present.

- -
- - - the boost - - - - measured (suppressed) - - after +boost ≈ tour avg - - +4° - - - - - the honest catch - - - - - same low reading… - - - …both boosted here - a suppressed 17° and a genuinely-thin 9°look identical to the radar - -
Why the boost is a trade-off. Suppression and a genuinely thin/skulled shot produce the -same low reading. The boost fixes the common case (suppression) but will lift a real thin shot too — we -can't yet tell them apart. We keep the boost because, on validated data, it lowers overall error -(1.56° with it vs 1.87° without).
-
- -
-
⚠ The boost's known blind spot
-

A boosted Tier-2 number will read too high on a genuinely thin or skulled shot (by roughly the boost - amount). That's a deliberate, measured trade: it's right far more often than it's wrong. Distinguishing - "suppressed" from "actually thin" — e.g. via smash factor — is an open problem, not yet solved.

-
- - -

8The whole decision flow

-

Here's how a single shot travels from raw frames to the number on screen.

- -
- - - - - - - - two-ray estimate - ball elevation per frame - - - - Tier-1 gate? - nval≥2 · maxsep≥9 - maxel ok · pos fit - - - yes - - TIER 1 · 0.85 - "measured" - - - no - - reads suppressed? - maxel < 0.43 × tour - - - yes - - TIER 2 · 0.35 - + boost → tour avg - - - no - - TIER 2 · 0.65 - as-measured - -
Tier first, boost second. A clean shot is a measurement (Tier 1). Everything else is a -lower-confidence estimate (Tier 2), boosted only when it carries the suppression fingerprint. Every club is -tour-derived, so two_ray runs on all of them.
-
- -

One more gate: the screen

-

The server has the final say on what's shown, checking the confidence against a display floor of -0.65. Tier 1 (0.85) and as-measured Tier 2 (0.65) clear it and display as radar -measurements. A boosted Tier 2 sits at 0.35 — below the floor on purpose: a boosted shot is barely a -measurement, so in normal play the server shows the simple ball-speed-and-club formula instead. A -test mode can bypass the floor to surface every shot the radar catches — that's when the boosted reading -appears as its 1-dot self.

- - -

9What's proven vs. what's assumed

-

Three weeks of work taught us to be precise about confidence. Here's the honest ledger.

- -
-
Solid
-
    -
  • The 7-iron is TrackMan-validated: on the 6/15 session, Tier-1 shots hit 0.68° mean error. The 7-iron's gate, trigger, and boost are tuned to that ground truth.
  • -
  • The indoor/outdoor maxsep split (≈11° vs ≈2.8°) is measured and repeatable.
  • -
  • Keeping the boost is net-positive on validated data (1.56° vs 1.87° overall error).
  • -
-
- -
-
Principled, but not yet ground-truthed
-
    -
  • All clubs are tour-derived by one uniform formula (no hand-tuned overrides); the coefficients are - seeded so it reproduces the 7-iron's TrackMan-validated config. Only the 7-iron has been checked - against ground truth, so treat the other 19 as principled defaults to refine as data arrives.
  • -
  • The pitching wedge in particular measured steeper than the linear trend predicts, so its boost is - likely a touch low — flagged for a per-club override if it reads flat in practice.
  • -
  • The specular-vs-diffuse explanation for the outdoor collapse is a working hypothesis, not a - proven mechanism.
  • -
-
- -
-
Known gaps
-
    -
  • We can't yet distinguish a suppressed shot from a genuinely thin one — the boost helps the first and hurts the second.
  • -
  • Outdoors, there's no validated Tier-1 path — grass rarely produces the clean separation Tier-1 requires, so outdoor shots lean on Tier-2.
  • -
  • Very fast / very low shots can hit the radar's blind spots (the DC clutter zone and the range wrap) and get refused.
  • -
  • Low-launch clubs (driver, woods) are the toughest case — and untested. A ~10° ball climbs only - ~1.8 ft over the window, so it never gets far from its floor image and the maxsep Tier 1 needs - (≥9°, the same for every club) barely develops; at 150–165 mph it also clears the net in ~1.5 frames, so even - getting two clean frames is hard. Expect these clubs to sit in Tier 2 — often single-frame — on a small - (~2.5°), unvalidated boost. This is reasoning, not data: no driver/wood shots have been collected yet.
  • -
-
- - -

10Glossary

- - - - - - - - - - - - - - -
Launch angleVertical angle the ball leaves the clubface. The number this whole pipeline exists to produce.
ElevationThe ball's vertical angle as seen by the radar in a given frame. Launch angle is reconstructed from how elevation climbs.
BoresightThe direction the radar is actually pointed (mount tilt + offset). Angles are measured relative to it.
Two-ray / multipathThe radar receives a direct echo and a floor-reflected echo. The reflection mimics a mirror-image ball below the floor.
SuppressionThe downward bias on measured launch angle caused by the floor image blending with the ball.
maxsepLargest angular separation between ball and image across a shot's frames. The two-ray method's core confidence signal — big = trustworthy.
maxelHighest elevation the ball was seen at. A low maxel is the fingerprint of a suppressed shot.
la_positionTiming-free launch angle from fitting a line through the tee and the measured (range, elevation) points. Tier-1's output.
Specular vs. diffuseA mirror-like (smooth/hard) reflection vs. a scattered (rough/grassy) one. Our explanation for why indoor separates and outdoor doesn't.
FSK range wrapThe radar's distance reading resets every 16.4 ft (at the 5 m setting), so far balls fold back to small values until "de-aliased."
Tier 1 / Tier 2Trusted measurement (0.85 → 3 dots) vs. lower-confidence estimate: Tier-2 is 0.65 (2 dots) as-measured, or 0.35 (1 dot) when boosted toward tour average because it reads suppressed.
- -
- - -
- - diff --git a/docs/legacy/launch-angle-explained.md b/docs/legacy/launch-angle-explained.md new file mode 100644 index 000000000..804425826 --- /dev/null +++ b/docs/legacy/launch-angle-explained.md @@ -0,0 +1,210 @@ +--- +icon: lucide/triangle +--- + +# How OpenFlight Measures Launch Angle + +!!! warning "DEPRECATED HARDWARE" + + This explains the **K-LD7** launch-angle method. The K-LD7 angle radars are + deprecated; the supported angle radar is the TI IWR6843 — see + [Launch angle](../how-it-works/launch-angle.md) for the current pipeline. + Kept because the two-ray multipath reasoning here still explains why indoor + launch angle is hard. + +Why this is one of the hardest numbers to get out of a $40 radar, what the ground does to the signal, and how the **two-ray** method plus a two-tier confidence system turns a noisy reflection into a usable launch angle. + +A plain-language explainer. No prior context needed. Physics terms are defined the first time they appear and collected in the glossary. + +We point a small Doppler radar up the ball's flight to measure its **launch angle** (how steeply it leaves the face). The problem: the radar also sees the ball's **reflection off the floor** — a mirror-image ball that appears below the surface, the way a tree hangs upside-down in a still lake. Those two signals blend together and drag the measured angle **too low**. + +The **two-ray method** mathematically pulls the real ball apart from its floor-reflection. When that separation is clean — which we expect over a **hard, flat surface** (an indoor floor, or a mat on concrete) — we trust the geometry directly (**Tier 1**). When it isn't — typically a **soft, scattering surface** like grass — we fall back to a lower-confidence estimate and, if the shot reads suspiciously flat, **nudge it up** toward what that club normally launches (**Tier 2**). The surface, not the venue, is what really decides it — though so far only the indoor case is measured. + +## 1. The job & the hardware + +Launch angle is the vertical angle the ball leaves the clubface. A 7-iron tour average is about **16°**; a driver about **10°**; a wedge can be **30°+**. Get it wrong by a few degrees and the simulated carry distance is off by yards. + +OpenFlight measures it with a **K-LD7**: a 24 GHz **FSK Doppler radar** the size of a matchbox. It sits low, near the tee, **aimed up the flight line**. Every frame it gives us **two independent readings** of the ball — an **angle** and a **range** — and the launch-angle pipeline leans on both. + +![Radar setup geometry, drawn to scale](../assets/legacy/kld7-angle-01.svg) + +*The setup, to scale.* Radar 5 ft behind the ball, net 10 ft in front of it (15 ft from the radar). Over this short window the ball climbs in a near-straight line at its launch angle — the trajectory only curves into an arc much farther downrange. The slope of that climb is what we measure, from the **two frames** we typically get before the ball reaches the net (here at 25 ms and 54 ms — see limit ①). The dashed line is the radar's aim (boresight); the ball flies below it through this whole window. (A separate horizontal radar handles left/right aim; this document is only about the **vertical** launch-angle radar.) + +### Reading 1 · the angle — where the ball sits in the beam + +The radar has **two receive antennas**; the tiny **phase difference** between them encodes the ball's **elevation angle** each frame (this is **interferometry**). Stack those angles over the first few feet and the **rise** of the climb is the launch angle. It's the headline number — and the one a floor reflection corrupts (sections 2–3). + +### Reading 2 · the range — how far the ball is + +Being an **FSK** radar, the K-LD7 also reports the ball's **distance** (slant range) every frame, from the phase of a second transmit frequency. Three things make it the quiet workhorse of the whole method: + +- **It's accurate — and largely multipath-immune.** Range is a *distance* (time-of-flight), which the floor bounce barely shifts; the angle, by contrast, arrives off the floor from a completely different direction and gets wrecked. So the range is the one **clean, trustworthy clock** in the signal. +- **It pins the moment of impact.** The ball's range grows steadily as it flies — extend that line back to the tee distance (~5 ft) and you've found **when impact happened**, the timing anchor everything else hangs off. (Range + elevation each frame also fix the ball's actual position, which is what Tier 1 fits a line through.) +- **It comes in sub-frames.** Each ~29 ms frame is sliced into about a dozen **overlapping short windows**, each yielding its own range sample — finer timing than one reading per frame, which sharpens that impact anchor. + +![Range sub-frames over time](../assets/legacy/kld7-angle-02.svg) + +*One frame, many range samples.* The radar steps 13 **overlapping short windows** across each ~29 ms frame, and the frames run **back-to-back with no gap**; every window yields its own range dot (gold). That dense, climbing track is fit to a line and run back to the tee distance (~5 ft) to pin the moment of impact, and it stays below the 16.4 ft wrap indoors. Only the **range** sub-divides like this — the **angle** is read once per frame (the sub-frame fringe it carries is consumed by the two-ray fit in section 4 rather than kept as a track). + +Range has just one catch — it **wraps** past 16.4 ft — which is limit ② below. + +## 2. Three hard limits of the K-LD7 + +Everything downstream exists to work around these. None of them is a bug — they're the physics of a cheap, short-range radar. + +### ① You get one or two usable frames — and can't choose when + +The radar free-runs at about **35 frames per second** — one frame every **~29 ms**, on *its* clock. Impact lands at a random point in that cycle, so **we can't pick when frames arrive**; "give me a frame at 20 ms" isn't an option. Indoors, with a net ~10 ft away, the ball is in clean view for only a fraction of a second — room for **two frames at best**. + +- **Best case — 2 frames.** Two frames land inside the clean window, e.g. **25 ms** and **54 ms** after impact (one 29 ms step apart). Two points plus the known tee define the climb — the only road into **Tier 1**. +- **Common case — 1 frame.** If a frame fires **right at contact**, the ball is still on the tee in the radar's blind/clutter zone, so that slot is wasted — leaving a single usable frame. One frame can't show a climb, so it routes to a single-frame **Tier 2** estimate. + +There's no "collect more data" lever: 35 Hz is the radar's ceiling, and the impact-to-frame timing is luck of the draw. + +![Two frame-timing scenarios: 2 frames vs 1 frame](../assets/legacy/kld7-angle-03.svg) + +*It's the luck of the timing.* The 29 ms spacing is fixed by the radar, but where impact lands within that cycle is random shot-to-shot. Catch two frames in the clean window and Tier 1 is on the table; lose one to the moment of contact and you're left with a single-frame Tier 2 estimate. We can't request a frame at a chosen time. + +### ② Range "wraps" past 16.4 ft + +The radar measures distance using a phase that resets every cycle. At the 5 m range setting, that cycle covers **16.4 ft**. A ball truly at 23 ft reports as 23 − 16.4 = **6.6 ft** — it "wraps," like a clock hand passing 12. For close nets this never matters; for **far nets or screens** it does, which is why OpenFlight can *un-wrap* these readings when you tell it the net distance. + +![Range wrapping past 16.4 feet](../assets/legacy/kld7-angle-04.svg) + +*The 16.4-ft clock.* Anything past one full cycle folds back to the start. Knowing the physical net distance lets OpenFlight add the cycle back ("de-aliasing") instead of believing the folded value. + +### ③ The ground corrupts the angle + +That delicate interferometric phase — the angle reading from section 1 — is exactly what a reflection off the floor corrupts. It's the central problem, and it earns the next section. + +## 3. The core problem: two rays + +A radar aimed low at a ball over a floor never sees just the ball. It sees the ball **twice**. + +One signal travels straight to the ball and back: the **direct ray**. A second bounces off the floor on the way — the **ground-reflected ray**. To the radar, that reflected ray looks like it came from a **mirror-image ball below the floor**, exactly the way a tree is mirrored beneath the surface of a still lake. This is the classic **two-ray ground-reflection** situation. + +![Two-ray ground reflection geometry](../assets/legacy/kld7-angle-05.svg) + +*One ball, two echoes.* The radar can't natively tell the direct ray from the floor-bounce. It reports a **blend** of the two angles — and because the image sits *below* the floor, that blend is pulled **downward**. The result: launch angles that read too flat. + +### This shows up as "suppression" + +When the two echoes blend, a ball that truly launched at **17°** can be measured at **9°**. We call this **suppression** — the floor image quietly suppresses the apparent launch angle. It's worst when the ball is still low (early in flight), because that's when the real ball and its image are closest together and hardest to separate. + +![Suppression: true vs measured angle](../assets/legacy/kld7-angle-06.svg) + +*Suppression in one picture.* This is the systematic error the rest of the pipeline is built to detect and undo. + +## 4. Why "two-ray" demodulation + +If the floor reflection is the problem, the fix is to **model it on purpose** and subtract it out — rather than pretend the radar sees only the ball. + +The **two-ray method** treats each frame's signal as the sum of two pieces — a ball component and an image component — and solves for both at once. Out of that fit we get the one number that matters: the **true ball elevation**, with the floor image accounted for instead of contaminating it. + +The fit also hands us a quality signal we lean on constantly: + +- **maxsep** — the angular **separation** between the ball and its image, at its largest across the shot's frames. Big separation = the two rays are cleanly distinct = we can trust the decomposition. Small separation = they're smeared together = don't trust it. +- **maxel** — the highest **elevation** the ball was actually seen at. If even the ball's peak reading is low, the shot probably reads suppressed. + +![Decomposition into ball and image components](../assets/legacy/kld7-angle-07.svg) + +*The whole idea.* Split the blend into ball + image; keep the ball; measure how far apart they were (**maxsep**) as a confidence signal. Large maxsep is what makes a trustworthy measurement possible. + +## 5. Why the same radar behaves differently indoors and outdoors + +The two-ray method lives or dies on being able to **separate** the ball from its image. The surface under the ball decides whether that's possible. + +![Indoor specular vs outdoor diffuse reflection](../assets/legacy/kld7-angle-08.svg) + +*Surface decides everything.* A hard indoor floor reflects like a mirror — one clean image, wide separation, the two-ray fit succeeds. Grass scatters the reflection — the image smears out, separation collapses, and the fit can no longer be trusted. + +What we've **measured**: indoor shots show `maxsep ≈ 11°`, outdoor shots `≈ 2.8°`, and the outdoor fit error runs **~2.5× higher**. Interestingly, the reflection's *strength* is about the same in both (the image is ~88% as strong either way) — it's the **angular separation we can resolve** that collapses outdoors, not the amount of reflected energy. + +The **specular-vs-diffuse** story above is our working *explanation* for that, and it's consistent with the data — but the exact mechanism hasn't been nailed down. The behavior split is solid; the physics label on it is a hypothesis. + +## 6. Tier 1: a real measurement + +When the separation is clean, we don't guess — we read the launch angle straight off the geometry. That's **Tier 1**, and it's the only tier we call a true measurement. + +A shot earns Tier 1 only if **all four** of these hold: + +| Gate | Meaning | 7-iron value | +| --- | --- | --- | +| `la_position` exists | A range-based, timing-free angle could be fit | required | +| `nval ≥ 2` | At least 2 clean frames survived | ≥ 2 | +| `maxsep ≥ 9°` | Ball & image were well separated | ≥ 9° | +| `maxel ≥ tour−7.3°` | Ball was seen at a believable height | ≥ 9.0° | + +The angle itself comes from the **position fit** (`la_position`): draw the straight line from the fixed tee through the clean (range, elevation) points the radar measured. The slope of that line **is** the launch direction. No clock, no trajectory model, no extrapolation — just geometry anchored at a point we know exactly. Tier 1 ships with **confidence 0.85**. + +![A clean Tier 1 shot, two frames](../assets/legacy/kld7-angle-09.svg) + +*What a Tier-1 shot looks like.* The realistic best case: **two** clean frames, the ball well above its floor image (wide maxsep), both anchored to the known tee. The line through tee + the two frames is the launch angle — confidence 0.85. (Two frames is as good as it gets — see limit ①.) + +## 7. Tier 2 & the boost + +A shot lands in **Tier 2** for one of two reasons: the ball/image **separation collapsed** (usually grass), or there was **only one good frame** — most often because a frame burned at contact (limit ①), so there's no second point to fit a climb. Either way we still want to show the player *something*, so Tier 2 is a lower-confidence estimate. + +The three outcomes map straight to the UI's confidence dots — **Tier 1 → 3 dots** (0.85), **Tier 2 as-measured → 2 dots** (0.65), **Tier 2 boosted → 1 dot** (0.35) — an honest *measured → estimated → corrected* gradient. Tier 2 has two flavors: + +#### Reading looks plausible + +The ball reached a believable height (`maxel` is not suspiciously low). We show the estimate as-is, just with lower confidence than Tier 1. + +#### Reading looks suppressed + +Even the ball's *peak* elevation is low (`maxel < 0.43 × tour`). That's the fingerprint of suppression, so we **add a fixed boost** toward the club's tour-average launch. + +The boost is derived per club from its tour-average launch — no hand-tuning. For a 7-iron (tour 16.3°) the trigger is `maxel < 7°` and the boost is **+4.0°**. It's a blunt, fixed nudge: enough to undo the typical suppression, applied whenever the suppression fingerprint is present. + +![Tier 2 boost and the thin-shot ambiguity](../assets/legacy/kld7-angle-10.svg) + +*Why the boost is a trade-off.* Suppression and a genuinely thin/skulled shot produce the *same* low reading. The boost fixes the common case (suppression) but will lift a real thin shot too — we can't yet tell them apart. We keep the boost because, on validated data, it lowers overall error (**1.56°** with it vs **1.87°** without). + +A boosted Tier-2 number will read **too high on a genuinely thin or skulled shot** (by roughly the boost amount). That's a deliberate, measured trade: it's right far more often than it's wrong. Distinguishing "suppressed" from "actually thin" — e.g. via smash factor — is an open problem, not yet solved. + +## 8. The whole decision flow + +Here's how a single shot travels from raw frames to the number on screen. + +![End to end decision flow](../assets/legacy/kld7-angle-11.svg) + +*Tier first, boost second.* A clean shot is a measurement (Tier 1). Everything else is a lower-confidence estimate (Tier 2), boosted only when it carries the suppression fingerprint. Every club is tour-derived, so two_ray runs on all of them. + +### One more gate: the screen + +The server has the final say on what's *shown*, checking the confidence against a display floor of **0.65**. **Tier 1 (0.85)** and **as-measured Tier 2 (0.65)** clear it and display as radar measurements. A **boosted Tier 2 sits at 0.35 — below the floor on purpose**: a boosted shot is barely a measurement, so in normal play the server shows the simple **ball-speed-and-club formula** instead. A **test mode** can bypass the floor to surface every shot the radar catches — that's when the boosted reading appears as its 1-dot self. + +## 9. What's proven vs. what's assumed + +Three weeks of work taught us to be precise about confidence. Here's the honest ledger. + +- The **7-iron is TrackMan-validated**: on the 6/15 session, Tier-1 shots hit **0.68° mean error**. The 7-iron's gate, trigger, and boost are tuned to that ground truth. +- The **indoor/outdoor maxsep split** (≈11° vs ≈2.8°) is measured and repeatable. +- Keeping the **boost is net-positive** on validated data (1.56° vs 1.87° overall error). + +- **All clubs are tour-derived** by one uniform formula (no hand-tuned overrides); the coefficients are seeded so it reproduces the 7-iron's TrackMan-validated config. Only the **7-iron** has been checked against ground truth, so treat the other 19 as principled defaults to refine as data arrives. +- The **pitching wedge** in particular measured steeper than the linear trend predicts, so its boost is likely a touch low — flagged for a per-club override if it reads flat in practice. +- The **specular-vs-diffuse** explanation for the outdoor collapse is a working hypothesis, not a proven mechanism. + +- We **can't yet distinguish a suppressed shot from a genuinely thin one** — the boost helps the first and hurts the second. +- Outdoors, there's **no validated Tier-1 path** — grass rarely produces the clean separation Tier-1 requires, so outdoor shots lean on Tier-2. +- Very fast / very low shots can hit the radar's blind spots (the DC clutter zone and the range wrap) and get refused. +- **Low-launch clubs (driver, woods) are the toughest case — and untested.** A ~10° ball climbs only ~1.8 ft over the window, so it never gets far from its floor image and the `maxsep` Tier 1 needs (≥9°, the same for every club) barely develops; at 150–165 mph it also clears the net in ~1.5 frames, so even getting two clean frames is hard. Expect these clubs to sit in Tier 2 — often single-frame — on a small (~2.5°), unvalidated boost. This is reasoning, not data: no driver/wood shots have been collected yet. + +## 10. Glossary + +| **Launch angle** | Vertical angle the ball leaves the clubface. The number this whole pipeline exists to produce. | +| --- | --- | +| **Elevation** | The ball's vertical angle *as seen by the radar* in a given frame. Launch angle is reconstructed from how elevation climbs. | +| **Boresight** | The direction the radar is actually pointed (mount tilt + offset). Angles are measured relative to it. | +| **Two-ray / multipath** | The radar receives a direct echo *and* a floor-reflected echo. The reflection mimics a mirror-image ball below the floor. | +| **Suppression** | The downward bias on measured launch angle caused by the floor image blending with the ball. | +| **maxsep** | Largest angular separation between ball and image across a shot's frames. The two-ray method's core confidence signal — big = trustworthy. | +| **maxel** | Highest elevation the ball was seen at. A low maxel is the fingerprint of a suppressed shot. | +| **la_position** | Timing-free launch angle from fitting a line through the tee and the measured (range, elevation) points. Tier-1's output. | +| **Specular vs. diffuse** | A mirror-like (smooth/hard) reflection vs. a scattered (rough/grassy) one. Our explanation for why indoor separates and outdoor doesn't. | +| **FSK range wrap** | The radar's distance reading resets every 16.4 ft (at the 5 m setting), so far balls fold back to small values until "de-aliased." | +| **Tier 1 / Tier 2** | Trusted measurement (0.85 → 3 dots) vs. lower-confidence estimate: Tier-2 is 0.65 (2 dots) as-measured, or 0.35 (1 dot) when boosted toward tour average because it reads suppressed. | + +Scope: the K-LD7 **vertical** (launch-angle) radar. Companion deep-dives in this repo: `kld7.md` (setup & usage), `kld7-ball-detection-theory.md`, `kld7-subframe-stft-findings.md`. Tier thresholds and the boost live in `src/openflight/kld7/two_ray.py` (`classify_two_ray_tier`). This is an explainer, not a spec — the code is the source of truth. diff --git a/docs/reference/datasheets.md b/docs/reference/datasheets.md index d45c20191..28760a835 100644 --- a/docs/reference/datasheets.md +++ b/docs/reference/datasheets.md @@ -39,7 +39,7 @@ than checked-in copies: For what OpenFlight actually does with the device, the [firmware developer guide](../development/firmware.md) and the -[launch angle field report](../iwr6843_field_report_2026-07.html) are more useful than +[launch angle field report](../how-it-works/launch-angle.md) are more useful than the TI documents. ## Other hardware diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 19cd9a5a4..5733b1424 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -253,6 +253,11 @@ .md-header { border-bottom: 1px solid var(--md-default-fg-color--lightest); + /* The header deliberately matches the page ground rather than using the + primary colour — but it must be *opaque*, or content scrolling underneath + the sticky tabs bar shows through the wordmark. */ + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); } .md-header__button.md-logo :is(img, svg) { @@ -269,6 +274,20 @@ .md-tabs { border-bottom: 1px solid var(--md-default-fg-color--lightest); + /* Opaque for the same reason as .md-header — navigation.tabs.sticky keeps + this bar pinned while the page scrolls beneath it. */ + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); +} + +.md-tabs__link { + opacity: 1; + color: var(--md-default-fg-color--light); +} + +.md-tabs__link:is(:hover, :focus), +.md-tabs__link--active { + color: var(--md-default-fg-color); } /* Sidebar section labels get the marketing site's eyebrow treatment */ diff --git a/zensical.toml b/zensical.toml index 664ab6e42..57c1c701a 100644 --- a/zensical.toml +++ b/zensical.toml @@ -73,6 +73,7 @@ nav = [ "how-it-works/index.md", "how-it-works/pipeline.md", "how-it-works/rolling-buffer.md", + "how-it-works/launch-angle.md", "how-it-works/ballistics.md", "how-it-works/positioning.md", ] }, @@ -98,12 +99,14 @@ nav = [ "development/spin-replay.md", "development/camera-yolo.md", "development/analysis-tooling.md", + "development/color-palette.md", ] }, { "Legacy (K-LD7)" = [ "legacy/index.md", "legacy/troubleshooting.md", "legacy/ball-detection-theory.md", + "legacy/launch-angle-explained.md", "legacy/session-review.md", ] }, @@ -161,7 +164,7 @@ language = "en" features = [ "announce.dismiss", "content.action.edit", - # "content.action.view", + "content.action.view", "content.code.annotate", "content.code.copy", "content.code.select", @@ -178,8 +181,9 @@ features = [ "navigation.path", # "navigation.prune", "navigation.sections", - # "navigation.tabs", - # "navigation.tabs.sticky", + # 11 top-level sections is a wall in a sidebar; tabs put them across the top. + "navigation.tabs", + "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", From e99ec22f6fcb44d8abcea027c5643062e9a614f0 Mon Sep 17 00:00:00 2001 From: Austin1 Date: Sun, 23 Aug 2026 13:37:50 -0500 Subject: [PATCH 06/10] test: follow the battery guides to their new paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Phase 2 restructure moved docs/battery/README.md → docs/using/battery.md and docs/battery/geekworm.md → docs/build/battery.md. These two tests read those files directly and broke on the move. The link assertion now checks for the actual relative link the provider overview uses, rather than a bare filename that no longer disambiguates — both files are named battery.md now. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W8y3SgA2agfyojFeiiz1xd --- tests/test_geekworm_setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_geekworm_setup.py b/tests/test_geekworm_setup.py index df66aa4fc..5bb65f5c2 100644 --- a/tests/test_geekworm_setup.py +++ b/tests/test_geekworm_setup.py @@ -12,8 +12,8 @@ PANEL_PATCH = ( PROJECT_ROOT / "scripts" / "battery" / "patches" / "wfplug-batt-capacity-and-power.patch" ) -BATTERY_GUIDE = PROJECT_ROOT / "docs" / "battery" / "README.md" -OPERATOR_GUIDE = PROJECT_ROOT / "docs" / "battery" / "geekworm.md" +BATTERY_GUIDE = PROJECT_ROOT / "docs" / "using" / "battery.md" +OPERATOR_GUIDE = PROJECT_ROOT / "docs" / "build" / "battery.md" MAIN_SETUP_SCRIPT = PROJECT_ROOT / "scripts" / "setup" / "setup.sh" @@ -135,7 +135,9 @@ def test_battery_guide_documents_provider_interface_and_cli(): assert "--battery geekworm" in guide assert "PowerReader" in guide - assert "geekworm.md" in guide + # The operator guide lives at docs/build/battery.md; this asserts the + # provider overview still links to it. + assert "../build/battery.md" in guide def test_main_setup_offers_geekworm_provisioning(): From 4d5992307fdfe109f1d8e06f9d6243ba98642fe2 Mon Sep 17 00:00:00 2001 From: Austin1 Date: Sun, 23 Aug 2026 14:18:01 -0500 Subject: [PATCH 07/10] Update zensical config --- zensical.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zensical.toml b/zensical.toml index 57c1c701a..c44298b25 100644 --- a/zensical.toml +++ b/zensical.toml @@ -1,5 +1,5 @@ [project] -site_url = "https://openflight.dev/docs/" +site_url = "https://austin1.github.io/openflight/" site_name = "OpenFlight Docs" # Config lives at the repo root so every existing `docs/…` link — from the From 645960dfcedf7b43c79cb15607a09815029670e3 Mon Sep 17 00:00:00 2001 From: Austin1 Date: Sun, 23 Aug 2026 18:27:55 -0500 Subject: [PATCH 08/10] docs: keep site_url on the canonical openflight.dev domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 4d59923 repointed site_url at a fork's GitHub Pages URL, which is right for that fork's deploy and wrong for this repo — it would set the canonical link and sitemap of the published site to the fork. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01STqqwp1ijuEZkjxtQoGmqY --- zensical.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zensical.toml b/zensical.toml index c44298b25..57c1c701a 100644 --- a/zensical.toml +++ b/zensical.toml @@ -1,5 +1,5 @@ [project] -site_url = "https://austin1.github.io/openflight/" +site_url = "https://openflight.dev/docs/" site_name = "OpenFlight Docs" # Config lives at the repo root so every existing `docs/…` link — from the From fa3fff357543609e0cc172988a41ea411706a009 Mon Sep 17 00:00:00 2001 From: Austin1 Date: Fri, 28 Aug 2026 15:15:54 -0500 Subject: [PATCH 09/10] docs nav and links fixing, trying to keep up with main --- docs/changelog.md | 2 +- docs/setup/raspberry-pi.md | 8 ++++---- docs/{ => setup}/splash-screen.md | 6 +++--- zensical.toml | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) rename docs/{ => setup}/splash-screen.md (96%) diff --git a/docs/changelog.md b/docs/changelog.md index 5d41eb8c8..c50ece53a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 visible. The footer logo opens units, dark/light theme, language, simulator, and ball-detection status; a persistent footer power button opens the shutdown confirmation. Club (or training implement) selection is a Live header action. - See the [UI README](../ui/README.md). + See the [UI README](https://github.com/jewbetcha/openflight/blob/main/ui/README.md). - **Kiosk languages.** English, Spanish, French, and Portuguese. Choice is stored in `localStorage` (`openflight.locale:v1`). - **Dark and light themes.** Toggle in the footer menu; stored as diff --git a/docs/setup/raspberry-pi.md b/docs/setup/raspberry-pi.md index 1a8c3112b..1deb1f8ff 100644 --- a/docs/setup/raspberry-pi.md +++ b/docs/setup/raspberry-pi.md @@ -160,8 +160,8 @@ reboot. Run the device wizard, then follow the legacy guide: ``` The wizard also installs the required FTDI low-latency rule. See -[Legacy K-LD7 Setup](kld7.md) for mounting and startup, and -[K-LD7 Troubleshooting](kld7-troubleshooting.md) for serial failures. +[Legacy K-LD7 Setup](../legacy/index.md) for mounting and startup, and +[K-LD7 Troubleshooting](../legacy/troubleshooting.md) for serial failures. ### Desktop Launcher And Startup Splash @@ -225,8 +225,8 @@ sudo systemctl restart openflight ./scripts/start-kiosk.sh --mock # Mock mode (no hardware needed) ``` -Use the [IWR6843 Operator Guide](iwr6843/README.md#start-openflight) or -[Legacy K-LD7 Setup](kld7.md) for angle-radar startup commands. +Use the [IWR6843 startup steps](../iwr6843/verify.md#start-openflight) or +[Legacy K-LD7 Setup](../legacy/index.md) for angle-radar startup commands. ### Manual Start diff --git a/docs/splash-screen.md b/docs/setup/splash-screen.md similarity index 96% rename from docs/splash-screen.md rename to docs/setup/splash-screen.md index 3b936bf8a..383d359bc 100644 --- a/docs/splash-screen.md +++ b/docs/setup/splash-screen.md @@ -11,7 +11,7 @@ scripts/start-kiosk.sh --startup-splash The splash is opt-in. Without the flag, `scripts/start-kiosk.sh` retains its existing startup behavior. -![OpenFlight starting the server and TI radar](assets/startup-splash-ti-loading.png) +![OpenFlight starting the server and TI radar](../assets/startup-splash-ti-loading.png) ## What It Shows @@ -101,7 +101,7 @@ spin with an opt-in kinematic estimate whenever a measured launch angle is available. Enable it only when that behavior is desired. The geometry above is an example, not a default. Follow the -[IWR6843 operator guide](iwr6843/README.md#measure-the-geometry) and enter the +[IWR6843 mounting guide](../iwr6843/mounting.md#measure-the-geometry) and enter the measurements from the actual installation. The example wrapper also holds a per-user launch lock. Repeated taps exit @@ -169,7 +169,7 @@ action, and retains the terminal-log location for diagnosis. | OPS unavailable | TI firmware wedged | |---|---| -| ![OPS radar startup failure](assets/startup-splash-ops-error.png) | ![TI radar reset guidance](assets/startup-splash-ti-error.png) | +| ![OPS radar startup failure](../assets/startup-splash-ops-error.png) | ![TI radar reset guidance](../assets/startup-splash-ti-error.png) | Select **Return to desktop** after a failure. This dismisses the error state and releases the launch lock before the next attempt. diff --git a/zensical.toml b/zensical.toml index 57c1c701a..b2811318d 100644 --- a/zensical.toml +++ b/zensical.toml @@ -51,6 +51,7 @@ nav = [ "setup/raspberry-pi.md", "setup/rolling-buffer.md", "setup/auto-start.md", + "setup/splash-screen.md", "setup/diagnostic.md", ] }, From a5638a8a17165d0f9d5b8d4a130bba30d7732c7e Mon Sep 17 00:00:00 2001 From: Austin1 Date: Fri, 28 Aug 2026 15:26:35 -0500 Subject: [PATCH 10/10] fix AI slop error --- .gitignore | 2 +- docs/archive/plans/2026-04-05-kld7-radc-processing-plan.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0545f5140..ec4b2b006 100644 --- a/.gitignore +++ b/.gitignore @@ -87,4 +87,4 @@ ui/test-results/ .crossnote/config.js .crossnote/head.html .crossnote/parser.js -.crossnote/style.less \ No newline at end of file +.crossnote/style.less diff --git a/docs/archive/plans/2026-04-05-kld7-radc-processing-plan.md b/docs/archive/plans/2026-04-05-kld7-radc-processing-plan.md index 0075127ba..c9c879036 100644 --- a/docs/archive/plans/2026-04-05-kld7-radc-processing-plan.md +++ b/docs/archive/plans/2026-04-05-kld7-radc-processing-plan.md @@ -1388,4 +1388,4 @@ Plan complete and saved to `docs/plans/2026-04-05-kld7-radc-processing-plan.md`. **2. Inline Execution** — Execute tasks in this session, batch execution with checkpoints -Which approach? \ No newline at end of file +Which approach?