Skip to content

feat: judge a basketball by what a basketball looks like - #43

Merged
ralyodio merged 1 commit into
masterfrom
feat/ball-recall
Aug 10, 2026
Merged

feat: judge a basketball by what a basketball looks like#43
ralyodio merged 1 commit into
masterfrom
feat/ball-recall

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

"and also the ball is hardly ever identified. we need to work on that but its getting a lot better."

I measured before changing anything, on 20s of the reported game at the shipped 2×2 grid. The obvious lever is the wrong one.

3×3 tiling makes it worse

2x2 (shipped)   ball 15 tracks / 173 positions   rim 316   90s
3x3             ball 13 tracks / 127 positions   rim 100   188s

Tiles get smaller, but the whole-frame pass has to downsample further to feed them, and the rim loses more than the ball gains — for twice the runtime. The grid stays at two, and the preset now carries a comment saying why, because 3×3 is the first thing anyone will reach for next.

The confidence floor is the lever

Swept over the same clip:

floor ball tracks / positions rim positions player tracks
0.25 (today) 15 / 173 316 279
0.18 16 / 206 351 294
0.12 17 / 253 379 285
0.08 17 / 293 403 313
0.05 16 / 330 427 294

Ball track count stays flat while positions nearly double. That's the shape of better recall, not more noise — a looser detector invents short spurious tracks, it doesn't lengthen the ones already there.

Per-class, not global

Lowering it globally would loosen people too. A basketball is a handful of pixels and the model is right to be unsure; a player fills a fifth of the frame and a 0.08 "person" is junk.

The model runs once at the most permissive floor any class asks for, and each detection is then held to its own class's standard. The decoder can't do this itself — it works in class indices, before the sport's mapping exists. ByteTrack's low-score pass follows the same floor, since re-attaching exactly these faint detections is what that pass is for.

Defaults: ball/puck 0.08, hoop/net 0.15, everything else unchanged.

Verified through the real worker CLI

before  ball 15/173 | hoop 5/316 | player 279/8457 | 90s
after   ball 17/293 | hoop 6/359 | player 279/8457 | 91s

+69% ball positions, +14% rim, players identical to the byte, same cost.

584 tests pass, 8 skipped. Lint and build clean. Merged with current master.

3 of 3 from the report — #40/#41 (duplicate athletes, live UI) are already in; #42 is jersey/team.

🤖 Generated with Claude Code

"the ball is hardly ever identified. we need to work on that."

Measured before changing anything, on 20s of the reported game at the shipped
2x2 tile grid. The obvious lever turned out to be the wrong one:

  3x3 tiling   ball 13 tracks / 127 positions, rim 100 — against 15 / 173 and
               316 for 2x2, at twice the runtime.

Tiles get smaller, but the whole-frame pass has to downsample further to feed
them, and the rim loses more than the ball gains. So the grid stays at two, and
the comment in the preset now says why, because 3x3 is the first thing anyone
will reach for next.

The lever that does work is the confidence floor, swept over the same clip:

  floor   ball tracks / positions   rim positions   player tracks
  0.25         15 / 173                  316             279
  0.18         16 / 206                  351             294
  0.12         17 / 253                  379             285
  0.08         17 / 293                  403             313
  0.05         16 / 330                  427             294

Ball *track* count stays flat while positions nearly double. That is the shape
of better recall rather than more noise: a looser detector would invent short
spurious tracks, not lengthen the ones already there.

Lowering it globally would have loosened people too, so the floor is now
per-class. A basketball is a handful of pixels and the model is right to be
unsure about it; a player fills a fifth of the frame and a 0.08 "person" is
junk. The model runs once at the most permissive floor any class asks for and
each detection is then held to its own class's standard — the decoder cannot do
this itself, since it works in class indices before the sport's mapping exists.
ByteTrack's low-score pass follows the same floor, because re-attaching exactly
these faint detections is what that pass is for.

Verified through the real worker CLI on the same clip:

  before  ball 15/173 | hoop 5/316 | player 279/8457 | 90s
  after   ball 17/293 | hoop 6/359 | player 279/8457 | 91s

+69% ball positions and +14% rim, with the player numbers identical to the
byte, at the same cost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 8bbbbd9 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