Skip to content

chore: print track ids from the stitch probe - #37

Merged
ralyodio merged 7 commits into
masterfrom
chore/probe-print-ids
Aug 10, 2026
Merged

chore: print track ids from the stitch probe#37
ralyodio merged 7 commits into
masterfrom
chore/probe-print-ids

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

The stitch probe printed spans — what was matched — but not the thing a caller actually acts on. The ids are what get assigned, so they belong in the output.

  • each proposal line now carries its trackId
  • a final ALL: line joins the whole set, reference plus proposals, comma-separated — exactly the shape the identify endpoint takes, so it can be pasted straight in

Net change is 4 lines in scripts/stitch-probe.mjs. No production code, no test changes.

On the conflict

This branch was stacked on #36, which was squash-merged as 9e07183. That squash carries this branch's own 2833a9d, c8efaef, 73320b9 and aa3276b under one new hash with no ancestry link, so git saw scripts/stitch-probe.mjs as a conflict against a version master already contains.

Nothing to reconcile: master's tree is byte-identical to aa3276b, and this branch is aa3276b plus the commit above. Resolved to this branch's side and verified — git diff 5bffe86 is empty after the merge, i.e. the result is exactly the PR head's tree.

Verified locally on the merge result: pnpm test:run 566 passed / 8 skipped / 0 failed, pnpm typecheck clean, pnpm lint clean.

🤖 Generated with Claude Code

ralyodio and others added 7 commits August 10, 2026 12:46
Identifying a child only ever labels them where the user happened to look, and
re-identification could only confirm a binding where one already existed. On a
real game that left an athlete known for 31.7s of 300s across six fragments,
all inside the one 32-second window originally clicked. Every signal that
follows the athlete was dark for the other 90%, and the moments that survived
were scene-wide ones with nothing to do with them.

A jersey is the one thing about a child a detector can see that stays the same
all afternoon, so the worker gains an `appearance` command: a coarse HSV
histogram of the torso, from one pass over the 540p proxy. Boxes arrive on
stdin because there are thousands of them.

Colour is a veto, not an identifier, and that is the whole design. Measured
against the production game, three ways:

  colour only (>= 0.55)   661 of 1152 tracks, 2306s of "athlete" in a 300s video
  continuity only          56 tracks, 120.3s
  both                     14 tracks,  51.2s (from 6 tracks, 31.7s)

Teammates wear the same shirt, so colour alone selects a *team* — and the
children it wrongly volunteers are exactly the ones standing next to yours.
Continuity alone links whoever happens to be nearby. The identity claim
therefore rests on continuity — a fragment that begins where and when another
ended, within 2s and a distance a child could actually run — with colour able
only to rule a link out. At a 4s gap the accepted links reached 894px of a
1920-wide frame for eight more seconds of coverage, which is where that trade
stops being worth taking.

Nothing is assigned. Matches are pre-selected in the picker with the evidence
behind each one — the gap, the distance, the colour agreement — and a human
confirms, because the cost of a confident wrong answer is another family's
child in your highlight reel.

Also: `run` can write to a child's stdin, which the box list needs.

Verified against production via scripts/appearance-probe.mjs and
scripts/stitch-probe.mjs, both read-only; the numbers above are theirs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#35 shipped appearance matching and it returned zero matches on the very
footage it was designed against — where a read-only probe of the same algorithm
had found eight. Two faults, both in the seam between the parts, both invisible
to every unit test because each half was right on its own.

The coordinate space. Tracks are stored in source-video pixels, and appearance
deliberately reads the 540p proxy because a shirt's colour survives that and
decodes in a fraction of the time. The worker took the box space from the file
it had opened, so 1920-wide boxes were measured against a 960-wide frame at a
scale of 1: every torso rect landed off the right-hand edge, was clamped to
nothing or to background, and the signatures came back empty or meaningless.
The space now travels with the boxes, in the same JSON payload, because it is a
property of the coordinates and not of whatever file happens to be open.

The candidate floor. Proposals reused the picker's 1.5s minimum, which exists so
a human has a crop long enough to recognise. Stitching wants the opposite: short
fragments are the connective tissue, and five of the eight links that recovered
a real athlete were under 1.5s. Lowered to 0.25s, where continuity and colour
carry the claim instead of a person's eyes.

`decodePlanFor` is now a pure function with the scaling rule stated once, and
signatures.integration.test.ts builds a two-colour video with ffmpeg and checks
that a box quoted in a larger space still lands on the right half of it —
skipped where ffmpeg is absent, as the model test already is. Verified against
real pixels: left vs right 0.0000, left vs left 1.0000, both crops non-empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #35 was squash-merged as fea3483, which is this branch's own 2833a9d
with a new hash and no ancestry link. Git therefore saw the four files
that commit introduced as add/add conflicts against themselves.

There is no content disagreement to settle: origin/master's tree is
byte-identical to 2833a9d, so every conflict resolves to this branch's
side and the merge result is exactly the tree of c8efaef. Verified with
`git diff c8efaef` being empty after resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…footage

The reason #35 shipped returning zero matches is not that the judgement was
wrong — it was that nothing could exercise it. Every decision lived in a module
that also reaches a database, a subprocess and a filesystem, so importing any of
it drags in a native driver, so the only way to try it against real footage was
to re-implement it in a probe. A probe that agrees with a re-implementation
proves nothing about what ships, and the bug was in the plumbing between the two
halves, where neither was looking.

`stitch.ts` now holds the whole decision and touches nothing: the link rule, the
colour veto, the candidate filter and the iterative chain. `appearance.ts` keeps
only fetch, call and return. The probe imports the same `chooseAthleteTracks`
the app calls rather than a copy of it.

Run against the production project, read-only, with the shipped code:

  reference 6 track(s), candidates 2598, 10090 boxes, 600 frames in 5s
  +218.0-218.9s  gap 0.60s dist 120px colour 0.770
  +251.5-253.8s  gap 1.07s dist 379px colour 0.786
  +255.7-256.3s  gap 1.93s dist 152px colour 0.867
  +256.3-259.5s  gap 0.03s dist  60px colour 0.861
  +260.7-261.0s  gap 1.27s dist  85px colour 0.769
  +253.9-254.4s  gap 1.33s dist  50px colour 0.724
  +262.0-266.9s  gap 0.97s dist 405px colour 0.734
  +266.9-273.9s  gap 0.07s dist  52px colour 0.787
  before: 6 track(s), 31.7s
  after : 14 track(s), 51.2s

Ten tests over the isolated logic, including the two that matter: a matching
shirt that continues nothing is refused, and a fragment on screen beside the
athlete is refused however well its colour agrees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spans told you what was matched and not what to act on. The ids are what
gets assigned, so they belong in the output — including a comma-joined line of
the whole set, reference plus proposals, which is exactly the shape the
identify endpoint takes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #36 was squash-merged as 9e07183, which carries this branch's own
2833a9d, c8efaef, 73320b9 and aa3276b under a single new hash with no
ancestry link. Git therefore saw scripts/stitch-probe.mjs as a content
conflict against a version it already contains.

There is nothing to settle: origin/master's tree is byte-identical to
aa3276b, and this branch is aa3276b plus 5bffe86, whose only change to
that file is the four lines that print track ids. The conflict resolves
to this branch's side and the merge result is exactly the tree of
5bffe86, verified by `git diff 5bffe86` being empty afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio ralyodio changed the title Chore/probe print ids chore: print track ids from the stitch probe Aug 10, 2026
@ralyodio
ralyodio merged commit e6d9eb2 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