input: canvas-locked frames · output: validation.json, atlas, manifest, review.html
Three machine checks, then a human look
The line ends where automation stops being honest. Code can tell you the frame count came back, the base did not slide, and no key colour survived. It cannot tell you the gesture reads, or that the character's glasses vanished in frame four. So the last stage produces two things: a JSON verdict on the parts that are decidable, and a set of artifacts built for looking at.
-
06a
What
farmer/qa.py:105passmeansExactly three booleans, and
passis their conjunction. Everything else invalidation.jsonis evidence, not a gate.The checks dict in validation.json, and what each one can and cannot catch Check Condition Catches Blind to frame_countlen(frames) == declaredFrames lost between slicing and packing Nothing much — slicing already raised if the count was wrong jitter_under_limitmax anchor jitter ≤ JITTER_LIMIT_PX = 3.0A subject whose standing base slides between frames Identity drift, wrong pose, a limb amputated by the despill residual_key_under_limittotal residual key pixels ≤ RESIDUAL_LIMIT = 400Surviving magenta fringe or an unresolved emissive halo Over-keying: a subject eaten by the deep unmix scores zero here Per frame the report also stores the alpha bbox, the visible pixel count, the anchor jitter, the torso motion and the residual count — enough to tell which frame is the problem without opening an image. And two images are written for the case where you do want to open something: a contact sheet with an onion-skin panel appended, and a GIF at the declared fps.
farmer/qa.py:19–26anchor_jitter — measuring the thing that actually reads as broken"""Ground-anchor stability: centroid drift of the feet/base band. The band is fixed in canvas coordinates from frame 0's subject bbox, so a choreographed torso lean or a raised arm cannot pollute the measurement — only actual translation of the standing base registers (the artifact the eye reads as jitter/sliding). """
The band is the bottomFEET_BAND_FRACTION = 0.15of frame 0's subject rows, and it stays at those canvas coordinates for every frame — deliberately not recomputed per frame, which is what makes it immune to the choreography. -
validate() → pack() → write_review_page()
-
06b
Packing, and the review loop
farmer/pack.py · farmer/qa.py:226Packing is short and strict: per-frame PNGs, a near-square atlas laid out row-major at
cols = ceil(sqrt(n)), and a manifest with absolute rects, frame size, fps and loop flag. Non-uniform frame sizes are rejected outright, because a canvas-locked run is the precondition for everything downstream.Then the loop that closes the line.
write_review_page()emits a self-contained HTML file — no server, no dependencies, no network — showing every frame on a checkerboard with a keep/reject checkbox, the GIF, the contact sheet, and a button that copies acuration.jsonto the clipboard.Figure 13 — the loop that closes the line. The review page is a single HTML template with an inlined script ( qa.py:161–223) and a clipboard fallback that builds a hidden textarea whennavigator.clipboardis unavailable — which is what happens when you open the file from disk.$ cat curation.json {"name":"guide-gesture-presenting","keep":[0,1,2,3,5],"reject":[4]} $ python3 -m farmer apply-curation runs/gesture-presenting curation.json { "name": "guide-gesture-presenting", "kept": 5, "rejected": [ 4 ], "frames_before": 6, "revalidated_pass": true }
Export targetsBeyond the manifest,
--site-exportwrites the CatPlayer runtime format:{name}_{i}.pngfiles plus ananim.jsonthat is merged into any existing one rather than overwritten, so several animations can share one export directory (pack.py:94–105). -
and underneath all of it: tests/run_tests.py
-
06c
A self-test that calls nothing
tests/ · farmer/cli.py:144Thirteen files, plain
assertstatements, no framework. Each runs in its own interpreter and passes if it exits zero; the runner's exit code is the number of failures. Every fixture is synthetic — magenta sheets drawn at 4× supersampling so the blob edges are genuine blend pixels, and mock providers that repaint the template they were handed. Nothing reaches the network.$ python3 -m farmer selfcheck [PASS] test_align.py (2.5s) [PASS] test_composite.py (0.2s) [PASS] test_curation.py (0.4s) [PASS] test_despill.py (0.3s) [PASS] test_grids.py (0.2s) [PASS] test_juice.py (0.2s) [PASS] test_manifest.py (0.2s) [PASS] test_overlay.py (1.2s) [PASS] test_providers.py (0.5s) [PASS] test_registry.py (0.2s) [PASS] test_silhouette.py (0.6s) [PASS] test_slice.py (0.2s) [PASS] test_states.py (1.3s) 13/13 test files passed
Figure 14 — the test surface. The interesting design choice is in the footer: both fixtures were built to make the assertions meaningful rather than merely green. A mock that returned the template unchanged would pass composite-back trivially; this one injects drift so the guarantee has something to remove. A footnote on running itselfcheckshells out withsys.executable, so it uses whatever interpreter invoked it. On a machine where the system Python lacks numpy, all thirteen files fail on import — which is a packaging observation, not a code failure. Inside the project's environment the suite is green in roughly eight seconds. -
06d
Where the guarantees stop
README.md · DESIGN.mdEverything this pipeline can prove, it proves in code and writes down. It is worth being equally clear about the boundary of that.
-
identity drift
Nothing measures whether the character in frame 4 is the same character as in frame 0. Accessory details and small faces wander; the anchor sheet reduces it and the contact sheet exposes it, but the check is a person looking.
-
over-keying
The residual metric counts key colour that survived. A subject partly eaten by the deep unmix scores zero and passes. Only the onion-skin panel and the GIF catch that.
-
gesture quality
Torso motion is recorded and explicitly not gated. Whether the presenting gesture reads as presenting is not a decidable property, and the code does not pretend otherwise.
-
plane cutting
Silhouette refinement degrades toward the bounding rectangle when object and surroundings share colour statistics, and keeps occluders that sit inside the box. The module documents this in its own docstring under the heading “Honest scope”.
“Inspect outputs visually after every stage —
skills/claude/SKILL.md, the instruction given to the calling agentReadthe contact sheet and preview GIF; never claim success without looking.”That instruction is the honest summary of the whole system. The Python is there so that when the agent does look, everything it sees is a real consequence of the generation rather than an artefact of the plumbing.
-
identity drift