Sports Performance Analysis · September 2026

Internal · confidential · not for distribution

Tracking players from a football broadcast

A first pass at turning broadcast footage into player and ball positions in metres, so the tactical efficiency model has something to work from. Germany vs Denmark U21, 1080p30, measured on a 30 second window at t = 60–90 s.

01What this is

A working end-to-end pipeline on one window of one match, and an honest account of how far that is from a system.

Everything on this page was measured on one 30 second window of one broadcast. No figure here is quoted from a paper or a published benchmark, and none of it is a claim about a second match, a second ground or a second camera. Where a number would be flattering in isolation, the line underneath it says what it cannot show.

The footage is a broadcast feed, not a tactical wide camera, chosen deliberately because it is the harder case and the one a production system has to survive. It brings four properties that shape every decision below: the camera pans and zooms constantly, there are 4 hard cuts in 180 seconds, only part of the pitch is in shot at any moment so a full 22-player state is never directly observed, and a burned-in scoreboard sits over the top left of every frame.

The pipeline runs in stages, each writing its own JSON keyed by original frame index, each readable without re-running the stage before it: detect people, assign a team by shirt colour, join detections into tracks, find the ball, fit the pitch to the image, write positions in metres. The last file is the one that matters to a consumer and it has its own page: the data contract.

02What you are looking at

The broadcast on the left, and the same moment reconstructed in metres on the right.

On a phone, this one is small. It is a wide side-by-side, so it renders about a fifth of the height of your screen. Use it to check that IDs match across the two panels, then open the full-resolution broadcast overlay below for anything you actually want to look at.

Open the full-resolution broadcast overlay →

Track IDs match across the two panels, so a player can be followed from the broadcast into the pitch view and back. The right-hand panel is not a redrawing of the left: every dot is the bottom centre of a detection box pushed through that frame’s pitch calibration, so when a dot sits in the wrong place, the calibration is what is wrong. The ball carries a short trail and turns blue while its position is interpolated through an occlusion.

Detection runs at 10 Hz and each result is held across three frames for 30 fps playback, so boxes step rather than glide. The number on a box is its track ID. A trailing ? marks a low-confidence team assignment, and a box whose torso window straddles two people is labelled unresolved rather than guessed.

Three things worth watching for, because they are real and they are visible. Hi-vis figures in front of the hoardings are tracked and labelled as non-players rather than dropped, so a detection problem stays countable. Around t = 83 s the camera whips across and the boxes briefly vanish and come back with new IDs. And a handful of dots in the pitch panel sit outside the touchline: those are kept and flagged, never clipped back onto the grass.

03What works, and the numbers that prove it

Six stages produce something usable today. Each figure names the window it was measured on, and each block ends with what that figure cannot tell you.

People detected
21–23
per clean frame, stock model, no fine tuning
Unresolved team labels
0.14%
8 detections of 5,607
Track IDs, best config
24
from 56; zero identity swaps
Ball positioned
85.3%
of frames; 14.7% honest gaps
Pitch fit residual
3.68 px
92.8% of on-surface line ink explained
Positions inside the pitch
99.1%
of 5,607 rows; rest flagged, not clipped

Player detection is solved at this camera distance

Stock yolo11m, imgsz 1280, conf 0.25 · 21–23 people per clean frame · no fine tuning

The off-the-shelf COCO person class finds every player, the keeper and the referee without any training on football. Smaller models do not: yolo11n and yolo11s at the same input size find 17, and yolo11m dropped to 960 px finds 16. Model size is doing real work here; input size is doing more.

What it cannot show. There is no ground truth, so this is a detection count, not a measured recall. It says nothing about whether a box is on the right person, and it counts stewards and ball crew as happily as players.

Team assignment is verified, one track at a time

Torso colour in CIE LAB, aggregated per track · five classes · 8 detections of 5,607 left unresolved

Median LAB colour of a window across the chest, taken per detection and then per track, then k-means. The classes come out cleanly separated: Germany L 86.3, Denmark L 36.5 a* +43.2, referee L 16.3, keeper a* −42.5.

The verification was done by eye, track by track. On the first identification run all 110 stable tracks were opened as image crops and all 110 were correct. That run fitted 34 tracks to each kit, which is what about eleven a side fragmented into roughly three IDs apiece should look like.

