Secondhand Motion

Pipeline: archive shot → doodle cartoon

One shot is one folder, shots/<category>/, and one state file, shot.json. Every stage reads files from that folder and writes new files to it; every page is a view of those files. Stages are numbered so a ledger line can say where a repair went back to.

Two lanes have been tried, and the pipeline joins them:

# Stage Reads Writes Runs where Gate
0 Select dataset/clips.csv, data/subject_queue.json source.json, contact-sheet.png agent + browser (archive video) continuous event, subject ≥ 1/10 of frame
1 Observe source video clip.json (frames at 12 fps), camera.json, scene_masks.json browser: SAM 2.1 + Grounding DINO; Python: phase-correlation pan mask on ≥ 95% of frames
2 Identity Quick, Draw! category file design.json, design.png CLIP joint space + classifier + agent pick doodle recognised, typical stroke style
3 Plan masks, footage x-sheet draft in shot.json agent each key has a written reason
4 Keys (look) frames, design, plan keys/art-key-*.png, keys/bg-plate.png, prompts image model or agent drawing identity critic; M1; B1
5 Trace raster keys keys/traced.json tools/trace_keys.py T1: recall ≥ 0.98, parts_kept = 1.0
6 Rig traced keys, masks poses.json agent + rig.py M3 mask fit; rig redraw matches the trace
7 In-between poses, camera, masks per-frame cast strokes inbetween.py M4–M6
8 Planes bg plate, masks, doodles shot.json build_shot.py each object names its source and plane
9 Tests shot.json tests.json tests.py all measured and structural tests
10 Critic critic packet only reviews/*.json fresh agent stills, then playback at normal speed
11 Ledger every proposal and verdict ledger.jsonl loop.py append-only, hash-chained
12 Pairs accepted shots pairs/ script only playback-accepted shots are train-eligible

The two lanes, measured on one scale (20 Oct 2026)

tools/style_score.py simplifies any drawing the way Quick, Draw! simplified its own data, then compares six stroke statistics with real doodles of the category (data/lane_compare.json):

tools/match_strokes.py gives the look lane its first motion: keys aligned by translation, strokes matched key to key, matched strokes interpolated and the rest swapped at the midpoint (test T3). Both lanes play side by side with their footage in pages/projector.html.

Every subject, redrawn the giraffe way (20 Oct 2026)

tools/puppet.py does by hand, for each subject, what Giraffe Walks Past did: the chosen Quick, Draw! doodle itself (every stroke kept) gets a few joints, the agent places those joints on the footage at each key (tools/look.py gives a gridded key sheet), bones carry the strokes by linear blend skinning, in-betweens interpolate the joints with planted contacts, and the background is drawn once. Then tools/shot_tests.py runs the shot tests and tools/critic_packet.py makes a blind packet: a fresh critic sees eight frames of the cartoon only, names what it is, and then judges identity against the doodle.

What happened, across ten subjects:

Where learning comes in

Nothing has been trained yet. What exists is search: an agent proposes, tests and critics judge, the ledger keeps every attempt. That is the data a learner would need:

  1. Pose from silhouette. Each accepted key is a pair (footage frame + SAM mask → rig pose). With a few hundred pairs per body plan (quadruped, bird, vehicle), a small model can propose keys that the agent then corrects; the corrections are the next round's data.
  2. Preference over repairs. Each ledger line holds a before, an after and a critic verdict. Those are preference pairs for a reward model over drawings, scored together with the measured tests, not instead of them. The classifier alone is a misleading reward: it accepts traced silhouettes.
  3. In-betweening. Accepted shots give dense key → in-between sequences in one topology, which is what a learned in-betweener needs. Until then inbetween.py is the baseline to beat.

Training starts when a pair set exists that a fresh critic has approved at playback speed. Still-key approval alone does not make a pair train-eligible.

Tests

Current tests, as shots/giraffe-walks-past/tests.py computes them, plus the two the join adds:

Id Name Bar Kind
M1 Reads as the category classifier p above 0.75 (real human doodles score 0.75) measured
M2 Looks hand-drawn stroke-style percentile at least 30 among real doodles (traced outlines: 36) measured
M3 Holds the footage pose mask fit mean at least 0.65, no frame below 0.45 measured
M4 Planted feet stay put under 2 px drift, on at least 4 planted intervals measured
M5 Body keeps its proportions under 10% variation for each body segment measured
M6 Head travels on smooth arcs under 4 px jerk measured
T1 Trace keeps the drawing recall at least 0.98 and every ink piece kept measured (new)
B1 Background holds at least 90% of background strokes unchanged between keys on a locked-off shot measured (new)
S1 Near planes move faster sky slowest, foreground fastest structural
S2 Every drawing has a source all objects structural
S3 Exposure sheet covers the shot every frame, keys marked structural
S4 Page works on a phone no sideways scroll at 390 px, no script errors structural
S5 Nearer things hide farther ones no far lines inside the character structural
J1 Story reads with sound off a fresh viewer names the action and every subject judged
J2 Drawn beats traced blind A/B preference for the drawn version judged
J3 Keys alone tell it keys-only playback reads as the action judged

Lessons that set these bars:

Scaling to more drawings