The linesolid node = trunkdashed node = fork

assetfarmer Stage 00 · the line v0.1.0 · MIT · pillow + numpy only

farmer/ — 3,920 lines across 24 modules · tests/ — 13 files, 1,108 lines

One generation in. Registered frames out.

assetfarmer turns a single keyed-background image into an animation strip whose frames are pixel-registered, whose background is genuinely gone, and whose failures are loud. An agent writes the choreography and judges the result; deterministic Python does every operation where a model would drift. This page is the line itself — follow it left to right, then drop into any stage.

The trunk: run_strip()

Nine stages, in order, inside one function — farmer/pipeline.py:54. Every stage writes its output into the run directory before the next one starts, so a failure is always inspectable. Follow the arrows; each node opens further down this page.

The assetfarmer line: agent lane above, deterministic code lane below Two horizontal lanes. The upper lane lists the four judgment acts the calling agent performs: writing the per-cell pose script, reading the contact sheet and preview GIF, ticking frames in review.html, and choosing plane bounding boxes. The lower lane is the nine stage trunk of run_strip in pipeline.py: choreograph, anchor sheet, prompt, generate, despill, slice, align, validate, pack, joined by arrows. Beneath the trunk, tick lines connect stages to the files they write into the run directory. At the bottom, three dashed forks leave the trunk: locomotion actions divert to brief.py, registered objects divert to states.py, and scene plates divert to layers.py and scenes.py. AGENT LANE — judgment, creativity, regeneration writes -‌-script poses.json reads contact-sheet.png, preview.gif ticks frames in review.html chooses plane bboxes in regions.json no LLM is called from inside the CLI — choreograph.py:5 CODE LANE — run_strip(), pipeline.py:54 choreograph choreograph.py 6 pose lines anchor sheet grids.py 2×3 on #FF00FF prompt prompts.py + strict rules generate providers/ 1 image call despill despill.py 4 passes slice slice.py components align align.py canvas lock validate qa.py pack pack.py anchor-sheet.png prompt.txt raw-sheet.png sheet-transparent.png work/work_i.png validation.json manifest.json review.html contact-sheet.png FORKS — other entry points reusing the same organs brief.py — i2v handoff if is_locomotion(action) states.py — object states reuses despill + slice, adds composite_back layers.py / scenes.py — plates no chroma key at all; RGB decomposition Hard stops the trunk enforces SliceError when the declared frame count is not recoverable · ValueError on locomotion actions · ProviderUnavailable raised at selection time, never mid-pipeline · RuntimeError if composite-back is violated
Figure 1 — the whole line. The upper lane is everything a model is trusted with; the lower lane is everything it is not. The trunk is literally the body of run_strip() in farmer/pipeline.py:54–138, and each dashed tick is a file written to the run directory as that stage completes.

“The calling agent is the creative brain: for anything not in the template library it writes its own per-cell script and passes it through. No LLM is called from inside the CLI.”

farmer/choreograph.py:4–6

Walking the trunk

  1. 01

    Choreograph

    farmer/choreograph.py

    An action word becomes one explicit pose line per cell. Eight template functions cover idle breathing, gesture presenting, stamp letter, wave and the four object presets; _resample() spreads a six-phase list across whatever frame count you asked for. Anything else is the agent's job — pass a JSON list of exactly N strings via --script.

    Locomotion is rejected here on purpose. LOCOMOTION_WORDS (choreograph.py:16) holds thirteen words; if the action intersects that set and no explicit script was given, run_strip() raises before a single image is generated.

    Stage 01 in full →

  2. 02

    Anchor sheet

    farmer/grids.py

    The accepted master frame is tiled into every cell of the grid at one fixed scale and one fixed foot line, on flat #FF00FF. The generator is then only asked to change the pose. Grid choice is table-driven and never a single row — _GRIDS maps 4 → 2×2, 6 → 2×3, 9 → 3×3, up to 16 → 4×4.

    Sheet geometry →

  3. 03

    Prompt

    farmer/prompts.py

    Four builders, one shape: subject, style, reference notes, the numbered per-cell list, then a strict-rules block that spells out the magenta contract — no bloom on the background, no cell borders, nothing crossing a cell edge, subject inside the central 70% safe area. Unused cells are padded with EXACT repeat of frame 1's pose (loop-return filler frame) so no cell is ever left empty.

    The invariants →

  4. 04

    Generate

    farmer/providers/

    One image call per action. Providers declare capabilities, are constructed fail-fast — a missing dispatch script or unset API key raises ProviderUnavailable from the constructor — and every one of them re-opens and PIL-verifies the file it produced rather than trusting a printed path.

    Provider matrix →

  5. 05

    Despill

    farmer/despill.py

    Four passes: a hard colour-distance cut, a Chebyshev depth map out of the keyed region, a soft-alpha unmix that solves observed = (1-k)·subject + k·key so antialiased coverage survives as partial alpha, then a trapped-spill correction that fixes colour without touching alpha. An adaptive retry handles emissive halos, and refuses to run when the subject is genuinely magenta.

    Stage 02 in full →

  6. 06

    Slice

    farmer/slice.py

    Frames are recovered as connected components, not by cutting on grid lines. The declared count must come back or SliceError is raised with every component's area and bbox in the message. Grid cutting exists as --slice-mode grid, an explicit opt-in, never an automatic fallback.

    Stage 03 in full →

  7. 07

    Align

    farmer/align.py

    Each frame is shifted to maximise alpha overlap with frame 0 — but only the upper 65% votes, so a raised arm cannot drag the body. The union bounding box becomes a canvas lock shared by every action of that character; if a later action needs a bigger box, earlier actions are re-cropped from their stored working canvases.

    Registration →

  8. 08

    Validate

    farmer/qa.py

    Three booleans decide pass: frame count recovered, maximum anchor jitter ≤ 3 px, total residual key pixels ≤ 400. Torso motion is recorded but not gated — it contains the choreography you asked for. The contact sheet ships an onion-skin panel next to the frames so drift is visible at a glance.

    Stage 06 in full →

  9. 09

    Pack

    farmer/pack.py

    Per-frame PNGs, a near-square atlas, a manifest with absolute rects and fps, a GIF preview and a dependency-free review.html. Packing refuses non-uniform frame sizes outright — pack requires canvas-locked frames.

    Packing →