The fifth class exists because of a failure. A four-class run reported twelve goalkeepers on a pitch that can hold two, because hi-vis bibs cluster with a green keeper kit. Hi-vis is now its own label and those tracks are carried through as non-players rather than deleted.

Every track's median torso colour, grouped by assigned label
The verification sheet from that run: every track’s median torso colour, grouped by the label it was given, 126 tracks in total. Crossed swatches are tracks held out of the cluster fit.
The shipped file is a later, different run, and the numbers are not the same. Identification originally ran before track stitching, so a merged track inherited the label of whichever fragment was its root, and one Denmark player with 117 detections came out unknown and drew no icon at all. Re-running identification on the repaired tracks fixed it: unresolved detections fell from 125 to 8 (0.14%). The current file has 56 tracks, of which 51 carry a track-level label, 44 of them high confidence and 7 low, and 5 are labelled per detection. That set has not been re-checked crop by crop, so the 110-of-110 figure belongs to the earlier run and is not a claim about this one.
What it cannot show. “Referee” is a colour claim, not a role claim, so a dark-clad coach or photographer near the boards lands in it. Detections whose torso window straddles two people are labelled unresolved rather than guessed, because guessing the nearest cluster there produced real errors.

Track identity: 56 IDs down to 24, with zero identity swaps

t = 60–75 s · seven tracker configurations compared on identical detections

The default tracker produced 56 distinct IDs on a 15 second window holding about twenty people on camera. The best configuration, tracktrack with appearance ReID followed by an offline stitching pass, produces 24: zero colour-inconsistent tracks, zero tracks under a second, and a median track length of 14.35 s of a 15 s window. Of those 24 IDs, 14 were present from the first frame, 6 entered at the edge of the picture, and only 4 are the tracker re-acquiring somebody it had already seen.

The mechanism is one parameter, and it was found rather than guessed. Of the 39 IDs the default tracker invents mid-window, 39 of 39 are born on a detection below 0.70 confidence and 23 are below 0.45; median birth confidence 0.38. The better tracker ships new_track_thresh: 0.70 and its median birth confidence is 0.75. Two things that sound like the fix are measured and are not: appearance ReID on the default tracker made it worse, 57 IDs to 70, and tuning its thresholds gave 37.

ConfigurationRaw IDsAfter stitchingSwapsMedian track
botsort, the default 56 31 0 12.6 s
botsort + ReID 70 48 0 4.3 s
botsort, tuned thresholds 37 31 0 11.9 s
tracktrack + ReID 31 24 0 14.35 s
What it cannot show, and it matters. A count of distinct IDs cannot tell fragmentation from identity swapping, because merging fragments lowers it and so does conflating two people into one ID. The two are counted separately here, using shirt colour: a track that disagrees with itself about its own player’s kit is split before anything is merged, and a cross-kit merge is refused outright (1,544 such pairs refused on the 30 s window).
The video above is not this configuration. The 24-ID result is measured on the 15 s comparison window. The 30 s render and positions_60_90.json were produced with the default tracker: 126 raw IDs stitched to 56, with 2 colour-inconsistent tracks surviving and a median track length of 6.5 s. Re-running the published window under the recommended configuration is a pending job, not a finished one.

Ball tracking: 85.3% positioned, and the gaps are occlusion rather than loss

300 sampled frames · 209 detected, 47 interpolated, 44 gaps

The ball is 10 to 19 pixels wide here, so no single confidence threshold solves it. The design is recall first: generate candidates cheaply in one inference pass at conf 0.02, add white-blob candidates for frames the detector misses entirely, throw away anything off the pitch polygon or inside the scoreboard box, then do all the precision work offline with a motion model. A crowd false positive does not stay on a plausible trajectory; a ball does.

The gaps were checked rather than assumed. All 44 gap frames have the ball in contact with a player, so the misses are occlusion, not random loss. Interpolation is applied only to short skipped runs inside a linked segment, never across a re-acquisition where nothing vouches for the positions in between, and every interpolated frame is flagged in the data.

What it cannot show. 69.7% of frames carry an actual detection; the rest of the 85.3% is interpolation, and it is labelled as such. A ball in flight is projected as if it were rolling on the ground, because the homography maps the ground plane and nothing here estimates height.

