Skip to main content

M3: The Ball Gets a Vote

Trevor McCormick
Data Product @ Disney+

That clip up top: point_36 charting itself under loop v2 — ball boxed, the striker's box flashing at each contact, and the string s6f1f?b3b??3f2? assembling along the bottom. Watch the far box wander onto a spectator late in the clip. That matters below.

cv-08 ended with a confession: the striker column broke alternation wherever the event frame caught the ball mid-air between the players. This session replaced the striker logic — twice, because the first replacement died on contact with the data — and then ran the whole reel.

Proximity dies twice

v1 assigned each hit to whoever was closest at the event frame. Frame checks already showed the flaw: at the cusp of a detection window the ball is closest to nobody.

The obvious fix was ball direction — after a hit, court-y velocity should point away from the striker. It reads negative after hits from both ends. The homography assumes the ball is on the ground plane, so an airborne ball's "court velocity" is dominated by its vertical motion in image space. The homography lies about airborne balls — and it has been lying since M1. Landings survived because bounces happen on the plane; everything mid-flight was fiction. Nobody noticed until a striker rule tried to lean on it.

Alternation as the constraint, votes as the evidence

What replaced it: a rally has exactly two possible striker assignments — near starts or far starts — plus one parity flip wherever a hit could hide inside a ball-track hole. Holes are observable: SAM losing the ball for frames 54–78 of point_53 is exactly where a far hit vanished, and the flipped chain is frame-verified.

Each candidate assignment collects votes, strongest first: touch (weight 3) — the ball reached exactly one player's box in the contact window; the serve call (weight 2) — a prior, not an anchor; and landing half (weight 1) — a far-half bounce says the near player struck (the collapse detector is far-half-only by construction: one-sided, but honest).

Making the serve outvotable paid off the same day. point_59: SERVER-OVERRIDE. The gated serve detector said near; the ball said far; the frames say the ball was right.

Letters earn their commit

Once the striker is known, proximity is safe again — contact refines to the frame where the ball is nearest the assigned striker. The f/b letter is committed only if the ball actually reached that player's box, with the gate scaled by apparent height: a fixed pixel gate rejects every far-end letter, because the far player is 55 pixels tall. And player boxes go rogue at the worst moments — a "far" box on a spectator by the Perrier cooler, a "near" box on a court shadow — so those letters stay ?. Committed letters frame-verified: 3/3 on point_16, plus a point_24 spot-check.

The reel was won at the dry-run

Before spending anything, every one of the 54 prompt boxes went into a montage grid for eyeball review. That review killed 25 of the 29 mover-fallback bootstraps: glare, shadow edges, line junctions, the net band, a FedEx cooler, a ballkid. The ballistic three-point chain isn't enough during camera pans — static features form fake chains. The toss bootstrap got two free fixes in the same pass: the above-head search window now scales with apparent height (130 pixels above a 55-pixel far server was searching the crowd), and a ball-shape filter drops elongated racquet-arc blobs. That recovered seven far-end tosses the fixed window had thrown into the stands.

Sent 23 clips, got 18 tracks back. SAM lost five outright — giant stuck boxes, including every "weak accept" I talked myself into; the eyeball grade was the ground truth. Two API lessons for the record: an all-dropped result crashed the batch (now a graceful skip), and fal chunks long videos — past ~490 frames the API throws "No prompts available for this video chunk". point_55 worked trimmed to 450; point_58 — 893 frames, the 49-shot point — is parked until split-and-stitch.

The verdict

21 points charted, up from 5. Six far serves finally appear in the strings as s5/s6 — serve_zone() is actually called now; v1 defined it and then coded serves by full-court thirds. point_24 reads a complete s5f3b3?. Every string still ends in ? because endings aren't coded, and the conflicts column is the honesty metric: votes that lost the argument, kept on the record.

Bootstrap scorecard: toss 18/25 tracked (72%); mover 3/29 credible at dry-run (10%). The toss is the bootstrap; the mover fallback needs rethinking, not tuning. Session cost ~$1.50; project total ~$2.80 of the $9 budget — and the dry-run montage is why it isn't more.