fix: four reasons a five-minute game suggested nothing - #34
Merged
Conversation
…nst the run that did it Production: 1,415 tracks, a rim seen for 28s, a ball for 23s, and every line the user was shown reading plausibly — "tracks found", "athlete identified: yes", "highest reachable 1.000", "none scored above 0.35". Together those say the footage was dull. The footage was fine. The athlete every focal signal depends on was bound to a ten-frame fragment lasting 0.3s of 300s, and nothing on screen said so. - Absent is not idle. `player_acceleration` and `toward_goal` returned 0 rather than null whenever the athlete had no position, as did `activity_near_goal` with no rim in frame. That is the mistake ball proximity was already fixed for, and it costs more: it kept 0.35 of weight in every denominator, and reported those signals as measurable, which is what pushed the stated ceiling to 1.000 — telling the user a threshold was reachable that arithmetically was not. - Say how much of the game the athlete is actually on screen for. `focalSeconds` and `focalTrackCount` join the diagnosis, and a binding covering under 5% of the footage now names itself as the likely reason instead of leaving "athlete identified: yes" to imply otherwise. - Re-identification could only shrink an athlete. Matching took the single best new track per old track, so N fragments in gave at most N out, however the new run cut the same child up. The 0.3s binding therefore survived two re-detections intact, over runs producing 3,948 and 1,415 tracks. Every fresh fragment clearing the threshold is now attached. - Do not detect from a proxy smaller than the size the preset detects at. The 540p editing proxy is shorter than every inference size above `fast`, and the worker decodes to its own input size regardless, so the proxy was *upscaled* — identical inference cost for strictly less picture. Measured on this 1080p game, the same preset found 145,975 detections across 3,948 tracks from the source against 67,985 across 1,415 from the proxy. The ball goes first. Verified by re-scoring the production database with the new code (see scripts/rescore-probe.mjs, read-only): the thin binding now reports 0.3s of 300s with a 0.435 ceiling and names itself, and the run goes from 0 moments to 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Diagnosed against the production project
7th-grade-boys-basketball(300s, 1,415 tracks), which reported0 suggested moment(s)across three runs while every explanatory line read plausibly.What was actually wrong
The focal athlete was bound to a 10-frame track spanning 0.3s of 300s.
ath_58d23250("My athlete",is_focal=1) → 10 points, ts 0→0.3. The good binding —ath_793506a5("Joe", 932 points, ts 219.5→251.1) — was not focal and was ignored. Nothing in the UI or the job log surfaced this; "athlete identified: yes" is equally true of both.Fixes
Absent ≠ idle.
player_acceleration,toward_goalandactivity_near_goalreturned0rather thannullwhen the athlete (or the rim) was not in frame — the mistake ball proximity was already fixed for. It kept 0.35 of weight in every denominator while contributing nothing, and reported those signals as measurable, which is what produced the misleadinghighest reachable 1.000.The diagnosis now reports focal coverage.
focalSeconds/focalTrackCount/durationSeconds, and a binding covering <5% of the footage names itself as the likely cause.Re-identification can now grow an athlete.
rebindAthletestook the single best new track per old track, so N fragments in gave at most N out. The 0.3s binding survived two re-detections intact. Now every fresh fragment clearing the similarity threshold is attached.Never detect from a proxy smaller than the inference size. The 540p editing proxy is shorter than every inference size above
fast, and the worker decodes to its own input size regardless — so the proxy was upscaled. Same inference cost, strictly less picture. Measured on this footage: 145,975 detections / 3,948 tracks from the 1080p source vs 67,985 / 1,415 from the proxy, same model.Verification
scripts/rescore-probe.mjs(read-only) re-scores an existing project database with the current scoring code. Run against production:The thin binding now says so instead of blaming the footage.
12 new tests in
thinfocal.test.tsandrebindgrow.test.ts; the re-bind test fails on the old code (1 track instead of 3). Full suite: 516 passed, 5 skipped. Lint and build clean.Still open
The athlete is trackable for only 31.6s of the 300s game even when bound correctly — the tracker fragments people and nothing re-links fragments across an occlusion. That is a separate change (track stitching) with real wrong-child risk, not included here.
🤖 Generated with Claude Code