Pitch calibration: 3.68 px residual, propagated to all 300 frames

One key frame pinned by hand · 92.8% of on-surface line ink explained · median 93.1% across the window

A human names four to six markings with a rough two-point guide on one frame; everything after that is measured. Each guide is refitted to the ink it contains, the homography is solved from the crossings, and iterative closest point pulls the whole model pitch onto the line mask. That frame is then carried across the window by tracking grass features between consecutive frames, and every frame is re-seated on its own markings rather than trusting the chain.

Two independent checks agree. The centre circle, whose 9.15 m radius is fixed by Law at every ground, back-projects to 9.12 m. And the reconstruction was checked against something the fit never saw: turning detection boxes into player heights gives a median of 1.75 m over 297 boxes across 16 frames.

The model pitch reprojected onto the final frame of the window
The model pitch drawn back onto the last frame of the window, 300 propagations after the one frame that was pinned by hand. The end of the chain is where a calibration fails, so it is the end of the chain that is shown.
What it cannot show. 92.8% is the share of line ink on the playing surface that the model explains, and that denominator is mildly circular because its far edge is the model’s own guess at the touchline. Against the raw mask the ceiling is about 37% however good the fit is, since roughly two thirds of detected ink is advertising lettering and the board edge. Across the window the median is 93.1%, but the minimum is 40.1% and 12 of 300 frames fall below 70%.

Positions in metres, with the awkward rows kept

5,607 player rows + 300 ball rows · 99.1% inside the pitch · full contract

A player’s position is the bottom centre of the detection box mapped through the ground plane, not the box centre, which is roughly chest height and projects metres further from camera than the player is standing. Positions outside the pitch are kept and flagged rather than clipped, because a player over the touchline, a steward behind the hoardings and a calibration error all produce out-of-bounds coordinates, and clipping silently turns the last two into plausible-looking football. Non-players are carried through with their label for the same reason.

What it cannot show. The 99.1% is a self-consistency figure, not an accuracy figure. Nothing here measures how far a player is from where he actually was, because no ground truth exists for this footage.

04What does not work yet

None of these is a detail. Each would have to be closed before positions from a full match could be trusted, and two of them are unbuilt rather than imperfect.

Generalisation is unproven, and the one test we ran failed

The same tracker configuration on a different 15 second window of the same match gives 37 IDs after repair, not 24, and 14 detections per frame against 20. The 14 is not a recall failure: the camera has zoomed tight on a goalmouth attack with seven people in shot, then pulls back out to wide. There is no cut anywhere in that window.

So the headline tracking result is a result about one window. A single window is not evidence, and the second window we tried is 50% worse.

Three frames from the harder window, showing the camera zooming from tight to wide
The harder window, t = 102 s to t = 114 s. Seven people on camera, then thirteen, then twenty, with no scene cut. A zoom-out also reveals people in the middle of the picture legitimately, which is why the re-acquisition count is reported as an upper bound rather than an exact figure.

The pitch mask rejects nothing, so about a third of the surplus IDs are not players

A yellow-bibbed ball boy seated on the advertising hoardings is re-acquired under six separate IDs. A steward in black behind the boards holds a single ID for 232 detections. The pitch polygon is computed and is used by the ball stage, but the player tracking stage does not filter on it: on the comparison runs, 0 detections were rejected as off-pitch.

This is cheap to fix and it inflates every ID count on this page. It is left visible rather than quietly filtered, because a detection problem that has been hidden cannot be counted.

Identity across camera cuts is not built

There are 4 hard cuts in 180 seconds of this footage, in two brief pairs, and every one resets player identity completely. Nothing in the pipeline attempts to carry a player across a cut. For a tactical model this is the difference between a possession chain and a pile of fragments, so it is a requirement rather than an extension.

Worth knowing: the worst single event in this window is not a cut. A fast camera pan at t = 83.4 s collapses detection from 20 people to one on a single motion-blurred frame, and every ID in the window is reborn on the other side of it. A pan-compensated stitching pass recovers 11 of the 15 new IDs that event creates.

