The trust contract
How we measure your technique, and how confident we are.
Rallo is a technique coach. We claim observations about your stroke — knee load through the trophy pose, the timing of your rotation peaks when a slow-motion capture can resolve them, contact position relative to your body, racket path through impact. Every one of those carries a target band or a qualitative read, a set of capture conditions it depends on, and an honest statement of what we have and have not checked it against. Today that answer is the same for every metric: not yet checked against any independent reference. This page is the engineering contract behind those observations, in plain language. If a metric is missing here, it is not in the app.
What Rallo is not: a line judge. We do not call ball placement or in/out. We do not show a ball-flight map. The trust-contract wedge is the accuracy of what we say about your body, your racket, and your timing — not where the ball landed.
This page is that contract in full. The bands below are the accuracy targets each metric must meet to ship — they are targets, and none of them has been checked against an instrument or an independent human annotation. What we run today is a stability check against our own earlier output on the bundled clips, described exactly in section 02; the tighter instrument cross-checks a lab would use are named there, each with its real status — some planned, one not scheduled at all. When a target or a method changes, we log it at the bottom — no silent revisions.
v1 scope note (2026-05-17)
v1 ships the body-pose + scale-prior path. Metrics tagged COURT_CALIBRATED in the tables below are deferred to v1.1. That includes ball placement, ball speed, on-court player position, foot-fault checks, and court-frame metrics generally. The high-frame-rate metrics that would need 120/240 fps — outgoing ball speed, racket-head speed at impact, millisecond rotation timing, and brush distance through contact — are likewise deferred (v1.1–v1.5); v1 enforces no automatic frame-rate gate that drops a metric, and no such gate exists in the shipped build. Metric scale comes from your stored height and racket length rather than the court geometry — bands widen accordingly. The technique-coaching value prop ships intact.
01 /What we measure
Three confidence tiers, and one rule across all of them: no coaching read carries an exact figure, because we cannot yet put an interval on one. A metric we can measure and word robustly is published as a read in words, body-relative, with its derivation beside it. A metric whose wording would flip inside our own accuracy target, or that the metric matrix does not govern at all, is hidden and the app says why. The third tier below is the list of numbers we will not show you, and the reasons. The tables give each metric's accuracy target — the bar it has to clear before any figure could be published — not a measured result.
High confidence Read in words
Rendered as a plain-language read of what your body did, not as a figure. No exact value and no error band are shown, because we do not have an interval to put on one. The band in the table below is the accuracy target the metric must meet — a design target, not a measured result — and no measurement on this page has yet been checked against an instrument or an independent human annotation (section 02 sets out exactly what we do and do not run). Withholding the figure is a decision, not an oversight. We could print the number on its own, and it would look more precise than anything we can support; we could model an interval from tracking quality and print a plausible ±, and it would look more rigorous still. Tracking quality is a statement about how well we followed your joints, not about how close the resulting angle is to the truth, and presenting it as though it were the second would be the exact substitution this page exists to refuse. So the read is published in words that survive being moved by the whole accuracy target: if nudging the measurement to either edge of that target would change the wording, the read is withheld rather than hedged. The first check that could produce a real interval is independent human annotation, listed in section 02; when one exists, the figure and its interval will be published together with the reference that produced them. If the capture conditions for a metric fail, the metric is hidden and the app says why.
| Metric | Units | Target band (target, not measured result) | Capture required |
|---|---|---|---|
| Knee load at loading | degrees | ± 8° peak (geometry target, not a measured result). Deep flexion at the trophy is the hardest knee angle to read from a single camera, so this carries the widest band of the knee readings. | SIDE_VIEW |
| Leg drive at contact | degrees | ± 8° peak (geometry target, not a measured result). Read as knee extension, not as a height — a single uncalibrated camera cannot turn vertical travel into centimetres. | SIDE_VIEW |
| Leg drive into contact | degrees | ± 8° peak (geometry target, not a measured result). Same limitation as leg drive at contact: an angle change, never a distance. | SIDE_VIEW |
| Leg drive travel | degrees | ± 8° peak (geometry target, not a measured result). The span of knee extension across the drive, not a measured displacement. | SIDE_VIEW |
| Trunk balance at contact | degrees | ± 5° controlled, ± 8° peak (geometry target, not a measured result). Distinct from balance offset above, which is a centimetre reading. | SIDE_VIEW |
| Trunk extension at contact | degrees | ± 5° controlled, ± 8° peak (geometry target, not a measured result). | SIDE_VIEW |
| Trophy elbow angle | degrees | ± 5° | SIDE_VIEW |
| Knee joint angle (general) | degrees | ± 5° controlled, ± 8° peak | SIDE_VIEWPLAYER_IN_FRAME |
| Elbow joint angle | degrees | ± 5° controlled, ± 8° peak | SIDE_VIEWPLAYER_IN_FRAME |
| Shoulder elevation angle | degrees | ± 5° controlled, ± 8° peak | SIDE_VIEW |
| Wrist layback angle | degrees | ± 5° controlled, ± 8° peak | SIDE_VIEW |
| Body rotation at contact | degrees | ± 5° | AUDIO_FIDELITY |
| Racket path angle | degrees, low to high | ± 5° | SIDE_VIEWFRAMERATE_120+ |
| Racket drop depth | cm below top of head | ± 5 cm | SIDE_VIEW |
| Racket drop dwell time | ms | ± 16 ms (1 frame at 60 fps) | FRAMERATE_60+ |
| Contact height above shoulder | cm, court-calibrated | ± 5 cm | COURT_CALIBRATEDSIDE_VIEW |
| Forward contact distance | cm in front of lead hip | ± 5 cm | COURT_CALIBRATEDSIDE_VIEWAUDIO_FIDELITY |
| Contact lateral position | cm from body midline | ± 5 cm | COURT_CALIBRATEDFRONT_OR_BACK_VIEWAUDIO_FIDELITY |
| Toss height | cm above contact | ± 5 cm | COURT_CALIBRATED |
| Toss lateral placement | cm from center mark | ± 5 cm | COURT_CALIBRATEDBEHIND_FENCE |
| Vertical hip displacement | cm | ± 2 cm at typical 6–8 m, ± 1 cm at tight 4 m | COURT_CALIBRATEDSIDE_VIEW |
| Forward weight transfer | cm of CoM travel | ± 5 cm | COURT_CALIBRATEDSIDE_VIEW |
| Balance offset | cm | ± 5 cm | SIDE_VIEW |
| Player position on court | (x, y) m on court surface | ± 5 cm | COURT_CALIBRATED |
| Ball placement at bounce | court coordinates, in/out | ± 10–20 cm | COURT_CALIBRATED |
| Outgoing ball speed | km/h | ± 5 km/h target (v1.1; radar cross-check planned, not yet run) | COURT_CALIBRATEDFRAMERATE_120+ |
| Contact timing (audio anchored) | ms | same pose-frame window (~33 ms at 30 fps); audio impact onset corroborates contact in that frame, not a sub-frame bound | AUDIO_FIDELITY |
| Stroke phase boundaries | frame index per phase | ≤ 2 frames at 60 fps (~33 ms) | PLAYER_IN_FRAME |
| Split-step timing | ms vs opposing-stroke-end | ± 1 frame at 60 fps | PLAYER_IN_FRAME |
| Racket tip 2D trajectory | pixels per frame, 1080p | ≤ 8 px error, ≥ 95 % detection on unoccluded frames | SHUTTER_LOCKED_FAST |
| Racket tip court-plane trajectory | (x, y) m on court | ≤ 5 cm RMS during groundstroke phases, ≤ 10 cm at peak swing | COURT_CALIBRATEDSHUTTER_LOCKED_FAST |
| Volley backswing length | cm, target < 10 cm | ± 3 cm | SIDE_VIEW |
| Volley followthrough length | cm, target < 25 cm | ± 3 cm | SIDE_VIEW |
| Volley wrist-angle variation | degrees through contact | ± 3° | FRAMERATE_120+ |
| Choke-up grip detection | cm of palm above grip butt | ± 1 cm | SIDE_VIEW |
| Grip type | categorical: continental, eastern, semi-western, western | ≥ 90 % correct, contact frame | SIDE_VIEWPLAYER_IN_FRAME |
| Stroke type identification | 12-class (serve flat / slice / kick, FH topspin / slice, BH 1H / 2H / slice, volleys, smash) | macro F1 ≥ 0.92 target (labelled-set certification planned) | PLAYER_IN_FRAME |
| Backhand handedness (1H vs 2H) | categorical | ≥ 95 % correct in contact ± 100 ms window | PLAYER_IN_FRAME |
| Court calibration status | categorical: ok / degraded / failed | v1.1 court-pose target, not yet set or run; v1 is single-line scale calibration, checked against known ITF line geometry — no court-pose accuracy figure claimed | INTRINSICS_PRESENT |
Medium confidence Number with caveat, or qualitative bucket
Either the band is wider than coaching needs, the signal isn't fully validated yet, or a physics floor (camera frame rate, motion blur) caps the achievable accuracy. Where the band exceeds what a coach would call useful, the app drops the number and shows a qualitative bucket instead.
| Metric | Units | Target band (target, not measured result) | Capture required |
|---|---|---|---|
| Projected 2D segment-rotation peak timeline (experimental) | observed peak time (a timecode into your own clip) for each projected segment LINE the side-on camera can see, plus the adjacent lead-gap (ms) between two ladder-neighbouring segments' peaks, quantised to the pose sampling grid. No ordering verdict, no comparison, no reference. | Experimental. Reports only WHEN each projected 2D segment LINE a side-on camera can see reached its rotation peak during the forward drive, and the gap in milliseconds between two adjacent segments' peaks — a descriptive timeline, never an order verdict and never a judgement that the swing was right or wrong. Six stated limitations: (1) the pelvis-line rung is usually withheld on the required side view, because a side-on hip line foreshortens to almost nothing; (2) the "spine line" rung is sagittal spine-line pitch (mid-hip → mid-shoulder), not pelvis rotation; (3) the remaining rungs are projected upper-arm and forearm line orientations, not joint mechanics; (4) no validated mapping from this projected timeline to a true 3D kinematic sequence exists; (5) the SIDE_VIEW test is a hip-line geometry threshold calibrated by judgement, NOT validated against ground-truth camera azimuth — a clip it cannot positively establish as side-on is withheld, not guessed; (6) the timeline only renders when the clip's POSE-SAMPLE RATE can resolve the phenomenon at all. Adjacent segment peaks in a swing fall roughly 20–80 ms apart, and the timing floor is two pose samples (~33 ms at 60 fps, ~67 ms at 30 fps) — inside or above that window at every ordinary rate — so an ordinary-rate clip can only "resolve" gaps too wide to be a chain and is instead asked to re-film in slow motion (120/240 fps). Above a physiological upper bound (~80 ms, a stated conservative literature figure, NOT a measured one) two adjacent peaks are reported as too far apart to be linked, never as sequencing. The pipeline runs end-to-end on the bundled pro forehand and serve clips as test fixtures, not as a reference standard; no per-clip cross-check against an independent reference has been run, and none is scheduled. | SIDE_VIEWPLAYER_IN_FRAMEFRAMERATE_120+ |
| Cross-session technique comparison | qualitative, PLANNED for v1.1 and not in the current product: an ordered per-session read history in words — the archive of what each comparable session read. No direction, no trajectory, no improvement, and no session-to-session change would ever be claimed or drawn | PLANNED for v1.1 and NOT in the current product — the panel was removed before launch and this row describes a future capability, with no promise about what ships or when. Two blockers must clear first, recorded here so the page never outruns the code: (1) the frame-fill tolerance that decides whether two sessions were filmed a comparable distance away is a self-set judgement, not yet checked against an instrument, and admitting two sessions into a comparison on an unvalidated tolerance is a claim we will not make until a repeated-same-setup capture study derives it from measured jitter; (2) comparability must hold across the whole accepted set at once (global spread), not only against the most recent session — pairwise-against-the-newest is not transitive, so three sessions can each pass against the newest yet be incomparable with each other. When it does ship it will show only an ordered per-session read history in words and it FAILS CLOSED: two sessions line up only when we can positively confirm the same capture, at least two comparable sessions are required before a history reads as a history rather than a single snapshot, and any session we cannot line up is shown as a NAMED gap, never silently dropped and never compared. It will never infer a direction or a change across sessions. | SIDE_VIEWPLAYER_IN_FRAME |
| Hip–shoulder separation (X-factor) | degrees, 3D | ± 5°, peak timing ± 1 frame at 60 fps | SIDE_VIEW |
| Hip–shoulder separation timing | ms | ± 1 frame at 240 fps (~4 ms), audio-anchored | FRAMERATE_240AUDIO_FIDELITY |
| Stance class | categorical: open / semi-open / neutral / closed | ≥ 80 % target (labelled-set certification planned) | COURT_CALIBRATEDSIDE_VIEW |
| Racket-head speed (groundstrokes 60–120 km/h) | km/h | ± 8–12 km/h (240 fps sampling-rate floor) | FRAMERATE_240SHUTTER_LOCKED_FASTAUDIO_FIDELITYCOURT_CALIBRATED |
| Racket-head speed (serves 120–200 km/h) | km/h, or qualitative bucket if band > 15 km/h | ± 10–15 km/h (sampling-rate floor) | FRAMERATE_240SHUTTER_LOCKED_FASTAUDIO_FIDELITYCOURT_CALIBRATED |
| Racket face orientation at contact | degrees from vertical | ± 8°, hidden if motion blur > 8 px or racket edge-on | FRAMERATE_240SHUTTER_LOCKED_FASTAUDIO_FIDELITY |
| Brush distance (topspin) | cm of vertical racket-tip travel through contact | ± 10 cm | FRAMERATE_240AUDIO_FIDELITY |
| Spin classification | categorical: topspin / flat / slice | ≥ 90 % target (labelled-set certification planned) | AUDIO_FIDELITY |
| Contact timing (visual fallback, no audio) | ms | ± 16–33 ms (visual frame quantum at 30–60 fps) | PLAYER_IN_FRAME |
| Foot-fault check | categorical with cm-over-line if true | ± 15 cm boundary uncertainty (ankle-only) | COURT_CALIBRATEDBEHIND_FENCETRIPOD |
| Forehand late-contact catch position | categorical: late / on-time / out-front | qualitative bucket; no degree band claimed | PLAYER_IN_FRAME |
| Forehand hip rotation (load → contact) | degrees, image-plane | ± 10° on side-view; behind-fence reads attenuated | PLAYER_IN_FRAME |
| Forehand shoulder rotation (load → contact) | degrees, image-plane | ± 10° on side-view | PLAYER_IN_FRAME |
| Backhand late-contact catch position | categorical: late / on-time / out-front | qualitative bucket | PLAYER_IN_FRAME |
| Backhand shoulder rotation (load → contact) | degrees, image-plane | ± 10°; 1H BH band 95–130°, 2H BH band 80–110° | PLAYER_IN_FRAME |
| Racket-drop arm fold | degrees, not scored | Not scored, and not published. A single 2D camera cannot back a validated window for it, so there is no wording we can stand behind and no figure we can put an interval on. The stroke read hides it and says why. The deepest dominant-elbow fold between loading and contact. Distinct from racket drop depth above, which is a centimetre reading. | SIDE_VIEW |
| Shoulder-over-shoulder at contact | degrees, not scored | Not scored, and not published. A single 2D camera cannot back a validated window for it, so there is no wording we can stand behind and no figure we can put an interval on. The stroke read hides it and says why. The tilt of the shoulder line from horizontal at contact. This is not the shoulder elevation angle listed above. | SIDE_VIEW |
| Elbow at contact | degrees, not scored | Not scored, and not published. A single 2D camera cannot back a validated window for it, so there is no wording we can stand behind and no figure we can put an interval on. The stroke read hides it and says why. The groundstroke elbow angle at contact. Listed separately from the elbow joint angle above, which is graded against a window. | SIDE_VIEW |
| Knee at contact | degrees (2D), not scored | Not scored, and not published. A single 2D camera cannot back a validated window for it, so there is no wording we can stand behind and no figure we can put an interval on. The stroke read hides it and says why. A three-point angle from hip, knee and ankle read in the image plane at contact. Listed separately from the knee joint angle above, which is graded against a window. | SIDE_VIEW |
| Shoulder separation at contact | degrees (2D), not scored | Not scored, and not published. A single 2D camera cannot back a validated window for it, so there is no wording we can stand behind and no figure we can put an interval on. The stroke read hides it and says why. A three-point angle across both shoulders and one hip, read in the image plane. It is not the 3D hip–shoulder X-factor listed above and must not be read as one. | SIDE_VIEW |
What we don't claim Hidden in v1
A single phone on a tripod cannot recover every measurement a biomechanics lab can. Where the physics or the licensing block a credible number, we hide the metric and tell you why instead of fudging it. These are the lines we will not cross to look more impressive.
| What we don't show | Why |
|---|---|
| Spin in RPM | Optical RPM measurement of a tennis ball from a single phone is not solved at consumer hardware. Phones can't deliver > 1000 fps consistently; specialised radar or Doppler is required. We show qualitative spin (topspin / flat / slice) instead. |
| Pose-derived ball speed in mph as a precise number | Pose-only estimation lands at roughly ± 8 % accuracy. That's not a number to put next to a radar gun. Outgoing ball speed comes from ball tracking with court calibration (high confidence, see above), not from the player's pose. Anything called "swing speed" without ball tracking gets a qualitative bucket. |
| Clinical 3D internal joint angles at impact | A single monocular camera cannot recover hidden depth at peak velocities. Published floors for monocular methods sit around 4.8° rotational MAE under good conditions; that is not clinical grade. Bert AI and OnCourtAI display these anyway. We don't. |
| Joint torques and kinetic-chain power | Requires force plates or instrumented insoles. Camera-only ML estimation that approaches lab grade is research-licence-only and cannot ship in a commercial app. |
| Internal shoulder rotation and pronation at impact | Occluded and motion-blurred even at 240 fps from a side-view phone. An optional racket-butt IMU sensor add-on is on the v2 path. |
| 3D string-bed normal at impact | The racket is edge-on, blurred, or occluded at impact. A 3-frame illustration is not a continuous 3D measurement and will not be shown as one. |
| Ground reaction force | No camera-only path. Needs force plates or instrumented insoles. |
| Foot-strike pattern (heel vs toe) | Requires heel and first-metatarsal keypoints. The pose stack we use has ankle only, no foot keypoints. Documented as a known gap, not silently substituted. v2 path adds a small targeted foot-keypoint detector. |
| Foot pronation | 3D foot orientation requires foot keypoints with depth. Single-phone monocular cannot resolve this reliably. Same v2 path. |
| Sub-cm foot-fault precision | Sub-cm requires foot extent (toe and heel), not just ankle. v1 ships a coarse near-baseline / past-baseline read with a ± 15 cm buffer. We will not say "out by 3 cm" until the foot detector ships. |
| Injury-risk diagnosis | Out of scope. Clinical claims unsupported by single-phone biomechanics, with liability we will not assume. |
| Match-play analysis | v1 is practice only. Match play violates the capture contract: handheld phone, no tripod, no consistent court calibration, no locked exposure. The app refuses to enter metric mode under those conditions instead of silently degrading. Match analysis returns as a v2+ feature once the on-tripod product is shipped and trusted. |
| "AI tennis coach used by ATP players" or similar global accuracy claims | Marketing language without external validation. We will not put it next to a metric until pro-canary results plus independent coach review pass for two release cycles. |
02 /How we validate
Today, validation is what we can actually run, and it is narrower than we previously described on this page. We used to say the full pipeline ran end-to-end on the bundled pro-clip fixtures every build, with the measured reads pinned so an unintended change failed the build. That was not true, and here is what runs instead. The iPhone app is compiled but not tested on every push and pull request to main that touches ios/**; that lane's own note records that the pro clips and their sidecars are not present in CI, so it could not run them if it wanted to. The lane that does drive bundled fixtures, ios-heavy-canaries, runs on demand, and every Monday at 13:00 UTC, restricted to 17 named test targets. The suite that checks measured reads against pinned expectations, MeasurementAcceptanceTests, is in none of those 17 and is run by no workflow at all — it runs when a person runs it locally. The Python side (validate) runs on every push and pull request to main, and on demand: it lints and type-checks the modelling code, validates the metric matrix, and checks the published bands against it. This website's own build runs on every pull request and push to main, and on demand, and lints, type-checks, tests and builds the web app — it cannot execute the phone's measurement pipeline at all. The cadences in this paragraph are generated from the workflow triggers themselves and re-checked on every build of this site (see src/content/ciLanes.ts); the phone-side lanes are transcribed from that repository at commit 7485c3d2 and this site's build cannot re-read them, so treat those four as dated and checkable rather than machine-verified. Even at its best, a fixture lane is a regression check against our own earlier output: it proves our numbers are stable, not that they are correct. No human has hand-marked a contact frame or a phase boundary on any clip committed to this repo, so no measurement on this page has been checked against an independent reference. The instrument cross-checks a biomechanics lab would use — mocap, goniometers, a radar gun — are named below with their real status: planned, or in one case not scheduled at all; nothing on this page is anchored to them today. Independent human annotation is the next check we intend to run, ahead of any instrument work, because it is the only one on that list that can show a timing claim is wrong rather than agree with it.
| Reference | Status and what it covers |
|---|---|
| Bundled pro-clip fixtures in use | The full pipeline runs on the bundled clips — Djokovic and Isner serves, Sinner and Nadal forehands, and the SportAI reference serve — in the app and in a test suite that holds their measured reads pinned. Nothing runs that suite automatically. MeasurementAcceptanceTests is in no lane's selector and is run by no workflow at all; it runs when a person runs it on their own machine, so the pinned reads catch drift only as often as someone remembers to run them, and a change that moved a number can reach a build unnoticed. Section 02 lists every lane and the cadence it actually declares. Read the check itself for what it is, too: a stability check against our own earlier output. It catches regressions. It cannot tell us whether a number was right in the first place, because the value it compares against is one we produced, not one an instrument or a person measured. |
| Hand-marked frames not in use | We previously claimed on this page that we hand-mark contact and phase frames on the bundled clips and check the pipeline lands in the same window. That was wrong, and we have removed it. There are no hand-marked frames on any bundled clip. The one fixture of frame indices we hold covers a single serve on a clip that is not committed here, no reviewer or review date was ever recorded against it, and its indices were derived by the same kinematic rules the segmenter itself uses — contact as the lowest dominant-wrist position, toss release as the non-dominant-wrist apex, knee bend as the minimum knee angle. Two of its eight stages are not observations at all but the midpoint in time between two neighbouring stages. Checking the segmenter against those indices tests that it reproduces its own rule; it cannot detect a rule that is wrong. Genuine human annotation is planned and is listed below. |
| Independent human annotation planned | A reviewer marking contact and stroke-phase boundaries frame-by-frame from the video, blind to what the pipeline produced, on clips that are committed with resolved licensing. This is the first check that could actually falsify a phase or contact claim rather than confirm it. Until it runs, every timing claim on this page rests on rules we wrote, checked against themselves. |
| Court line geometry in use | ITF singles-court dimensions are exact, so court calibration is checked against the known line geometry rather than an instrument. The court-frame metrics it enables are themselves v1.1 (see the scope note). |
| Motion-capture cross-check not scheduled | A marker-based mocap comparison for joint angles at peak motion and kinematic-sequence timing. We previously listed this as planned for v1.5. It is not funded and not scheduled, and saying otherwise was a promise we had no plan to keep. It is also not the right first check: mocap certifies a lab condition with markers, controlled lighting and a fixed rig, and Rallo is used on a phone propped against a fence. It could tell us our geometry is sound under conditions we never ship in. Independent human annotation costs less, matches the condition we actually run in, and is the only one of these that can falsify a timing claim rather than confirm it, so it goes first. Nothing on this page is anchored to mocap today, and nothing is waiting on it. |
| Goniometer cross-check planned | Controlled slow-swing joint-angle comparison to tighten the degree bands (knee, elbow, shoulder, wrist). Planned; not yet run. |
| Radar cross-check planned v1.1 | Outgoing-ball-speed agreement against a radar gun, shipping with the court-calibrated speed path in v1.1. Planned; not yet run. Until then we publish no absolute ball speed. |
| Larger labelled benchmarks planned | The classifier figures above (stroke ID, stance, grip, spin, handedness) are the targets each must meet. The balanced labelled sets to certify them are planned, not yet assembled. |
| Public-dataset held-out splits planned | Reproducible held-out validation on public data (THETIS, AthletePose3D, RacketVision and similar) so an outsider can check the numbers. Planned; not part of the shipping build yet. |
The audio-anchored kinematic sequence (the high-confidence version that uses 240 fps + impact-onset audio as t=0) is staged for v1.5. The experimental projected 2D segment-rotation peak timeline at medium confidence ships now. It reads no reference and makes no comparison: it reports only when each projected segment line the side-on camera can see reached its rotation peak during the forward drive, as a per-segment timecode into your own clip, plus the milliseconds between two adjacent segments' peaks. There is no order verdict and no judgement that a swing was right or wrong — it describes what the pose clock recorded, not a validated 3D kinematic sequence. The bundled pro clips are test fixtures the pipeline runs end-to-end, not a reference standard; no mocap cross-check is scheduled; see the row above for why independent human annotation goes first.
03 /Capture conditions
The bands above hold when the capture meets the conditions below. The app evaluates these every session and tells you when a metric is hidden because a condition failed. Practice sessions only. No match play in v1 — the conditions can't be met handheld, courtside, mid-rally.
Player visible across the stroke
The player's torso is confidently tracked across the stroke — in at least 40 % of frames, with no tracking dropout longer than 10 frames or ~12 % of the clip — and each required region (head, feet) is tracked in at least a quarter of frames. A clip where the player walks out of frame or is cropped mid-swing is refused, with the specific reason and how to re-record. This is a pose-coverage check, not a per-frame keypoint quota.
Court calibration locked
Court-frame metrics are v1.1 (see the scope note). What v1 ships is single-line scale calibration: you mark one court line of known length (an ITF baseline, service box, or net) and that fixes real-world scale for the distance and speed reads, checked against the known line length — not against an instrument, and with no accuracy figure claimed. Full court-pose calibration — solving the camera against the whole court geometry, with an accuracy target for it — is planned for v1.1 and has not been built or validated. There is no continuous drift-detection or auto-pause today: if the phone moves, you re-mark the line. No court-frame metric is shown until that calibration lands.
Side view (parallel to baseline)
The side-on check reads the clip-median hip line: pointing across the camera (side-on) reads low, squared to the camera reads high. Under about 0.28 is treated as side-on, over about 0.45 is refused as too head-on, and the band between is ambiguous and held back rather than guessed. This is a judgement-calibrated guide, not a validated view classifier and not a first-frame angle detector, and there is no per-angle metric selector — a clip we can't confirm is side-on is held back, not run anyway.
Behind fence (perpendicular to baseline)
Required for serve toss lateral placement, foot-fault check, and the behind-fence half of the serve report. We support both views; the report uses whichever angles are present.
120 fps or better
A capture target for outgoing ball speed and frame-quantised timing tighter than 33 ms — both deferred (see the scope note). v1 computes neither. The one shipping surface that enforces a pose-rate floor is the experimental projected 2D segment-rotation peak timeline: it renders an order only when the clip's pose-sample rate can resolve segment sequencing (about 100 Hz or better, i.e. 120 fps slow-motion capture), and below that it shows no order and asks you to re-film in slow motion. No other metric enforces an automatic frame-rate gate, and none is dropped for being under 120 fps. When the deferred metrics ship, their own frame-rate floor ships with them.
240 fps (slow-mo)
A capture target for racket-head speed at impact, millisecond rotation timing, and brush distance through contact. The high-confidence audio-anchored versions are deferred (v1.5; see the changelog and scope note), and v1 enforces no automatic 240-fps gate that drops them — no such gate is built. The experimental projected 2D segment-rotation peak timeline is the one surface that benefits directly: at 240 fps its two-sample timing floor (~8 ms) sits below the ~20–80 ms window between adjacent segment peaks, so it can resolve a real sequence rather than only gaps too wide to be one. iPhone 14 Pro and newer can sustain 240 fps.
Shutter locked at 1/500 s or faster
Locked exposure, ISO, and shutter at recording start. Without this, the racket motion-blurs to a streak at impact and the keypoint detector can't read it.
Audio impact band clean
Phone built-in microphone, RMS in the 3–5 kHz transient band above floor. The audio onset corroborates which pose frame contact lands in — frame resolution (~33 ms), not a sub-frame time. If the wind or distance kills the impact transient, contact timing falls back to a wider visual-only band and the app says so.
Tripod-class stability
IMU-measured orientation noise under 0.1°. Stable handheld is supported with widened bands; wobbly capture is review-only and the metric layer is silent. Foot-fault detection always requires tripod.
04 /What we changed
The trust contract changes. When it does, we list the change here and date it. No silent revisions.
/trust URL in every mode, including after public launch, rather than only under the prelaunch static path, and added it to the public sitemap. Aligned the Terms page's uncertainty clause to the same contract stated here — every observation carries an error band or a qualitative read, not a universal error band — so a qualitative read such as knee load is not misdescribed as a degree band.
metrics_matrix.yaml authority file: no such file exists, and this page is the contract in full. Corrected the loading-knee row to a qualitative read (good load / shallow), matching the app, which reads load depth as a quality rather than a degree because a single camera can't pin the knee angle at the trophy. Also fixed a capture note that still said audio gives "sub-frame" contact timing — it corroborates the frame, at ~33 ms resolution, not below it.