rall Trust

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_IN_FRAME

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_CALIBRATED

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

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

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.

FRAMERATE_120+

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.

FRAMERATE_240

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_FAST

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_FIDELITY

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

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.

2026-08-06
Corrected the opening summary of what we claim. It listed “kinetic-chain ordering” among our observations, but the engine computes no ordering verdict: the projected 2D segment-rotation read is a descriptive peak timeline — when each segment line the side-on camera can see reached its rotation peak, and the gap in milliseconds between neighbours — and it is usually withheld on an ordinary-rate clip, which is asked to re-film in slow motion (120/240 fps). The summary now says only that we read “the timing of your rotation peaks when a slow-motion capture can resolve them,” matching the experimental peak-timeline row in section 01 and the code. No band or method changed; only the overclaiming summary phrase did.
2026-08-06
Withdrew the Contact reach target and removed its row above. It read the hitting wrist’s height above the shoulder line at contact, in torso-lengths, on the serve. Three problems, any one decisive. It is a tautology on the only stroke that produces it: a legal serve is struck above the shoulder essentially by definition, so the read returns “above the shoulder line” for every valid serve it will ever see — a constant with a confidence interval attached is not a measurement, and it teaches you that our reads are trivial. Its torso scale is a population guess: normalising by an assumed torso makes the error larger for the juniors we serve, and clip-to-clip scale does not transfer. It could never publish anyway: the ratio is ungoverned, so the governor hides the number and offers no word in its place. Rather than leave an inert read wired into the engine — where a phone-authored packet could still have printed a bare “n torso” through a side door — we deleted the read, its registry entry, and this row together, so the number is now unrepresentable rather than merely unshown. The body-relative contact-geometry work it grew out of is parked for a future release under a real court calibration.
2026-07-30
Made the absence of an error band an explicit position rather than a gap. We could model a ± from tracking confidence and print something that looks more rigorous than what we show you; it would be a number about our own certainty wearing the costume of a number about your body, and nothing on screen would let you tell those apart. So we publish the figure and an honest account of what stands behind it, and no interval at all. Also removed the planned v1.5 promise from the motion-capture cross-check and marked it not scheduled, because it is neither funded nor scheduled and listing a release for it was a commitment we had no plan to keep. Mocap certifies a lab condition — markers, controlled lighting, a fixed rig — and this product is used on a phone propped against a fence. Independent human annotation is now stated as the next check we intend to run, ahead of any instrument work: it costs less, it matches the condition we actually ship in, and it is the only check on that list that could show a timing claim is wrong rather than agree with it.
2026-07-30
Added Knee at contact as its own not-scored row. The app shows a knee angle on the stroke metrics panel, read as a three-point angle from hip, knee and ankle in the image plane at the contact frame. That is not the same quantity as Knee joint angle (general) listed above, which is graded against a validated window and carries a degree band. Publishing the panel’s reading under the graded row would have claimed a ± 5° / ± 8° band for a number we do not grade — the same mistake we already caught and removed once for the contact elbow, which is why Elbow at contact sits separately below. The knee now gets the identical treatment: shown with its caveat, never graded, no band claimed. A guard now requires every metric on that panel to resolve to a not-scored entry, so an ungraded panel reading can no longer be published under a graded row at all.
2026-07-30
Withdrew this page’s central validation claim, which was false. Section 02 said the pipeline’s outputs “are checked against hand-marked frames on those clips at frame resolution”, carried as an in use badge, and the high-confidence tier said metrics are “rendered as a number with its error band”. Neither was true. The five bundled clips carry zero labelled entries; every labelled entry we hold sits on a single serve in a clip that is not committed to the repo, with no reviewer and no review date ever recorded against it. Those indices were not marked by a person at all — they were derived by the same kinematic rules the segmenter under test uses (contact as the lowest dominant-wrist position, toss release as the non-dominant-wrist apex, knee bend as the minimum knee angle), and two of the eight stages are not observations but the midpoint in time between their neighbours. Checking the segmenter against them confirms it reproduces its own rule; it cannot detect a rule that is wrong. Separately, no error band is rendered anywhere in the product — the code deliberately refuses to show a ± it cannot support, so this page was promising a precision the app correctly withholds. What we actually run is a stability check against our own earlier output, and section 02 now says exactly that. Independent human annotation is listed as planned, because it is the first check that could falsify a timing claim rather than confirm it. A guard now parses this page’s validation table and fails the build if a row is badged in use without evidence behind it, if a self-referential check is published as an independent one, or if any in-use row claims hand-marking — and it reads the claim from this page while reading the evidence from a separate declared inventory, so editing one to agree with the other does not quiet it.
2026-07-29
Closed a completeness gap in this page’s own promise. The contract states if a metric is missing here, it is not in the app, but seven readings shipped with no row: contact reach, the three leg-drive readings, the racket-drop arm fold, trunk balance at contact, and trunk extension at contact. They are documented above now, each marked as a geometry target rather than a measured result. We also corrected knee load at loading, which this page described as read as a quality, not a degree while the app rendered a degree value. It now states degrees with the widest band of the knee readings, because deep flexion at the trophy is the hardest knee angle to read from a single camera. A test now runs the read engines directly and fails the build when any displayed measurement has no row here, or when a row states a different unit than the one shipped, so this promise is checked rather than asserted.
2026-07-28
Removed two fabricated calibration claims that had survived earlier passes. The band table asserted the court calibration status held to RMSE under 2 cm on the singles court, and the capture-conditions card described an on-device court detector or 4-tap court-pose solve that was re-evaluated continuously and would pause the affected metrics until you tap to recalibrate. No code produces that RMSE figure and no continuous drift-detection or auto-pause exists — the 2 cm number had no measurement or derivation behind it, and the runtime behaviour was never built. What v1 actually ships is single-line scale calibration: one court line of known length fixes real-world scale for distance and speed, checked against the known ITF line geometry, with no instrument RMSE. Full court-pose calibration and any accuracy target for it are v1.1, not yet built or validated. The card and the band now state exactly that, consistent with section 02, which already validates court calibration against line geometry rather than an instrument.
2026-07-28
Renamed the band-table column header from "Error band" to "Target band", with a "(target, not measured result)" sub-label, so a reader scanning only the table cannot read the physics-derived accuracy targets as achieved measurements — the framing that was only in the prose before now sits in the column header itself. Made this Trust page reachable at its canonical /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.
2026-07-27
Rewrote "How we validate" to state only the checks we actually run today — the pipeline executing end-to-end on the bundled pro-clip fixtures, and hand-marked contact and phase frames on those clips at frame resolution. Removed claims of validation we have not run: a Vicon mocap protocol, a goniometer comparison, a 100-stroke radar cross-check, hand-labelled benchmarks with specific stroke counts, held-out public-dataset splits, and a release-blocking canary gate with a km/h floor. Each is now listed as planned, with the release it is due in. Removed the reference to a synced 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.
2026-05-07
Added the experimental projected 2D segment-rotation peak timeline at medium confidence as the v1 path. The high-confidence audio-anchored version (240 fps + impact onset as t=0) waits on v1.5. This version reports a descriptive timeline — a per-segment rotation-peak timecode plus the lead-gap in milliseconds between two adjacent segments' peaks, quantised to the pose sampling grid; no ordering verdict and no degrees-per-second number is published. It reads projected 2D segment lines, not a validated 3D kinematic sequence.
2026-04-28
Revised racket-head speed bands from "± 5 % vs lab mocap" to per-stroke-class bands: ± 8–12 km/h on groundstrokes, ± 10–15 km/h on serves. The change reflects the sampling-rate floor at 240 fps for fast impacts: a single video frame at 150 km/h moves the racket tip about 17 cm, and finite-difference velocity is bounded by that regardless of model quality. If the band exceeds 15 km/h we drop the number and show a hard / medium / soft bucket instead.
2026-04-28
Narrowed v1 use-case scope to practice sessions only — drills, ball machine, basket feeding, serve practice, cooperative rallies, lessons. Match play moved out of v1 because the capture conditions can't be met handheld, courtside, mid-rally. Match analysis returns as a v2+ feature once the on-tripod product is shipped and validated.
2026-04-28
Vertical hip displacement band split by capture distance: ± 2 cm at the typical 6–8 m setup, ± 1 cm only at tight 4 m framing. The original "≤ 1 cm" claim assumed every clip was tight-framed; pixel-per-cm math at 1080p, 6 m, ~73° HFOV is the binding constraint.