Five consecutive sampled frames across the fast pan at t=83.4s
Five consecutive sampled frames across the pan. No scene change, no cut, nothing a cut detector would fire on — the middle frame is simply smeared, and the tracker starts again.

No ground truth exists, so nothing here has a measured recall

Every figure on this page is a self-consistency figure: how many boxes, how many IDs, how much line ink the model explains, how many positions land inside the pitch. Not one of them is a comparison against a known answer, because there is no labelled set for this footage.

Self-consistency is not truth, and it can move the wrong way. This project already produced a ball tracker whose coverage and smoothness both improved as the answer got worse. A hand-labelled minute would convert most of this page from plausible into measured, and it is the cheapest thing on the list below.

Pitch width was assumed, not fitted

Length was fitted as a free parameter and reads 102 m at this ground. Width was not: 68 m is an assumption carried straight into every coordinate. Near the touchlines that is worth roughly 1.5 m of uncertainty in the across-pitch direction, which is a full body width in exactly the region where wide players and the offside line live.

Lens distortion bows the lines and no single homography can absorb it

The painted markings in this footage bow by 1 to 6 px of sagitta over their visible length: the halfway line by 3.2 px, the penalty-area side line by 5.6 px. A homography maps straight lines to straight lines, so it cannot represent that, and the residual is absorbed as a small error spread across the fit. It also means a single homography cannot separate a genuinely short pitch from an uncorrected lens, which is a caveat on the 102 m figure as well as on the residual.

A broadcast feed never shows all 22 players

Between 16 and 23 of the 22 players are in frame at any moment. A full team state therefore has to be inferred, not observed, and nothing here attempts it. Whether the tactical model needs all 22 every frame, or is satisfied with the players in shot plus the ball, changes the difficulty of this project by a large factor, and it is the first open question below.

05What it would take to run a full match

The compute is not the obstacle. The obstacle is the three unbuilt stages above.

Measured cost, on the development machine

ModelInput sizeSeconds / framePeople found
yolo11n 1280 0.26 17
yolo11s 1280 0.50 17
yolo11m 960 0.60 16
yolo11m 1280 1.23 19–23

CPU-only Windows box, 1080p frames. At 10 Hz a 90 minute match is 54,000 frames: about 18 hours here, and under 10 minutes on one rented L40S-class GPU. Nothing in this pipeline needs a standing GPU, and the same shape as the hockey work applies — inference on rented pods, artefacts and clips to R2, review pages on Pages.

Sampling rate is a quality lever for some trackers and not for others, and getting this backwards costs three times the compute.

On the default tracker, sampling at 30 Hz instead of 10 Hz changed the ID count from 56 to 57. Tripling the compute bought nothing, which is the most useful negative result of the work. But the recommended tracker is the opposite: 24 IDs at 30 Hz against 26 at 10 Hz, and the 10 Hz run introduced identity swaps the 30 Hz run did not, because its association is driven by box overlap and a three-frame gap under a fast pan moves a box most of its own width.

So the honest statement is tune the stride, not the thresholds — and re-measure the stride whenever the tracker changes. A full match under the recommended configuration is 162,000 frames, three times the figures above.

What has to be built before a full match is worth running

  1. Re-identification across cuts. Required, not started. Four cuts in three minutes, every one resetting identity.
  2. Off-pitch rejection in the player stage. The mask already exists and the ball stage already uses it. Cheapest item on the list, and it removes roughly a third of the surplus IDs.
  3. Calibration without a human on the first frame. The automatic line-identification search does not converge on this footage, and the diagnosis is specific: the “constant x” and “constant y” line families do not separate by image angle here, so no angle threshold can sort them. The production answer is a trained pitch-keypoint model in the style of the SoccerNet calibration work, which predicts named landmarks directly and sidesteps identification entirely. It needs a GPU and a labelled set. Two cheaper things to try first: local refinement of a rough homography against the line mask, and using the centre circle as a conic, which is a very strong constraint that no straight-line method uses.
  4. A hand-labelled minute. Converts most of section 03 from plausible into measured, and is the only way any recall figure on this page becomes real.
  5. A second match, at a second ground. The turf reference is sampled from the frame rather than hardcoded specifically so that it can survive a different stadium, a day game or a different grass treatment — but that is a design intent, not a measurement.

