Set up ComfyUI

Generate an ordered frame set

Use frames when one asset is a short sequence of still renders controlled by one changing workflow input. This is intended for pose images, expression labels, or another explicit per-frame control. It is not video diffusion.

{
  "styles": {
    "character-motion": {
      "generator": "frames",
      "outDir": "assets/generated/motion",
      "seed": 1200,
      "quality": {
        "outDir": "assets/final/motion",
        "palette": ["#161321", "#49374f", "#c16c5b", "#f3d6b3"],
        "fps": 12
      },
      "providerOptions": {
        "comfyui": {
          "workflowFile": "workflows/pose-api.json",
          "outputNodeId": "9",
          "numImages": 1,
          "frames": { "vary": "pose", "seedStep": 0 },
          "bindings": {
            "prompt": { "nodeId": "6", "input": "text" },
            "width": { "nodeId": "5", "input": "width" },
            "height": { "nodeId": "5", "input": "height" },
            "seed": { "nodeId": "3", "input": "seed" },
            "pose": { "nodeId": "19", "input": "image" }
          }
        }
      }
    }
  },
  "assets": {
    "hero-idle": {
      "prompt": "the same hero breathing in place",
      "width": 832,
      "height": 1216,
      "providerInputs": {
        "pose": ["poses/idle-00.png", "poses/idle-01.png", "poses/idle-02.png"]
      }
    }
  }
}

frames.vary names one custom binding. Its asset value must be an ordered array of 2–64 matching scalars or manifest-relative PNG/JPEG paths. Every other binding remains fixed. seedStep defaults to zero; a nonzero value uses style.seed + frameIndex × seedStep and therefore requires a seed binding. The frame count comes from the array, so it cannot disagree with a separate count field.

PixelKiln submits one still workflow per value but tracks the result as one asset. After ComfyUI accepts a prompt, PixelKiln saves its id before submitting the next frame. An interrupted retry continues at the first missing frame. A changed spec starts a fresh set. Incomplete sets cannot be polled into review, and one failed render rejects the complete set while retaining every submitted prompt id for diagnosis. The review page shows an animated loop and ordered strip; accepting any frame accepts the whole set. Fetch writes hero-idle-frame-00.png, hero-idle-frame-01.png, and so on. pack turns those role-stable files into a sprite sheet whose atlas lists the set with its fps; pack --format aseprite makes it a frameTag with per-frame durations, and pack --format godot a looping SpriteFrames animation an AnimatedSprite2D plays by the asset's id.

With a quality profile, refine applies one palette to every frame and writes one approval record for the set. It rejects the entire set when source size, native dimensions, detected pixel step, or detected grid phase differs. The pinned Pixel Art Fixer reports phase in source-pixel coordinates; PixelKiln compares it modulo the shared step. A passing grid check still cannot judge character identity, motion arcs, foot sliding, or loop timing. Review the animation at 1× before recording approval.