A ComfyUI custom-node pack for building large, direction-aware character
animation sets from a single manifest. It is a dependency-aware FFLF
(first-frame / last-frame) resolver: it drives character → pose → animation
selection from one animations.json, gates what you can generate on what is
already rendered, and feeds your sampler the right positive/negative prompts and
start/end anchor images.
It does not sample or generate. You build the FLUX (pose/frame edits) and WAN (animation) graph; this pack resolves prompts, reference images, dependency gating, and completion metadata, and writes the results back to disk so the next node in the chain unlocks.
Everything is one dependency graph of two rendered node kinds, rooted at the base pose:
graph TD
ref["character reference<br/>(node input, persisted as _reference.png)"] --> base
manikin["manikin[dir]<br/>(bundled, per direction)"] --> base["base pose<br/>(root, per direction)"]
base --> stance["fighting_stance pose"]
stance --> idle["fighting_stance_idle<br/>(WAN loop)"]
base -. start_from .-> entry["fighting_stance_entry"]
idle -. end_at .-> entry
idle -. start_from + end_at .-> punch["punch / kick / …"]
- Base pose — the tree root. Each of its 8 directions is a FLUX.2 multi-
reference edit of the character reference image (a Character Creator input,
persisted as
_reference.pngso it can be reloaded) paired with the bundled manikin for that direction (which supplies the camera angle / body orientation). Renders to_base/{dir}.png+ a sidecar. A character's prompt layer lives incharacter.json(no provenance — base sidecars root staleness). - Pose — a per-direction still produced by a FLUX.2 edit of a source image
(another pose). A pose with no
fromis a root pose (base). Renders to_{pose}/{dir}.png+ a sidecar. - Animation — a Wan 2.2 i2v clip. Renders to
{anim}/{dir}/frame_*.png+meta.json.
Every animation needs a start image (the I2V initial latent): its explicit
start_from, otherwise the manifest's defaults.start_from. end_at is
optional — when present, the clip is FFLF. The cross-wiring is:
start_fromconsumes the dependency's last frame.end_atconsumes the dependency's first frame.- A single-image dep (a pose) resolves the same image for either slot.
Looping is a consequence of FFLF, not a flag. There is no loop field. A
clip loops when its start and end anchors resolve to the same image (e.g.
start_from and end_at both pointing at one pose) — it begins and ends on the
same frame. The Animation Writer detects that and drops the duplicated
final frame so the clip plays seamlessly on repeat.
Each render's final positive and negative are merged from layers, general → specific:
globals[kind] → entity
The character prompt layer (character.json) and the per-direction layer are
not cascade layers — they surface only via the opt-in template variables,
resolved by field context (positive vs negative):
| Variable | Expands to |
|---|---|
{character_prompt} |
the character's positive_prompt / negative_prompt |
{view_phrase} |
the manifest-level view_phrases[direction] camera phrase (positive only) |
{direction_prompt} |
the entity's own per-direction positive_prompt / negative_prompt |
{direction_name} |
the bare direction name (e.g. EAST) |
{view_phrase} is the key to the 8-direction workflow: per-direction camera
language lives once in the manifest's view_phrases map, so every pose and
animation can opt into all 8 directions with empty per-direction layers and still
get correct, affirmative view language — including the turnaround failure-mode
mitigations ("only one eye visible" on profiles, "no face visible" on back
views). The manifest stays character-agnostic: identity arrives only via
{character_prompt}.
Positives are joined as prose; negatives are merged as a deduped, comma-separated
term list. The merged prompt is hashed into the sidecar/meta.json as
prompt_hash.
FLUX.2 Klein has no negative-prompt path, so the seed manifest's poses carry no negative layer — pose failure modes are mitigated affirmatively in the positive (via
view_phrases). The negative pipeline is for the Wan animation path (effective at CFG > 1). Seedocs/prompting-guide.md.
Staleness is transitive on the prompt hash. A complete node is stale if its
own merged-prompt hash drifted from what was rendered, or any ancestor is
stale. Editing the character prompt layer or any cascade layer ripples
downstream. A stale node stays selectable — it just shows amber so you know to
re-render.
Clone (or symlink) this repo into your ComfyUI custom_nodes/ directory:
cd ComfyUI/custom_nodes
git clone https://github.com/andyhite/comfyui-andypack.gitRestart ComfyUI. The nodes appear under the andypack category, and the web
extensions (web/anim_coord.js — dynamic combos; web/anim_coord_panel.js — the
sidebar manager) load automatically.
Runtime deps are the ones ComfyUI already provides (torch, numpy, Pillow,
aiohttp). No extra install step is required for normal use.
| What | Location |
|---|---|
| Manifests | ComfyUI/user/default/andypack/animations/*.json |
| Character output | ComfyUI/output/characters/<character>/ |
A character is any directory under the characters root containing a
character.json, a _reference.png, a pose dir, or an animation dir. The
Character Creator always persists the reference art, so a character can be
reloaded (via the Character Loader) and its base pose re-generated later.
output/characters/cortex/
character.json character prompt layer { positive_prompt?, negative_prompt? } (no provenance)
_reference.png persisted reference art (read back by the Character Loader)
_base/EAST.png _base/EAST.json base pose frame + sidecar (the tree root)
fighting_stance_idle/EAST/
frame_00000.png … frame_000NN.png
meta.json written LAST (atomic) = completion sentinel
There is no .complete file. The sidecar / meta.json is written last via
temp-file + atomic rename; its presence is the completion signal. A directory
with no parseable meta/sidecar reads as incomplete.
All nodes live under the andypack category, grouped into Manifest /
Character / Reference / Pose / Animation / Sprite / Export /
Diagnostics / Loop. Custom passthrough types: ANIM_MANIFEST (the loaded,
validated manifest), ANIM_POSE (a pose job bundle from a selector to its
writer) and ANIM_ANIMATION (an animation job bundle). The *Unpack nodes
fan a bundle out into individual typed outputs.
| Node | Role |
|---|---|
| Manifest Loader | Load + validate animations.json (ref typing, cycle detection, 4n+1 length + view_phrases lint). Cached by file mtime. |
Character is pure CRUD: the Creator only ever writes, the Loader only ever reads. Base-pose emission (pairing the reference image with the bundled manikin for a multi-reference FLUX.2 edit) lives on the Pose Selector now — see andypack/Pose below.
| Node | Role |
|---|---|
| Character Creator | Write-only: persist a character's character.json prompt layer and its reference art (_reference.png). Passes through (CHARACTER, REFERENCE) so a create graph can chain straight into the Pose Selector — no manifest/direction inputs, no pose output. |
| Character Loader | Read-only merged reader: emit (REFERENCE, POSITIVE, NEGATIVE) from a character's persisted identity. A missing reference is not an error — it returns the empty-image sentinel, so this also works in the bootstrap stage (prompts authored, reference art not yet generated). |
The manikin/pose-reference authoring nodes — build a named pose-reference
turnaround (pose_references/<name>/) independent of any character or manifest.
| Node | Role |
|---|---|
| Manikin Loader | Load a bundled per-direction manikin as an IMAGE (+ its direction name) — the pose/camera source for authoring custom pose references (drive an OpenPose/ControlNet-capable graph per direction). |
| Reference Bundle | Assemble an ANIM_POSE from loose images (source + pose_reference + positive) for the manikin-turnaround authoring flow — feeds Pose Conditioning without a character/manifest. |
| Reference Selector | Emit the next un-authored direction of a named turnaround (pose_references/<name>/) with the front posed-manikin as source + the bundled manikin as guide. Drive it in a Sweep Loop to author all 8 directions. |
| Reference Writer | Save an IMAGE as pose_references/<name>/<DIRECTION>.png and return REMAINING (the sweep-loop continue signal). |
| Node | Role |
|---|---|
| Pose Selector | mode=sweep emits the next actionable (ready/stale) pose in dependency order — drive it inside a Sweep Loop to batch-generate every pose in one Queue press; raises when none remain. include_base=on also emits root (base) poses paired with their manikin — this is now where the base pose is emitted from, so one graph (→ Pose Conditioning) drives the whole turnaround, base + derived. mode=target force-regenerates one named pose@direction as a spot-fix, ignoring the completeness gate. Takes an optional flow (SWEEP_FLOW) input to sit inside a Sweep Loop, and an optional reference (IMAGE) that overrides the persisted reference art for a root pose — wire the Character Creator's REFERENCE output straight in for a one-press create graph. |
| Pose Unpack | Fan an ANIM_POSE out into SOURCE_IMAGE, POSE_REFERENCE, POSITIVE_PROMPT, NEGATIVE_PROMPT, OUTPUT_DIR, HAS_POSE_REFERENCE (and forward the bundle). |
| Pose Conditioning | One-node FLUX.2 pose-edit conditioning: text-encode + source (and pose-reference-when-present) reference latents + zeroed negative + empty latent → (positive, negative, latent). |
| Pose Writer | Write {dir}.png then the {dir}.json sidecar last (atomic). Returns (OUTPUT_DIR, REMAINING) — REMAINING is the live post-write actionable count (sweep mode) or 0 (target mode), feeding a Sweep Loop's continue/stop signal. Optional write_mirrored also writes a horizontally-flipped copy into every mirror_map direction derived from the one just written, each with its own sidecar and a mirrored_from provenance key (sound only for bilaterally symmetric designs — see docs/prompting-guide.md §4). |
| Node | Role |
|---|---|
| Animation Selector | mode=sweep emits the next actionable animation in dependency order — drive it inside a Sweep Loop to batch-generate every clip in one Queue press; raises when none remain. category scopes the sweep to one manifest category. mode=target force-regenerates one named animation@direction as a spot-fix. Emits an ANIM_ANIMATION bundle: START_IMAGE, END_IMAGE, IS_FFLF, merged prompts, plus LENGTH/FPS/WIDTH/HEIGHT/SHIFT that wire straight into WanFirstLastFrameToVideo + ModelSamplingSD3. Takes an optional flow (SWEEP_FLOW) input to sit inside a Sweep Loop. |
| Animation Unpack | Fan an ANIM_ANIMATION out into its typed outputs (start/end image, prompts, is_fflf, length, fps, width, height, shift, output_dir). |
| Animation Conditioning | One-node Wan 2.2 conditioning from an ANIM_ANIMATION bundle: text-encode + core FFLF encode, omitting end_image entirely for non-FFLF clips (the sentinel never reaches the sampler). Wire SHIFT into ModelSamplingSD3 as before. |
| Animation Writer | Write frame_{:05d}.png, trim the duplicate closing frame of a seamless loop, then write meta.json last (atomic). Records the sampler seed. Returns (OUTPUT_DIR, REMAINING) — same sweep-loop continue/stop signal as Pose Writer. Optional write_mirrored (same semantics as Pose Writer's) and loop_color_match (ramps a per-channel color match toward the first frame across a derived loop clip, mitigating the low-noise expert's color/contrast drift on the start_image == end_image seam). |
| Animation Loader | Load a rendered clip back as an IMAGE batch (+ fps) to reprocess (re-matte, re-pack, re-export) without re-sampling. |
| Node | Category | Role |
|---|---|---|
| Sprite Trim | Sprite | Trims transparent padding from each frame and records the pivot offset so all frames in a sheet stay consistently anchored. |
| Palette Lock | Sprite | Force frames onto one shared limited palette (optionally locked to a palette_image) for pixel-art consistency across directions/animations. |
| Frame Retime | Sprite | Resample a clip to a target fps (resample / trim / pad-hold) before packing/export. |
| Sprite Packer | Sprite | Packs a batch of frames into a single spritesheet image. |
| Sheet Builder | Sprite | Full-clip Stage-3 packer: rows = directions × cols = frames + a per-direction tagged atlas. Optional trim union-trims transparent padding across all directions before packing. entity_type: still packs a pose entity's per-state stills as single-frame tagged rows (state-sheets), threading any nine_slice into the atlas. |
| Atlas Writer | Export | Serializes frame coordinates, pivots, and per-frame duration to the engine format of your choice (incl. godot_stylebox for a 9-slice StyleBoxTexture). |
| Sprite Export | Export | GIF/APNG/WebP preview/export of a completed clip; preserves alpha (a 4-channel input, or a connected MASK, produces a transparent export instead of flattening to RGB). |
| Sheet Export | Export | Stage-3 batch export: one sheet + atlas per animation with ≥1 rendered direction, in one queue press. Skipped animations are listed in the report. |
| Layer Export | Export | Export a parallax background as an engine-ready layered scene: each rendered layer still is copied and a Godot ParallaxBackground .tscn wires them with per-layer motion_scale from the manifest scroll. Unrendered layers are reported. |
| Node | Role |
|---|---|
| Coverage Report | A status table over every (entity, direction) for a character: generated / ready / stale / blocked, plus a JSON blob. |
| Turnaround Sheet | Composites all rendered directions for a pose side-by-side as a contact sheet — catch drift between directions before packing. |
| Node | Role |
|---|---|
| Sweep Loop Open | Marks the start of a one-press sweep loop; emits a SWEEP_FLOW token wired into the sweep body's selector (flow) and into Sweep Loop Close (flow). |
| Sweep Loop Close | Closes the loop: while the writer's REMAINING (wired to remaining) is > 0, clones and re-expands the Open→Close body so the engine runs another iteration; terminates cleanly at remaining <= 0. |
Most of this is also driveable from the Andypack sidebar panel (see below).
- Manifest Loader →
MANIFEST. - Character Creator (persist
character.json+ reference art) — wire itsREFERENCEoutput straight into the Pose Selector's optionalreferenceinput to skip the disk round-trip in a one-press create graph. - Pose Selector (
include_base=on,mode=targetonbase) → Pose Unpack → FLUX.2 multi-reference edit (SOURCE_IMAGEfirst,POSE_REFERENCE/manikin second) → Pose Conditioning → sampler → Pose Writer. This is also how every other pose is generated — the base directions are no longer a special Character-Creator-driven step. - Pose Selector (
mode=target, one pose/direction) → Pose Unpack → FLUX.2 edit → Pose Conditioning → sampler → Pose Writer. Walk poses in dependency order (poses that build onbase). - Animation Selector (
mode=target, one animation/direction) → Animation Unpack → WanFirstLastFrameToVideo (START_IMAGE→start_image,END_IMAGE→end_image) → KSampler → VAE Decode → Animation Writer.
See Graph wiring for the exact FLUX.2 and Wan node connections.
Batch generation (one-press sweep loop). To work through a whole character
without hand-picking every (entity, direction) or re-queueing per cell, set the
Pose Selector / Animation Selector's mode to sweep and wrap
the body (selector → … → writer) in Sweep Loop Open / Sweep Loop Close:
wire Sweep Loop Open.flow into the selector's optional flow input AND into
Sweep Loop Close.flow, and the writer's REMAINING output into
Sweep Loop Close.remaining. Each pick still picks the next actionable job in
dependency order, but now a single Queue press re-runs the body until nothing
remains, instead of requiring a manual re-queue per cell (see
examples/workflows/1b_turnaround_batch.json / 2_animate_fflf.json). Persist
the character's reference art with the Character Creator first, then sweep the
8 base directions via the Pose Selector (include_base=on) same as every
other pose.
The web extension repopulates the combos with live status glyphs after each writer run, so newly-unlocked nodes appear without a manual refresh:
✅ generated · 🔵 ready · 🟠 stale · 🔴 blocked
The manifest is character-agnostic and identity-free — per-character prompt
text lives only in each character's character.json. See
examples/animations.json for a full, working
manifest (generated by scripts/build_seed_manifest.py,
which is the easiest way to author a large set), and the resolver code
(andypack/manifest.py, andypack/resolve.py) for the authoritative schema.
Top-level keys: version, directions (canonical 8-way ordering),
mirror_map, view_phrases (per-direction camera language, injected via
{view_phrase}), defaults (fps / length / width / height / shift /
start_from), globals (animation / pose cascade layers), poses, and
animations. The base pose has no from (it is the tree root) and lists all 8
directions. Every anchor pose and animation lists all 8 directions too (usually
with empty per-direction layers, leaning on view_phrases + the entity prompt).
A character can extend the manifest with its own poses / animations by adding
them to its character.json; the merged manifest is re-validated, so a bad ref or
a cycle is rejected rather than resolved silently.
By default every entity is a character: identity comes from a
character.json + _reference.png, stored under output/characters/<char>/,
across the manifest's 8 directions. A manifest can declare other kinds for
non-character assets — VFX, pickups, auras — via a top-level kinds map:
"kinds": {
"flipbook": { "identity": "none", "root": "flipbooks", "directions": ["flat"] }
}
A kind declares its identity (character = reads character.json +
reference art; style = reads a style.json prompt layer + optional
_reference.png, injected via {style_layer}; none = no identity layer, no
reference), a storage root (a subdir under the ComfyUI output dir), and its
canonical direction-set (a 1-entry list like ["flat"] is directionless).
An entity opts in with a "kind" field; entities with no kind stay
characters, so existing manifests are unchanged.
The bundled examples/animations.json ships a worked flipbook example: a
seed root still (the i2v first frame) and a pulse clip whose start_from
and end_at both anchor to seed, so the resolver derives a seamless loop
(there is no manifest loop flag). Select it with the Animation Selector's
kind input set to flipbook; pack it with Sheet Export's kind set the same.
Three generic capabilities cover zone/environment and UI-export lanes. All are
authored as data — no game term touches the pack. See the seed's backdrop,
parallax_bg, and meter_bar poses.
- Style layer (
identity: "style"). A kind whose instance is a place (a zone / level, e.g.environments/office/) carrying astyle.jsonprompt layer + optional_reference.png. Every entity of that kind injects the instance's prompt via the{style_layer}template var (a value-level synonym of{character_prompt}, resolved positive/negative by field), so one edit to the zone style re-stales every backdrop in it. This is "palette per zone" made enforceable. - Parallax backgrounds. A pose whose direction-set is the layer-set
(
back/mid/front), each layer carrying numericscroll(and optionalscale) metadata. Render the layers like any pose, then run Layer Export (format: godot_parallax) to emit a Godot 4ParallaxBackground.tscnwiring each layer'smotion_scalefromscroll. Layers with nothing rendered are reported, never silently dropped. - UI state-sheets & 9-slice. A
ui-kind pose whose direction-set is the element's states (off/partial/full), optionally with anine_sliceobject (left/right/top/bottommargins). Pack it with Sheet Builder inentity_type: stillmode — one single-frame tagged row per state — then Atlas Writer / Sheet Export withformat: godot_styleboxemits aStyleBoxTexture.trescarrying the margins (thenineSliceblock is also added to the JSON atlas formats when present).
The 8 bundled pose references in andypack/assets/manikins/<DIR>.png (one per
canonical direction) supply the camera angle / body orientation for the base
pose. The Character Creator pairs the character reference image with the matching
manikin as a second FLUX.2 reference, so all 8 base directions are generated
directly — base does not rely on mirror_map.
A pose can carry a pose-level reference_pose: the name of a turnaround folder
under user/default/andypack/pose_references/<name>/ holding one <DIRECTION>.png
per direction. Each direction's PNG replaces the bundled standing manikin as the
FLUX reference (root pose) or adds a second reference (derived pose). A pose is
blocked at any direction whose turnaround PNG is not yet authored. Build a
turnaround with the Reference Selector + Reference Writer inside a Sweep Loop.
Registered on PromptServer.instance.routes when running inside ComfyUI:
All routes return JSON only and take no client filesystem path — a manifest is addressed by a validated bare basename resolved under the pack's manifests dir, a character by a name snake-cased to one segment under the characters dir. There is nothing to traverse out of.
| Route | Purpose |
|---|---|
GET /anim_coord/ping |
Liveness check the frontend uses before enabling inputs. |
GET /anim_coord/characters |
List character directories (server-resolved). |
GET /anim_coord/manifests |
List available manifest filenames. |
GET /anim_coord/manifest_options?manifest=… |
Pose/animation → directions map (no rendered tree needed). |
GET /anim_coord/options?manifest=…&character=… |
Every (pose|animation, direction) with its status + blocked_by. |
GET /anim_coord/manifest?name=… |
Raw manifest JSON text (for the editor). |
POST /anim_coord/manifest/save {name, content} |
Validate + atomically save a manifest (rejected, not saved, if invalid). |
GET /anim_coord/character?character=… |
A character's character.json prompt layer. |
POST /anim_coord/character/create {character} |
Create a character dir + empty character.json. |
POST /anim_coord/character/save {character, positive_prompt, negative_prompt} |
Write a character's prompt layer (preserving any overlay). |
web/anim_coord_panel.js registers an Andypack sidebar tab
(app.extensionManager.registerSidebarTab) with three sections backed by the
routes above:
- Manifest — load / edit / validate-and-save a manifest JSON.
- Characters — create a character and edit its
character.jsonprompt layer. - Coverage — a live status grid over every
(entity, direction)for a character, auto-refreshing after each graph run.
The pack resolves and writes back; you build the FLUX.2 and Wan sampler graphs. The confirmed node wiring (late 2025 / 2026):
Poses — FLUX.2 Klein multi-reference edit. Per reference image
(SOURCE_IMAGE first, POSE_REFERENCE/manikin second), normalize with
ImageScaleToTotalPixels (~1 MP) then chain a ReferenceLatent per reference
onto the conditioning. Distilled Klein: 4 steps, guidance ~1.0, Euler + Simple
(never Euler Ancestral). FLUX.2 has no negative prompt — leave NEGATIVE_PROMPT
unwired. Pipe the edit → Pose Writer.
Animations — Wan 2.2 14B i2v first-last-frame. The recommended path is
Animation Selector → Animation Conditioning → samplers → Animation
Writer: the conditioning node text-encodes the bundle's merged prompts
and delegates to the core WanFirstLastFrameToVideo, omitting end_image
entirely for a non-FFLF clip so one graph serves both FFLF and plain-i2v
animations without a switch. Manual WanFirstLastFrameToVideo wiring remains
valid too — driven by the standard Wan 2.2 i2v models
(wan2.2_i2v_high_noise_14B + wan2.2_i2v_low_noise_14B, umt5_xxl text
encoder, wan_2.1_vae). Wire START_IMAGE→start_image,
END_IMAGE→end_image (leave end_image unconnected when IS_FFLF is false),
WIDTH/HEIGHT/LENGTH into the node, SHIFT into ModelSamplingSD3. The
generated clip's final frame equals end_image, which is exactly what the FFLF
cross-wiring assumes. Decode → Animation Writer (wire the sampler
seed in for provenance). See docs/prompting-guide.md
for prompt structure, the standard Wan negative block, and sampler settings.
Once you have rendered poses and animation clips, comfyui-andypack provides a second layer of nodes for turning those renders into engine-ready game assets.
Bring your own background-removal step (any BG remover node in your graph). Feed
its output into the writers' optional MASK input (or supply a 4-channel RGBA
image directly). The writers record has_alpha in the sidecar/meta and preserve
the transparency throughout the pack chain. ComfyUI IMAGE tensors stay 3-channel
inside the graph; RGBA materializes only at the disk boundary.
From there, the one-node path for a finished clip:
- Sheet Builder — the Stage-3 packer. Loads every frame of every
rendered direction of an animation and packs a game sheet (rows = directions,
cols = frames) with a per-direction tagged atlas (fps included). Optional
trimunion-trims transparent padding across ALL directions' frames before packing (RGBA renders only), shrinking cells while keeping every direction registered. Feed itsATLAS+SHEETstraight into Atlas Writer (aseprite/godot_spriteframesget one animation per direction). - Sheet Export — the batch form of the above: builds and writes a sheet + atlas for every animation in the character's manifest that has at least one rendered direction, in one queue press, instead of one Sheet Builder run per animation. Animations with nothing rendered are listed in the report, never silently dropped.
Or the manual chain when you want per-frame control:
- Animation Loader — load a rendered clip back as an IMAGE batch (+ fps) to re-process without re-sampling.
- Frame Retime — resample a clip to a target fps (
resample/trim/pad_hold) before packing/export — Wan renders natively at 16 fps, game sprites often want less. - Palette Lock — force every direction/animation onto one shared
limited palette (optionally locked to a
palette_image) for pixel-art consistency; run after background removal, before packing. - Sprite Trim — trims transparent padding from each frame and records the pivot offset so all frames in a sheet stay consistently anchored.
- Sprite Packer — packs a batch of frames into a single spritesheet image.
- Atlas Writer — serializes frame coordinates, pivots, and per-frame duration to the engine format of your choice.
- Sprite Export — GIF/APNG/WebP preview of a completed clip; preserves
alpha (a 4-channel input, or a connected
MASK, produces a transparent export instead of flattening to RGB).
- Turnaround Sheet — composites all rendered directions for a pose side-by-side as a contact sheet. Use it to catch drift between directions before packing.
- Coverage Report — a status table over every (entity, direction): generated / ready / stale / blocked. The at-a-glance "what's left to render."
- Pose Conditioning — collapses the whole FLUX.2 pose-edit conditioning
into one node: text-encodes the pose prompt, attaches the source image as a
reference latent (and the pose reference too when the pose carries one — a
base pose), and outputs
(positive, negative, latent). With the Pose Selector'sinclude_base, a single turnaround graph handles base (2-ref) + derived (1-ref) poses — no separate workflows. - Pose Selector / Animation Selector —
mode+category—mode=sweepemits the next actionable pose/animation in dependency order (drive it inside a Sweep Loop for one-press batch fill);mode=targetforce- regenerates one named pose@direction or animation@direction as a spot-fix.categoryscopes the sweep to one manifest category (e.g.locomotion,combat); leave empty for all.
The node set was culled (2026-06-30) to the pipeline-essential node set for clarity; niche/unused nodes (variant/color batchers, boomerang, tween, extra reports, the one-frame Character Atlas Builder) were removed in favor of the core create → turnaround → animate → sheet path. Manikin control, mirroring, and palette lock were later reinstated as focused nodes (Manikin Loader / Reference Writer, the writers'
write_mirroredflag, Palette Lock) once real authoring workflows needed them. The whole node surface was later renamed to a consistent<Domain> <Role>scheme, with pose-reference authoring split into its ownandypack/Referencecategory and Character reduced to CRUD.
pytest -q # tests
ruff check . # lint
mypy andypack # typesThe resolver core (andypack/resolve.py, andypack/manifest.py,
andypack/io.py, andypack/api.py) is pure stdlib — no ComfyUI or torch
imports — so it is fully unit-testable from the fixtures in tests/. The
torch/PIL bridge is isolated in andypack/images.py, and the ComfyUI node
classes in andypack/nodes.py are thin wrappers over the pure core.
| Module | Responsibility |
|---|---|
andypack/manifest.py |
Load, validate, ref-classify, cycle-detect. |
andypack/resolve.py |
Merge prompts, hash, completeness, anchors, transitive staleness. |
andypack/io.py |
Atomic writes, meta/sidecar builders, path safety. |
andypack/api.py |
JSON payload builders + manifest/character CRUD for the routes. |
andypack/server.py |
aiohttp route registration (read + write). |
andypack/nodes.py |
ComfyUI node classes. |
andypack/images.py |
torch/PIL ↔ ComfyUI IMAGE tensors. |
andypack/manikins.py |
Bundled per-direction manikin pose references. |
web/anim_coord.js |
Frontend: dynamic character-scoped combos + status glyphs. |
web/anim_coord_panel.js |
Frontend: the Andypack sidebar manager panel. |
scripts/build_seed_manifest.py |
Generator for the bundled seed manifest. |
This repo ships a Claude Code plugin, andypack-workflows, that teaches Claude
to author ComfyUI workflows with these nodes — a router skill plus one per
pipeline stage (create, turnaround, animate, export). Install it by adding this
repository as a plugin marketplace in Claude Code; the plugin lives under
plugin/ and versions in lockstep with the node pack. Each stage skill is
anchored to a tested workflow in examples/workflows/, and
tests/test_skill_node_refs.py fails CI if a skill's node references drift from
the actual node surface.