Open questions for the data side

  1. Does the tactical model need all 22 players every frame, or the players in shot plus the ball? This changes the difficulty by a large factor. Ball position is already confirmed as required.
  2. What sampling rate does the model want? 10 Hz is cheap, and on the evidence above it costs nothing on the default tracker and two IDs on the recommended one.
  3. Is jersey-number identification required, or is “team plus a stable track ID” enough? Numbers are a per-track cost and a separate model.
  4. Is there any access to a tactical camera or a club-provided feed? Almost everything on this page gets easier if the whole pitch is always in frame.
  5. How should the model consume a ball gap during occlusion: a marked gap, or an interpolated position with a confidence? The data carries the flag either way, so the model should choose rather than inherit our default.

06The traps

Seventeen things that looked right and were not. These are the transferable part of the work: most of them are not about football at all, and every one would cost the time again.

Seeing the pitch

The advertising boards are the same green as the grass

Looked right
The standard recipe, “take the largest green connected component”, produced a mask that covered the pitch.
Actually true
It had merged the pitch with the perimeter hoardings and run up into the crowd. Hue alone does not separate them: pitch sits at hue 40–45 with a standard deviation under 10 and saturation 135–158, the green boards at hue 64–66 with sd 40–64 and saturation 79–92.
Caught by
Measuring hue tightness and saturation region by region instead of trusting hue. The turf reference is now sampled from the bottom centre of the frame rather than hardcoded, which is what should let it survive a different ground.

Scanning up each column stops at the players’ feet

Looked right
Walk up each column of the image until the green stops, and that is the top edge of the pitch. A sensible, cheap rule.
Actually true
It stops at the first player, carving a wedge out of the mask exactly where each player is standing — which is the one place the mask has to be right.
Caught by
Looking at the mask rather than at the numbers. The column scan is now a blurred green density over a window: a player is narrow and the pitch is wide, so density survives a person and does not survive the boards.

White thresholding returns the shirts, not the lines

Looked right
Pitch markings are white, so threshold on white. The threshold returned thousands of pixels and 24 clean Hough segments on the test frame.
Actually true
Under floodlights the paint is dimmer than the white kit. None of the 24 segments was a pitch marking: they were Germany’s shirts and the goal frame.
Caught by
Drawing the segments back on the frame. The fix is a morphological white top hat, which finds whatever is locally brighter than its surroundings — what a painted line is, regardless of how that part of the pitch is lit — with the player boxes masked out first, since a white shirt is exactly what a top hat would otherwise keep.

The strongest line in the frame is not a pitch marking

Looked right
Take the highest-contrast long edge and call it the touchline. It fits beautifully: low rms, thousands of supporting pixels.
Actually true
There are metres of run-off behind the goal line and outside the touchline, and the strongest edge in the image is the pitch-to-hoarding boundary, which is not on the pitch model at all. At this ground the far touchline is not even visible. Using that edge puts every player metres out, and the fit still looks perfect.
Caught by
Reprojecting the whole pitch model rather than scoring the individual line. Touchlines are now excluded from the fit entirely and their position is only ever an output of it, never an input. Any calibration method that trusts line strength will take this bait.

Geometry that scores well and is wrong

A degenerate homography scores a perfect 0.00 px

Looked right
Mean reprojection error 0.00 px, every model sample reported in frame. The best score in the entire search.
Actually true
The homography had collapsed the whole pitch onto a single point, and that point happened to land on a line pixel. Distance to the nearest line is zero for every sample because every sample is the same sample.
Caught by
Guarding on the geometry rather than the score: the projected pitch outline must be a convex, correctly wound quadrilateral with real area and real spread, and the horizon implied by the homography must sit above the visible pitch. Those checks cost nothing and remove most of the search space.

A one-way distance score rewards shrinking

Looked right
Tiny mean distance from every projected model point to the nearest detected line, and a claim that 1,077 of 1,100 model points were in frame.
Actually true
The shot showed about a third of the pitch. The optimiser had squeezed the model until every projected point landed somewhere on some long line, which is free marks in a one-way metric. An optimiser walks downhill into this trap harder than a search does.
Caught by
Scoring both directions: model-to-image distance and the share of detected line ink the model actually explains. A shrunken model explains almost none of it.