Where the line forks

Three commands leave the trunk. Two of them reuse its organs; one refuses to use it at all.

The command surface

Eleven subcommands, one argparse parser, built in farmer/cli.py:175–299. The module docstring at the top of that file is what --help prints:

assetfarmer — python3 -m farmer --help
$ python3 -m farmer --help
usage: farmer [-h]
              {character,object,environment,adopt,state,transition,scenevar,apply-curation,selfcheck,cutout,brief}
              ...

assetfarmer CLI.

Subcommands:
  character    grid-sheet animation pipeline for a character action
  object       same pipeline with 2-4 frame loop presets (flicker/pulse/sway/steam)
  environment  cut a plate into parallax planes + inpainted base
  cutout       single frame: (generate ->) despill -> RGBA PNG
  brief        emit a Dreamina/Seedance i2v handoff for locomotion
  adopt        migrate pipeline output into the characters/objects/scenes registry
  state        generate a semantic state for a registered object (composite-back)
  transition   generate an endpoint-locked transition strip between two states
  scenevar     generate a scene variant plate + extract its lighting overlay
  apply-curation  drop review-rejected frames from a run and repack
  selfcheck    run the deterministic test suite (tests/run_tests.py)

options:
  -h, --help            show this help message and exit
Exit codes

Pipeline commands return 0 when the report's pass is true and 2 when it is false (cli.py:46, 66, 112, 125, 135). selfcheck returns the number of failing test files. A shell loop can gate on that without parsing anything.

What is built, and what is only declared

Accuracy about maturity matters more than a feature list. Everything below was read out of the tree, not out of the README.

Implementation status, verified against the source and the committed registries
AreaStateEvidence
Despill, slice, align, pack, QA Implemented, unit-tested tests/test_despill.py, test_slice.py, test_align.py, test_manifest.py
Composite-back guarantee Implemented, asserted end-to-end tests/test_states.py drives run_state() with a mock provider that injects +1 drift on every subject pixel, then asserts bit-identity outside the region
Scene lighting overlays Implemented Committed scenes/lab night variant records mean_abs_diff 2.076, 7.81% of pixels off by 8 or more
Providers openai, fal, gemini Implemented, not battle-tested Selection and a mock CLI are covered by tests/test_providers.py; the README states only codex has run live end-to-end
Transition strips Mechanism tested, not present in the shipped registry objects/lamp/identity.json has "transitions": {} and runs/transition-idle_to_on/ holds only anchor-sheet.png and prompt.txt — the run never completed, though test_states.py exercises the whole path
sockets, lightRig, slots, styleContract.prompt, palette Declared, never populated Written as empty containers in registry.py:100 and 189–194; no other code path assigns to them
styles/, prompts/, reference/ directories In DESIGN.md, absent from the tree Identity files point at "style": "styles/clay-building", a path that does not exist in the repository; prompts live in farmer/prompts.py
Environment plane cutting Deliberately below SAM quality refine_silhouette() is kmeans plus an edge-blocked flood carve in pure numpy, and falls back to the rectangle when the object survives at under 25% of the bbox
Read this as

A single-author v0.1.0 with an unusually disciplined core and a thin outer shell. The pixel operations are the mature part — they have tests, guard clauses, and named failure modes. The registry schema is ahead of the code that fills it. Nothing here has a user base to speak of; the honest claim is this works on the author's assets, and the repository shows the artifacts to back that up.