input: an action word and an accepted anchor · output: one raw sheet PNG
Everything that happens before a pixel is generated
The first four nodes of the trunk exist to make the generation boring. By the time a provider is called, the pose of every cell has been written out in prose, the master frame has been stamped into every cell at a fixed scale and foot line, and the prompt carries an explicit contract about what the background must be. The model is left with one job: change the pose.
-
01a
Action word → per-cell script
farmer/choreograph.pyTwelve keys map to eight template functions. Each function holds an ordered phase list — six phases for the character actions, four for the object presets — and
_resample()spreads that list across however many frames were requested. It is a lookup, not a generation; the file makes the position explicit in its own docstring.Figure 2 — resampling a phase list. The same six lines serve any frame count; only the sampling changes. Object presets store four phases instead of six, which is why flickerat its default three frames lands on phases 1, 3 and 4.Hard stopLOCOMOTION_WORDSholds thirteen entries — walk, walking, run, running, jog, sprint, stride, locomotion, crawl, climb, swim, fly, flying. If the action's words intersect that set and no--scriptwas passed,run_strip()raises atpipeline.py:61, before the anchor sheet is even built. The comment gives the reason: grid sheets were found to fail for locomotion, so the command routes you to an image-to-video brief instead of producing something bad.$ python3 -m farmer character --name hero --action "walk cycle" --frames 6 \ --anchor characters/guide/anchor.png --out out/hero --subject "x" --provider auto [providers] auto: using 'gemini' (skipped: codex: dispatch script not found at /Users/…/.claude/plugins/codex-image/scripts/codex-image.mjs — set FARMER_CODEX_IMAGE_SCRIPT or pick another provider.) Traceback (most recent call last): File "/…/farmer/cli.py", line 37, in cmd_character report = run_strip( ^^^^^^^^^^ File "/…/farmer/pipeline.py", line 61, in run_strip raise ValueError(f"{action!r} is locomotion — use `farmer brief` (i2v) instead") ValueError: 'walk cycle' is locomotion — use `farmer brief` (i2v) instead
The replacement command emits a start frame and a prompt, and calls nothing:
$ python3 -m farmer brief --start-frame characters/guide/anchor.png \ --action "walking left to right" --out out/guide-walk \ --subject "the clay guide character" \ --style "handcrafted claymation miniature style" --seconds 5 { "tool": "Dreamina / Seedance (image-to-video)", "start_frame": "start-frame.png", "duration_seconds": 5, "prompt": "the clay guide character performing: walking left to right. Art style: handcrafted claymation miniature style. Perfect seamless loop: the last frame matches the first frame exactly. locked-off static camera, no camera motion, no zoom, no cuts. The subject stays in place (treadmill-style walking left to right if locomotion) — centered, constant scale, feet on a constant baseline. Plain unchanging background exactly as in the start frame. Natural weight and secondary motion; no morphing, no identity drift, no added objects.", "post": "Downstream: sample frames from the returned video at the target fps, then run `farmer cutout`/align/pack on the sampled frames if a sprite strip is needed, or ship the video loop directly." }
-
scripts: list[str] → grids.build_anchor_sheet()
-
01b
The anchor-tiled sheet
farmer/grids.pyThis is the anti-drift device. Rather than describing the character again in words, the pipeline builds an image where the accepted master frame already occupies every cell at the exact intended scale and ground contact, and hands that to the model as the last reference. The prompt then only has to say change the pose.
Figure 3 — anchor-sheet geometry. Numbers computed from the repository's own characters/guide/anchor.pngusing the character path's defaults. Because the same transform is applied to every cell, camera distance, anatomical scale and ground contact are identical across the sheet before generation begins.Grid selection, sheet size and resulting cell resolution Frames requested Grid Sheet Cell Filler cells 2, 3, 4 2 × 2 1024 × 1024 512 × 512 up to 2 5, 6 2 × 3 1536 × 1024 512 × 512 up to 1 7, 8, 9 3 × 3 1024 × 1024 341 × 341 up to 2 10, 11, 12 3 × 4 1536 × 1024 384 × 341 up to 2 13 – 16 4 × 4 1024 × 1024 256 × 256 up to 3 A consequence worth namingCell resolution collapses as frame count rises: a sixteen-frame sheet gives each frame a 256 px cell, one quarter the linear resolution of a four-frame sheet. Nothing in the code warns about this. Frame counts above nine are supported arithmetically but the output is small.
Filler cells are never left blank.
build_sheet_prompt()pads the script list up torows × colswithEXACT repeat of frame 1's pose (loop-return filler frame)
, the pipeline declares the full cell count to the slicer, and the extra frames are dropped afterwards with a single slice:sliced = sliced[:frames](pipeline.py:106). -
anchor-sheet.png + scripts → prompts.build_sheet_prompt()
-
01c
The prompt contract
farmer/prompts.pyFour builders share one structure and one closing block of strict rules. The rules are not stylistic; each one exists to protect a downstream stage. Read them as preconditions for despill and slicing.
Each prompt invariant and the stage it protects Rule in the prompt Protects Failure it prevents Background is 100% solid flat magenta #FF00FF; no gradients, vignette, floor or background shadowdespill pass 1 A gradient background falls outside the colour-distance ball and survives the hard cut No bloom, halo, glow gradient or cast light painted onto the background despill halo retry Emissive glow blended into the key sits beyond the normal unmix reach and leaves a pink corona Nothing may touch or cross a cell edge; subject stays inside the central 70% slice Two frames fusing into one connected component, which is a hard SliceErrorNo borders, lines or frames between cells slice Drawn separators become their own components and confuse the seed ranking Same camera angle and distance; anatomical scale constant align, canvas lock Scale drift that registration cannot correct, since alignment is translation-only No text, labels, watermarks or UI slice, QA Stray marks attaching to the nearest frame as “noise” farmer/prompts.py:48–56build_sheet_prompt(), the anchor clauseparts.append( "The reference images own the subject's identity and geometry — this generation " "owns MOTION ONLY. The last reference image is a scale-and-root template: the same " "accepted master frame repeated in every cell at the exact intended position. " "Preserve its exact cell locations, camera distance, standing-equivalent anatomical " "scale, body proportions, foot-contact line, and padding. Change ONLY the pose in " "each cell per the frame list below. Never zoom, resize, restyle, or redesign the " "subject. Never reproduce guide boxes, borders, labels, or separators.")The written prompt is saved toprompt.txtin the run directory before generation, so a bad sheet can be diagnosed against the exact text that produced it. -
prompt + refs[] → provider.generate()
-
01d
One call, four possible backends
farmer/providers/The provider layer is deliberately small: an abstract
generate(), aCapabilitiesdataclass, and averify_png()helper that re-opens and loads the file before it is handed back. Two of the four backends shell out to a CLI; the other two speak HTTP with nothing but the standard library, because the package refuses to grow a dependency beyond pillow and numpy.Figure 4 — provider selection. Skipped candidates are not silent: the chain prints the list of reasons alongside the provider it settled on, which is exactly what the locomotion transcript above shows happening. Provider capabilities, declared in each module's Capabilities instance Provider Transport Prerequisite Max refs Edit endpoint Native transparency Fixed sizes codex(default)node dispatch script, 560 s timeout FARMER_CODEX_IMAGE_SCRIPT5 no no 1024², 1536×1024, 1024×1536 geminigemini --yoloslash commandgeminion PATH orFARMER_GEMINI_CLI3 yes no any openaiurllib + hand-rolled multipart OPENAI_API_KEY4 yes yes 1024², 1536×1024, 1024×1536 falurllib, refs as data:URIsFAL_KEY4 yes no any Live coverageOnly
codexhas been exercised live end-to-end. The other three are implemented and unit-tested at the level of selection behaviour and, for gemini, a mock CLI —tests/test_providers.py. Treat their real-world behaviour as unproven, which is what the repository's own limitations section says.“NOTE (spec §4.7): the edit endpoint regenerates the whole canvas; pixel stability is enforced by the pipeline's composite-back, never by trusting this provider.”
farmer/providers/openai_api.py:10–12