0.42 px per step, 300 px wrong by the end

Looked right
Carrying the calibration forward frame to frame gave a median frame-pair residual of 0.42 px on about 390 inliers. Excellent at every single step, and every step was checked.
Actually true
Chaining 299 of those walks the pitch model roughly 300 px off the markings by the end of the window. The share of pitch ink explained falls from 93% to 13%. The camera zooms out over these 30 s and a multiplicative scale error compounds.
Caught by
Measuring the end of the chain against the image instead of the per-step residual. Every frame is now re-seated on its own line mask and the chain is used only as an initial guess for the next frame, which is the one job it is reliable at. A per-step residual well under a pixel says nothing about where the 300th composition lands.

The burned-in scoreboard cancels the camera pan

Looked right
Track features between consecutive frames to recover the camera motion. Plenty of features, plenty of inliers, low residual.
Actually true
The scoreboard graphic does not move at all, which makes it the single worst feature source in the frame: it drags the estimate toward the identity transform and quietly cancels the pan, while the crowd and the players move independently of the ground plane and corrupt it in other directions.
Caught by
Dropping everything above the projected far touchline, which removes the crowd, the boards and the score bug in one test, and dropping the tracked player boxes with a margin. What is left is grass, which is the only thing that moves exactly with the ground plane.

This pitch is not 105 metres long

Looked right
Fit the penalty area and the halfway line together on the regulation 105 m pitch. Residual 4.95 px, which reads as a good fit.
Actually true
Calibrating against both at once is an assumption about pitch length, and this ground is about 102 m. The instrument that reads the error is the centre circle, whose 9.15 m radius is fixed by Law everywhere: at 105 m it back-projects to 9.36 m with a 5.73 px median residual, at 102 m to 9.12 m with 0.00 px, and the overall fit bottoms out in the same place at 3.68 px.
Caught by
Sweeping the assumed length instead of inheriting it, and checking against a dimension the Laws fix rather than one the groundsman chooses. Anything downstream that assumes the regulation length is roughly 1.5 m out near the halfway line.

The widest point of a projected circle is not the projection of the widest point

Looked right
The centre circle and the penalty arc are obvious landmarks, so let the operator click the widest point of the circle and the apex of the arc.
Actually true
A circle projects to an ellipse, and the extreme point of that ellipse is not the image of the extreme point of the circle, so nobody can click it correctly. Measured on a real fit: the circle’s widest point came out at 23.2 px of error and the arc apex at 17.3 px, while every intersection landmark sat under 7 px.
Caught by
Per-landmark error reporting, which made three points stand out from thirty. Only points defined as the crossing of two features survive projection, so those are the only ones the calibration tool offers.

Identity

Recording only confirmed tracks hides the tracker’s own failure

Looked right
Store the detections the tracker has accepted and given an ID to. The output was clean, well formed, and nothing about it looked wrong.
Actually true
On one frame it stored 1 player where the detector had seen 23. The tracker’s worst moment was invisible in the tracker’s own output, by construction.
Caught by
Recording every detection with "id": null when unconfirmed, which makes the failure countable. This is the hockey pipeline’s coverage is availability, not correctness rule, and it generalises: a metric computed only over the rows a stage succeeded on cannot measure that stage.

One track ID, two different players

Looked right
The tracker fragments people into too many IDs, so a lower ID count is a better result. Every change was scored that way.
Actually true
It also merges two people into one ID. Track 110 runs 34 consecutive detections on a Denmark player and then 66 on a Germany player; track 318 is 15 red then 16 white. Sixteen tracks disagree with themselves about their own player’s shirt colour more than 15% of the time. So a distinct-ID count cannot distinguish the two faults: merging fragments lowers it, and conflating players lowers it too.
Caught by
Shirt colour, which was already computed for every track and is free. Colour-inconsistent tracks are split before anything is merged, cross-kit merges are refused outright, and the two faults are now reported as separate columns. Both swaps were confirmed by pulling the rendered frames back out of the video at each end, and reproduce exactly against an identity file fitted independently.

A player went invisible because two stages ran in the wrong order

