Dave Cole
← TNNS HEAD · The series

TNNS HEAD · AI Tennis Coaching IRL · Part 2

AI Tennis Coaching IRL — Part 2: Side Quest → Epic Adventure

Dave Cole · November 3, 2026 · 8 min read

In the first post, I explained the problem we set out to solve: give tennis players useful coaching from the short, imperfect recordings a parent or friend can take during a match. We built a functional system, but it simply wasn’t good enough.

This post covers how we started, why the early results were misleading, and how a fairly straightforward web app became a “way down the rabbit hole” epic adventure.

Upload a video, get coaching

Our initial approach was to build a website with a good video player and an API behind it. Someone uploads a recording, we pass it to a model, and the player gets feedback tied to relevant moments in the clip. Easy right?

The early analysis used Google Gemini since it has native multi-modal support (kind of). We supplied the video, the player profile, and instructions for what to examine. The response came back in a structured format that we could turn into a coaching experience: a summary, observations, and timestamps connecting the advice to the recording.

The playback mattered. We wanted to pause at a useful moment, zoom in, and show the player what the feedback referred to. Coaches and players could share recordings and discuss specific points.

Early TNNS HEAD web coaching mode showing a player annotation, a Forehand label, and video controls
Early web coaching mode: a stroke label and a player annotation connected the feedback to the recording. Open the image to view it at full size.

There was also a less technical goal: keep parents and kids from fighting after a match. Giving them something else to react to seemed useful. Being annoyed with TNNS HEAD is considerably less consequential than being annoyed with the person driving you home.

The site was starting to look good. Analysis felt a little loose but it was pretty speedy and some of the coaching advice looked solid. It was time to do some fine-tuning and make it good.

So I did a moment by moment review of the advice. It was not only not good, it was dead wrong. It had invented a backhand that never happened. It couldn’t see obvious serves. Other strokes were missing or fabricated. Tweaking the prompt, few shot examples, evals… none of it made a big difference.

We weren’t debating whether the player should have approached the net or chosen a different target. The system was getting the events themselves wrong. The problem was foundational.

Early TNNS HEAD analysis screen with a video, an annotated moment, detailed coaching feedback, and a thumbnail timeline
The interface could put detailed coaching next to the video, complete with a timeline and related resources. We still had to check whether the events behind the advice had actually happened. Open the image to view it at full size.

Giving the model a better account of what happened

The models could recognize tennis and produce language that sounded like coaching. We couldn’t reliably depend on their understanding of the sequence: who hit which shot, where the players were, and how one action affected the next.

Perspective was particularly troublesome. From behind the baseline, the near player looks large and the opponent looks small. A player’s position in the image doesn’t translate directly into their position on the court. The models were clearly trained on language and knew little of visible depth or physics.

After some wringing of hands and long conversations with AI and humans, we realized we had to build a spatial intelligence pipeline, what we called SPI. SPI’s main role was to basically produce a package for the model that simulates human perception of where the person is, their posture, the placement and trajectory of the ball, the geometry of the court as well as what could be heard on the court from sneaker squeaks to line calls (“out!”).

This meant detecting the players and ball, tracking them across frames, estimating body positions, and mapping positions onto the court. Those observations could support candidate events—a serve, an approach, a recovery—which we could supply to the model as context for its coaching.

The first approach

  1. Uploaded videoPlus the player profile
  2. Gemini analysisStructured observations
  3. Coaching playbackAdvice linked to the clip

After adding SPI

  1. Video + audioThe recorded evidence
  2. PerceptionPose · player tracking · court geometry · ball tracking · audio
  3. Candidate eventsServes, strokes, approaches, recoveries
  4. Model + playbackCoaching from the available evidence
A simplified reconstruction of the early analysis flow and the perception stack we added around it, based on the code and architecture notes from late 2025 through February 2026.

Recreating human perception turned out to be an enormous pain in the ass. Here's a quick breakdown of what we tried and how it went:

In order to make it easier for the model to understand, we rolled up some of these (e.g., pose and ball tracking) into semantic events to ease the burden on the model a bit, allowing it see a forehand or a backhand versus interpreting the data.

Before we talk about how our perception stack worked in real life, it's worth taking a moment to understand how the models handle video.

What does “watching the video” mean?

One easily missed detail is what it means to give a model a video. The models sample uploaded videos by extracting select frames, with timestamps. Sampling a 30 FPS recording at one frame per second, for example, leaves 29 frames out of every second’s analysis. That may be ok to describe a scene, but racket contact or a bounce can fall entirely between samples when a ball is moving quickly in milliseconds. Gemini could also process the accompanying audio, but the visual evidence still depended on which frames were selected and how much detail they retained. Note that this is different from how a visual model like Elorian works, but more on that later. It’s consistent across Astra, Qwen and other models.

We experimented with how densely we asked the model to sample the video, including increasing the requested rate from 8 to 24 frames per second. That was an analysis setting, not the phone’s recording rate. A clip could contain useful evidence that never made it into the frames used for analysis. We created sets of frames like cartoon strips to provide more context to the model. We cropped, we zoomed to increase player size.

Player size matters a lot. Following someone from the sidelines requires less visual detail than analyzing their feet and racket from a recording. Someone could be plainly visible to a parent and still be too small in the image for useful analysis. Especially on the far side of the court.

Audio gave us another source of evidence. Impacts and vocalizations could help locate action and provide context when the view was incomplete. It’s quite good at helping determine what happens at the end of a point given how hard it is to see the ball. However, noise doesn’t stay on one court. Match play is often done around many other people who are playing. 8+ courts is normal. We had to compare sounds with visual evidence to avoid treating someone else’s racket hit or call as part of the point being analyzed. Tricky.

Meanwhile, we needed a way to judge whether changes were improving the system. We collected measurements of visibility, tracking, pose, and court-detection quality, and built tools to annotate recordings and compare the output with what actually happened.

Better, but…

We labeled recordings by hand and built an evaluation dataset so we could test the system against what actually happened. After a fair amount of iteration, we were getting accuracy around 70%. It wasn’t terrible, but it also wasn’t especially good for something intended to tell a player how to improve. We could have kept grinding on it for a very long time. Increasingly, though, it was clear that some recordings simply weren’t workable. The players were too small and far away to analyze reliably. The camera angle left too few usable court landmarks to establish the geometry. The ball was largely invisible. We were asking the system to reconstruct events from evidence the recording had barely captured.

So we became mobile developers

We’d reached the limit of what we could fix after a recording was uploaded. To improve the results, we needed control over how the video was captured and a way to guide the person holding the phone while they could still do something useful with that guidance.

That meant building a native mobile app. It also meant rebuilding substantial parts of SPI around the capture process: deciding what should run on the phone, what belonged on the server, and how the two would work together.

We were six months in and starting over in several important ways. What began as a website with an API now required us to become mobile developers and reconsider the architecture we’d spent those months building. A fairly substantial change of plans.

The next post covers that rebuild and what happened when we took it back to the courts.

← Part 1 All writing →