The trust contract
How we measure, and how confident we are.
Rallo claims numbers. Knee bend in degrees. Contact height in centimetres. Racket-head speed in km/h. Every one of those numbers carries an error band, a set of capture conditions it depends on, and a reference we validate against. This page is the engineering contract behind those numbers, in plain language. If a metric is missing here, it is not in the app.
The matrix below is the public face of docs/metrics_matrix.yaml, the file the app, the engine, and our release CI all read from. The two are kept in sync. When the matrix changes, this page changes; we list every change at the bottom.
01 /What we measure
Three confidence tiers. High-confidence metrics are published as a number with a tight band. Medium-confidence metrics are published with a wider band or as a qualitative bucket. The third tier is the list of numbers we will not show you, and the reasons.
High confidence Number with band
Rendered as a number with its error band. The band is anchored to a reference — goniometer, mocap, radar gun, or a tape measure on a calibrated court. If the capture conditions for a metric fail, the metric is hidden and the app says why.
| Metric | Units | Error band | Capture required |
|---|---|---|---|
| Knee bend at loading | degrees | ± 5° | 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 | AUDIO_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 vs radar reference | COURT_CALIBRATEDFRAMERATE_120+ |
| Contact timing (audio anchored) | ms | ≤ 5 ms RMS vs hand label, ≤ 2 ms vs mocap | 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 on internal benchmark | 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 | RMSE < 2 cm on the singles court | 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 | Error band | Capture required |
|---|---|---|---|
| Kinematic sequence (2D, v1 path) | verdict: proper / partial / inverted / insufficient, with adjacent lead-gap (ms) | verdict only; pipeline runs end-to-end on bundled pro side-view canaries (Federer FH, Sinner BH); per-clip mocap cross-check is v1.1 work | 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 % on internal labelled set | 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 % on internal labelled set | 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 |
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
Each band above is anchored to a reference. The references are mocap, goniometers, hand labels, radar, and pro-clip canaries. The anchor for each metric is in the matrix file; the categories below are the references we use most.
| Reference | What it validates |
|---|---|
| Vicon mocap (150 Hz, full marker protocol) | Joint angles at peak motion, kinematic-sequence ordering, racket-head speed, leg-drive timing. The reference for everything that makes a degrees-or-millimetres claim. |
| Goniometer comparison | Slow controlled swings: knee flexion, elbow angle, shoulder elevation, wrist layback. The reference for our ± 5° controlled bands. |
| Radar-gun cross-check (100 strokes) | Outgoing ball speed in km/h. The radar reading and the ball-tracking reading must agree within ± 5 km/h. |
| Tape-measured court points | Court calibration RMSE. ITF court dimensions are exact, so a tape measure on the lines is a hard reference. |
| Hand-labelled benchmarks | Stroke ID (~ 2000 strokes, balanced classes). Phase boundaries (200 strokes). Stance class (500 strokes). Grip type (200 contacts, cross-checked with a USPTA coach). Forehand and backhand defect labels (30-clip recreational set + bundled pro side-view clips). |
| Pro-clip canaries | Bundled Djokovic serve, Sinner forehand, and Alcaraz backhand fixtures. Every release run on these clips before ship. The serve must read at ≥ 180 km/h racket-head speed; the forehand canary must read at ≥ 145 km/h with X-factor present. Aggregate score floor is 90 / 100. Canary failure blocks release. |
| Public datasets | Training and validation use public-only data: THETIS for stroke ID, AthletePose3D and Tennis-MoCap for 3D pose, RacketVision for racket keypoints and ball, AthleticsPose and COCO-Wholebody for pose pretraining. Held-out splits validate the same metrics. We don't lean on proprietary clips — outsiders can reproduce the numbers. |
The mocap and audio-anchored kinematic sequence (the high-confidence version that uses 240 fps + impact-onset audio as t=0) is staged for v1.5. The 2D kinematic sequence at medium confidence ships now using the bundled pro canaries as its reference; a wider mocap cross-check is planned for v1.1.
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
At least 90 % of player keypoints present in every frame across the stroke arc. If the player walks out of frame mid-swing, that stroke is flagged low confidence.
Court calibration locked
The on-device court detector or a 4-tap court anchoring pass solves the camera pose against the court geometry. RMSE under 2 cm on the singles court. Re-evaluated continuously; if the tripod bumps and the calibration drifts, the affected metrics pause until you tap to recalibrate.
Side view (parallel to baseline)
Required for joint angles, racket drop depth, contact height, and most kinetic-chain reads. The app's first-frame view classifier flags the angle and selects the per-angle metric set.
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
Required for outgoing ball speed and any frame-quantised timing claim tighter than 33 ms. Below 120 fps, those metrics are hidden.
240 fps (slow-mo)
Required for racket-head speed at impact, body rotation timing in milliseconds, and brush distance through contact. iPhone 14 Pro and newer can sustain 240 fps; older devices fall back to 120 fps and the affected metrics are hidden.
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 gives sub-frame contact timing. 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.