Looked right
Identify teams, then repair the tracks. Both stages had been verified on their own, the labels were 110 of 110 correct, and the joined output was well formed.
Actually true
Stitching happens after labelling, so a merged track inherits the label of whichever fragment was its root — and a root can be a two-sample fragment. One Denmark player with 117 detections came out unknown and drew no icon on the pitch view at all. The stage boundary was correct; the stage order was not.
Caught by
Watching the rendered pitch view and noticing a player who was not there. Re-running identification on the repaired tracks took unresolved detections from 125 to 8. A verified stage feeding a verified stage is still not a verified pipeline.

Twelve goalkeepers

Looked right
Four colour classes — two kits, referee, keeper — cleanly separated in LAB space, with every track confidently assigned.
Actually true
It reported twelve goalkeepers on a pitch that can hold two. Ball crew and stewards in fluorescent bibs standing in front of the hoardings cluster with a green keeper kit. The general lesson: a colour class absorbs anything of that colour, and a broadcast frame contains a lot of people who are not players.
Caught by
Checking the count against what is physically possible. The separation is large once looked for: the two real keeper tracks sit 1.77 from the green cluster centre and the other ten sit 26.6 to 58.3, and all ten are independently too small for their distance from camera against a fitted perspective model. Hi-vis is now its own class.

Appearance ReID made the tracker worse

Looked right
Fragmentation is an appearance problem, so switching on appearance re-identification should fix it. It is the obvious lever and it is a supported option.
Actually true
On the default tracker it went from 57 IDs to 70, and sub-second tracks from 17 to 28. Tuning that tracker’s thresholds instead gave 37, which is better but still worse than simply changing tracker. ReID helps on the other tracker; the lever is not transferable.
Caught by
Running all seven configurations over identical detections so the only variable was the tracker, and diagnosing the real cause separately: 100% of the surplus IDs are born on detections below 0.70 confidence. Do not assume appearance helps. Measure it.

The ball, and metrics that flatter themselves

More input pixels made the ball harder to detect

Looked right
The ball is 10 to 19 px wide, so it is a small-object problem, so feed the detector more pixels. Raise the input from 1280 to 1920.
Actually true
Confidence on the test frame collapsed from 0.57 to 0.11. The detector was trained at 640, so upscaling pushes a 12 px ball out of the size distribution it learned. More pixels is not better here, which is the opposite of the intuition.
Caught by
Probing four frames at both input sizes before committing to either. The operating threshold was set the same way: the ball measured 0.57 / 0.68 / 0.69 on three frames and 0.03–0.08 on a fourth where it sat at a player’s feet, so candidate generation had to run below 0.03 or lose that whole class of frame by construction.

Coverage and smoothness both improved as the answer got worse

Looked right
100% ball coverage, and a smoothness check over consecutive detected frames that passed. Two independent healthy metrics.
Actually true
The path was jumping from (914, 374) to (1915, 754) to (462, 243) across consecutive sampled frames and then interpolating over its own jumps. Coverage was 100% because the interpolation filled everything, and the smoothness check skipped precisely the bad frames because they were separated by gaps. The root cause: a flat “miss” state carries no memory of where the ball was, so re-entering from it cannot be gated on anything.
Caught by
Watching the rendered ball track. The rebuild makes states pairs of observations so the cost is an acceleration residual rather than a position one — the camera pans, so a static white mark on the pitch moves frame to frame just as the ball does, but it cannot keep matching a constant-velocity prediction while the ball changes speed. Gaps are now crossed only within a distance and frame-count cap; when the ball genuinely goes away the path pays a restart cost and the gap is reported as a gap rather than bridged. Interpolation never spans a re-acquisition, and is flagged in the data.

The thread running through half of these. Six of the seventeen are not bugs in football code at all — they are a score that improved while the answer got worse. A degenerate fit scoring 0.00 px, a one-way distance rewarding a shrunken model, a per-step residual under a pixel hiding a 300 px drift, an ID count that cannot separate two opposite faults, a coverage figure computed only over the frames that succeeded, and a smoothness check that skipped exactly the frames that were wrong.

Every one was caught the same way: by looking at the rendered artefact, or by checking the number against something physical that the fit never saw — a 9.15 m radius, a 1.75 m player, a pitch that can hold two goalkeepers.