Skip to content

fix: four reasons a five-minute game suggested nothing - #34

Merged
ralyodio merged 1 commit into
masterfrom
worktree-scoring-focal-fixes
Aug 10, 2026
Merged

fix: four reasons a five-minute game suggested nothing#34
ralyodio merged 1 commit into
masterfrom
worktree-scoring-focal-fixes

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Diagnosed against the production project 7th-grade-boys-basketball (300s, 1,415 tracks), which reported 0 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

  1. Absent ≠ idle. player_acceleration, toward_goal and activity_near_goal returned 0 rather than null when 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 misleading highest reachable 1.000.

  2. The diagnosis now reports focal coverage. focalSeconds / focalTrackCount / durationSeconds, and a binding covering <5% of the footage names itself as the likely cause.

  3. Re-identification can now grow an athlete. rebindAthletes took 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.

  4. 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:

binding on screen ceiling moments
"My athlete" (the thin one) 0.3s of 300s 0.435 0 → 1
"Joe" 31.6s of 300s 1.000 1

The thin binding now says so instead of blaming the footage.

12 new tests in thinfocal.test.ts and rebindgrow.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

…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>
@ralyodio
ralyodio merged commit 83eb88f into master Aug 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant