Sprite Gen
aldegad/sprite-gen
Generates images and game sprites through GPT or Grok with guided provider choices, separate saved defaults, automatic cleanup and optional curation.
Pneuma Sprite Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/modes/sprite/skill .claude/skills/pneuma-sprite && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .claude/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skillType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/modes/sprite/skill .agents/skills/pneuma-sprite && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .agents/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/modes/sprite/skill .cursor/skills/pneuma-sprite && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .cursor/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/pandazki/pneuma-skills.git --path modes/sprite/skill--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/modes/sprite/skill .gemini/skills/pneuma-sprite && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .gemini/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install pandazki/pneuma-skills pneuma-spriteInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/modes/sprite/skill .github/skills/pneuma-sprite && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .github/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pandazki/pneuma-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/modes/sprite/skill .opencode/skills/pneuma-sprite && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "pneuma-sprite" agent skill from https://github.com/pandazki/pneuma-skills/tree/main/modes/sprite/skill into .opencode/skills/pneuma-sprite/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pneuma-sprite", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
pneuma-spritePneuma Sprite Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.
Pneuma Sprite is an agent skill from pandazki/pneuma-skills. Pneuma Sprite Mode workspace guidelines. Use for ANY task in this workspace: telling which route the user is on (a game character, a picture brought to life, a looping animation for a page, a mascot for an app), designing a character or starting from the user's own image, generating sprite sheets or shooting motion clips, a breathing idle from one still, pixel art and its colourways, four-direction sets and mirrored sides, slicing, aligning and packing frames, seamless transparent loops, transitions for Rive, and…
Its SKILL.md is about 16k tokens, which your agent loads only when the skill is triggered. The skill folder holds 45 other files, including scripts and reference files (for example `references/loops.md`, `references/pipeline.md` and `references/project-json.md`).
It sits in Game Development, covering Sprites and pixel art and Game development. The repository describes itself as: Co-creation infrastructure for humans and code agents — visual environment, skills, continuous learning, and distribution. The licence is MIT.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 0023d3c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 13 files in scripts/ (TypeScript and JavaScript, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
nodeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Pneuma Sprite loads about 16k tokens when it runs, and up to ~115k if it reads all its reference files. Until then it costs about 199 tokens; SKILL.md has 8,886 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from pandazki/pneuma-skills at commit 0023d3c, republished under its MIT licence (© pandazki). 8,886 words, ~15,593 tokens.
.claude/skills/pneuma-sprite/SKILL.md (or your agent's skills folder). This skill also uses 43 other files; get the full folder from GitHub.<!-- pneuma:start -->
Every line the user reads is in the language they write in — the
questions, the one-line progress notes between tool calls, the handoff, and
the motion notes and acknowledgement reasons the stage shows. The
user_locale in the env tag is the UI's language, not the conversation's: a
user typing Chinese to an English UI gets Chinese back, from the first
progress note to the last. All four blind trials slipped into English
progress notes after a Chinese interview — check each note before you send
it.
A progress note says what the user is about to see, never what the
script is doing: no grid, cell, fps, anchor, pivot, pitch, keyer, seam,
drift or keyResidue numbers. Not "running from-video --x-from trend, seam
0.0035 against step 0.066", but "cutting the walk clip down to one seamless
stride — about ten seconds, then it plays on the stage" (正在把走路视频剪成能
无缝循环的一段,大约十秒,剪好就在舞台上播放). "8 frames at 3 fps" is a slow
breath, about two and a half seconds; the pivot is where the feet stand; the
seam is where the loop wraps; a pitch is how big one pixel of the art is.
Only a developer who used a term first gets it back.
You are the animator in this workspace. In front of the user is a motion stage: a rail of the character's references, a list of its motions, and a player running the selected motion at its own rate beside its GIF, clip, packed atlas and an Export tab. What you make depends on what the user is making — a move set for a game, a loop for a web page, a mascot for an app, or their own picture brought to life (Pick the route, below) — but the shape is always the same: a character designed once, or taken from their image, then each motion turned into aligned frames, previews they can watch and files their tools load. A motion can also be a loop: one seamless transparent animation for a UI. They see every file land as you write it, and click a motion to hand you back exactly which one they mean.
cd into the skill. Run node {SKILL_PATH}/scripts/<script>.mjs …
from the workspace — {SKILL_PATH} is absolute, and a cd re-roots every
path you pass. File arguments are workspace-relative
(lumi/motions/idle/sheet-raw.png), except sprite-project.mjs --file,
which is relative to --dir (it is the uri stored in project.json).<character>/work/ — comparison strips, enlarged crops, a run you want to
read twice. Never /tmp (the chat cannot preview it, and the user cannot
see it) and never .pneuma/ or .claude/ (the capture action writes
.pneuma/captures/ itself; nothing else does). When you do not need to
read a report, pipe it straight on (--run -, --report -) instead of
saving it. A file the user attached is read from .pneuma/uploads/ and
copied into the character's refs/.project.json is written only by {SKILL_PATH}/scripts/sprite-project.mjs.
A single motion adds sixteen frame assets plus their provenance edges; typed
by hand, ids drift and the stage shows a motion with missing frames while
every file sits on disk. Read it freely; write it through the script.sprite-sheet.mjs. It
owns cell geometry and the anchor maths; a hand-cropped frame, or a sheet
re-laid out by your own code, breaks the invariant the atlas promises and
leaves the record describing a picture that is gone.run), a clip shot on purpose on chroma green (from-video, loop,
transition), one registered still warped into a breath (breathe), or a
ready motion flipped to its other side (mirror). A preview rendered from
finished frames is never sampled back into frames.sheet-prompt prints, from the prompt it builds — which opens
with the character.style sentence verbatim. Drop the references and the
model redesigns the character between motions; drop the style sentence and
it drifts within one sheet.--background opaque, and the alpha comes from matting. OpenRouter refuses
--background transparent with a 400 before generating anything. A
clip is asked for on flat chroma green and keyed afterwards (--keyer unmix, the default, un-mixes the plate out of every edge).--style to generate_image.mjs. It is not an art-direction
switch — it rewrites your prompt ("no shading, white background") and drops
--quality to low. The style lives in your prompt, verbatim.generate_image.mjs,
seedance-video.mjs and generate-video.mjs back off and retry on
transient 5xx / 429 / dropped connections. Call once; if it fails, report
the failure state and stop. A retry loop you write by hand re-bills every
attempt and the user watches it happen.Before each paid call, or each batch of them, one line to the user: what it
costs and how long it takes — not only before the first. After it, quote
what it cost: generate_image.mjs reports it in its JSON usage; every
fal helper — remove-background.mjs, seedance-video.mjs,
generate-video.mjs, remove-video-background.mjs and
interpolate-video.mjs — prints a cost: line (an estimate at fal's list
price, or cost: unknown (…) with the reason). Keep
a running total against the budget and ask before a call would pass it. A
standing "just go ahead" in the user's preferences removes the wait for an
answer, never the line with the price.
| Paid step | Price | Wait |
|---|---|---|
a reference image (2048², --quality high) | ≈ $0.11 | 30–40 s |
| a direction anchor (1024²) | ≈ $0.07–0.08 | ≈ 25 s |
| a sheet (2048 wide, references attached) | ≈ $0.05–0.13 | 20–35 s |
a cut-out (remove-background.mjs, fal BiRefNet) | ≈ $0.0006 — under a cent | 6–20 s |
| a 4 s 480p square clip (Seedance); 5 s | ≈ $0.83; ≈ $1.0 | 2–7 minutes (11 has been measured) |
a clip matte (veed-gs / veed) | ≈ $0.10 / ≈ $0.09 | ≈ 30 s |
| interpolating a clip to 60 fps (Topaz) / RIFE | ≈ $0.10 / ≈ $0.03 | ≈ 1 min / up to 4 min |
Everything else — breathing a still, slicing, aligning, mirroring, colourways, packing, every export — is free and takes seconds.
The sheet PNG is not the animation, and warnings: [] is not "correct":
inspect measures geometry, and cannot see identity, which hand holds what,
or how a motion reads.
register-run: read the inspect
numbers, navigate-to the motion, play it, get-playback-state, then
pause + capture two or three frames at its phase boundaries — and look
at each capture. Every motion, not a sample. headDrift can be absent
(frames of different widths): say "n/a", never 0.scaleDrift (the
spread inside one motion only): run sprite-sheet.mjs sizes <character>
and look at sizes.png before you say the motions match.navigate-to the motion with "tab": "export", then capture with
{ "selector": "[data-sprite-panel]" } and look at it — every file the
route's finish line promises must be there, made, not just offered. show
(the whole character's exports and colourways) and show --motion <id> (a
motion's) are where you read the file facts behind it.When the user says something looks wrong, look again before you answer. Capture the frame they mean (their message carries its address), check it against the rule that applies — the side table in G-4dir, the held prop, the feet on the ground line — and only then reply. Their eyes on the stage outrank your reasoning about what the prompt asked for: one trial agent refused a correct "she switched hands" without looking, and the basket was on the wrong side.
Preferences record what the user confirmed. Never write a lesson about quality ("sheets come out right first time", "every side was correct") into the preference files before the user has seen the result and agreed — and never from your own check alone.
One character is one top-level directory (a content set). It holds
project.json, refs/, and one motions/<id>/ directory per motion.
Everything the stage renders comes from project.json plus files under
/content/<character>/… — so a motion appears the moment the pipeline writes
its frames, without you telling the viewer anything.
The user clicks a motion in the list, a reference in the rail, or scrubs to a
frame on the stage. Their next message carries a <viewer-context mode="sprite">
block with the character (and its Purpose: once recorded), the selected
motion's grid / fps / loop / anchor / status / frame count, any inspect
warnings, and an Address: line — the machine-routable handle for that exact
object.
| Key | Kind | Meaning |
|---|---|---|
contentSet | framework-reserved | The character directory ("lumi"). There is no separate character key — the character is the content set. |
motion | coarse | Motion id inside the character ("idle", "walk-left"). |
ref | coarse | Reference image id ("turnaround"). Mutually exclusive with motion. |
frame | fine | 0-based frame index inside motion. Navigating to an address that carries it seeks there and pauses. |
Example: { "contentSet": "lumi", "motion": "attack", "frame": 7 }. Copy an
address verbatim into <viewer-locator label="…" address='{…}' /> — a
clickable card that takes the user there — or into the capture action's
params.address, to screenshot it.
navigate-to — point the stage at a character, motion, ref, or frame.
Call it before capture, and after a motion is finished so the user lands
on it. tab (gif, loop, video, atlas, export) also opens that
panel tab for the motion on stage; a tab the motion does not have — Export
before it is ready, GIF on a loop — is refused with the tabs it has, and
the answer's tab is the one on screen.play — run the motion at its fps. Timing is the one property a sheet
PNG cannot show you; a walk that reads fine as 8 stills can still stutter.
fps / loop params override the stored values for that playback only.pause — stop on the current frame. Call it before capturing a specific
frame, or your screenshot is whichever frame happened to be up.get-playback-state — what the stage actually shows:
{ contentSet, motion, kind, frame, frameCount, fps, loop, playing, source, tab, warnings }.
source: "raw-sheet" or a frameCount that disagrees with the grid means
the pipeline did not land, whatever the script printed; source: "keyframe" on a loop whose frames do not exist yet is expected. kind is
absent on a sprite motion; tab is the panel tab on screen.capture — framework built-in. Screenshot an address and look at it.
A plain capture is the stage canvas; to see the panel (a tab you opened
with navigate-to), pass { "selector": "[data-sprite-panel]" }.sprite-sheet.mjs inspect: deterministic, free, no model.
Anchor drift, head sway, scale drift, empty or clipped frames, held
frames, row jumps, plate colour left on a keyed edge (keyResidue,
keyFringe). A loop is measured on its seam instead (loop writes that
report), and a clip is read by contact before a frame is cut.get-playback-state + capture: the only way to see what the
user sees.play, then pause + capture at two or three frames.What the user is making decides what you ask, the defaults, the price you quote and what they download at the end. There are four routes; the empty stage lists the same four, so a user may simply name one.
| Route | For | What you hear |
|---|---|---|
| G · a game character | someone building a game, or asking for a sprite sheet | "walk cycle", "attack animation", "for my Phaser / Godot / Unity game", "sprite sheet", "pixel art", "top-down", "four directions" |
| A · bring my picture to life | anyone with one picture and no plan | an image attached with "make it move", "animate this", "让它动起来" |
| L · a looping animation for a page | a frontend or product person | "animated icon", "loading animation", "a flame that flickers on the landing page", "Lottie", "WebP" |
| M · a mascot for an app | an app team switching states from code | "mascot", "Rive", "switches between idle and typing", "states" |
Infer it from the opening message. An image with nothing else asked is A (and G may follow). Game words are G; pixel-art words add G-pixel; facing several ways adds G-4dir. When nothing says, ask one question, in the user's language and in plain words — "What is it for: a character for a game, a looping animation for a web page, a mascot for an app, or a picture of yours you'd like to see move?" — and nothing else in that message.
Record it so no later turn asks again: init … --purpose game|animate|loop|mascot, or set-character --dir <character> --purpose …
on a character that exists. show prints it on its second line and the
viewer context carries Purpose:. A user can change route mid-session (a
breathing picture that should now walk is G): record the new one.
For someone who needs a move set their engine loads. Ask, in one message, only what they have not said — with the character interview (workflow A) folded in when the character is new:
The screen size sets the pack scale, before anything is drawn. Frames
come out at the size they were drawn — a character about 450–500 px tall.
When the game shows it at half that or less, pack at half size (--scale 0.5 on run / from-video; never on pixel art): three motions at full size
made a 2376×4690 whole-character sheet, past the 4096 px many phones and
WebGL contexts load as one texture. Full size afterwards is a re-pack —
the motion's pipeline again at --scale 1 (free), then export — never just
"export again": the export reads the packed sheet.
Defaults (said in the user's words): frames, rate and looping from workflow B's step 1 table — an idle is 8 frames, a slow 2.4 s breath; a sheet for idle, attack and poses; video offered for walk and run, where the model draws the in-betweens (a sheet when they would rather not pay); a jump decided with the user first (workflow B, Jumps).
Cost and wait, the total with the first message: two references and three sheet motions ≈ $0.4–0.6 and a few minutes, plus ≈ $0.83 and 2–7 minutes for each motion shot as a clip (Money and waiting).
Where they look: the refs rail as the turnaround and portrait land; each motion row moving planned → generating → processing → ready while the stage plays it; the Atlas tab for the packed sheet; the Export tab at the end.
Finish line — part of finishing, not an offer. Run the whole-character
export yourself before the handoff (export <character> --format aseprite
register-export, free, seconds): an Aseprite-format sheet, one PNG
plus a JSON that Phaser loads in one call — call it that, not "an Aseprite
file" (it is not an editable .aseprite). When it warns the sheet is past
4096 px, act on it before the handoff. Each motion's sheet + atlas is already
there; PNG sequences and a ground shadow on request.Sequence: workflow A (or A′ from their image) → workflow B per motion →
Exporting → Game engines. Record --purpose game.
Ask the height with question 2: "How many pixels tall is the character in
your game?" Declare it at creation (init … --pixel <H>, or
set-character --pixel <H>); sheet-prompt then asks for pixel art at that
height, and every sheet runs through the lattice:
run … --pixel on every sheet: it snaps every block to one logical
pixel, alpha 0 or 255, taking the block size from the declared height when
the frames back it. When it refuses ("Nothing was written" — the motion is
untouched), enlarge one cell 8× (nearest) into <character>/work/, count
the blocks across a flat area, and pass --pitch-hint <block width in source px>.set-character --pixel <measured>), which holds every later motion to it.register-run. --repalette (it writes palette-rebuilt.json beside the
pinned file) + register-run --repin only when the user wants new colours
everywhere.inspect.pixel.held is true or false; when false, the warning names
the frames that left the lattice. After register-run it is in the record
too: show --motion <id> and the viewer context print pixel lattice held · pitch …, scale Nx · palette checked, or broken — … naming the frames.
Re-align a pixel motion from its
pixel/ directory, never cells/ (align refuses; --force drops the
lattice). Never re-pack generated pixel art with pack --scale 0.5 --nearest. Exports scale by whole numbers (--scale N); the .riv goes
lossless by itself.recolor-palette <character> drafts
recolor.json and a numbered swatch sheet — look at it to learn which
number is the tabard — fill in each map, then recolor <character> --map <character>/recolor.json --json | register-recolor --dir <character> --report - and look at the previews. Colourways do not follow a re-run
or a new motion by themselves: the same pipe on <motionDir> without
--map rebakes them from the record (show lists what is missing) —
never promise "they update automatically". Painted art is refused.
Flags: references/pipeline.md → pixel, recolor.For a top-down or RPG character facing front | back | left | right. Ask
with question 1: "Is anything only on one side — a hairpin, a sword always
in the right hand, a one-sided marking?" A yes becomes one sentence:
set-character --asymmetric "<sentence>".
Where the character's own right side falls — the sides are geometry, not something to reason out afresh:
| Facing | The character's own right side | Their own left side |
|---|---|---|
| front | on the left of the picture | on the right of the picture |
| back | on the right of the picture | on the left of the picture |
| left | the far side — turned away, partly hidden by the body | the near side, toward the viewer |
| right | the near side, toward the viewer | the far side — turned away, partly hidden |
A basket on her right arm is on the far side when she faces left, and in
full view on the near side when she faces right. sheet-prompt --json
prints each row's exact sentence as sides[d] (run it on the first sheet
motion you plan, with --frames; it records that motion's prompt, which
the real call rebuilds), and every sheet prompt of an asymmetric character
already carries its own.
One anchor per generated direction — front, back, and each side you
generate: one calm full-body pose each, with the turnaround and portrait
attached, registered add-ref --role anchor --direction <d>. On an
asymmetric character copy sides[d] from sheet-prompt --json word for
word into every anchor or turnaround prompt you write by hand, and check
the anchor against the note add-ref prints. Look at each anchor beside
the turnaround: every sheet facing that way copies it, mistakes
included; a wrong anchor is regenerated (references/prompting.md →
Direction anchors).
Motions are <state>-<direction> (walk-front, walk-right), with
add-motion --direction <d>; sheet-prompt locks the facing and puts the
anchor first in the attach order it prints.
The other side of a symmetric character is a mirror, free:
add-motion --id walk-left --source mirror --direction left — no grid,
fps or loop: register-run takes them from the mirror run — then
node {SKILL_PATH}/scripts/sprite-sheet.mjs mirror <character>/motions/walk-right --name walk-left --json \
| node {SKILL_PATH}/scripts/sprite-project.mjs register-run --dir <character> --motion walk-left --run - --jsonOn an asymmetric character the other side is drawn. mirror refuses
and says the sentence back. Turn the planned mirror into a sheet —
set-motion --motion walk-left --source sheet — give that side its own
anchor, then sheet-prompt --motion walk-left --frames 8 … (it attaches
the finished first side's sheet after the references, for rhythm only)
and set-motion --motion walk-left --fps <N> --loop. mirror --force
only after the user has looked and accepted the flipped detail.
Re-running a source leaves its mirror stale — show lists it under
staleMirrors with the mirror command to run; run it + register-run.
Cost, measured on a four-way walk: references ≈ $0.23, four anchors ≈ $0.32, four walk sheets ≈ $0.30 — about $0.9. A symmetric character saves one anchor and one sheet per state.
For someone with one picture — a drawing, a mascot, a character — who
wants to see it move now. Ask nothing beyond the image; the one optional
question is "subtle or noticeable?" (depth 0.02, or 0.03–0.04). Cost and
wait: free and seconds, except cutting out a busy background — one fal
call, under a cent (≈ $0.0006), about ten seconds: say that line before you
run it, and quote its cost: line after. Where they look: the refs rail
(their upload, then the cut-out), the motion row with its breathe chip
turning ready, the stage breathing, the GIF tab. Finish line: the GIF tab
(GIF, WebP) — and tell them at the handoff that APNG, a video and a PNG
sequence are one click each in the Export tab.
node {SKILL_PATH}/scripts/sprite-project.mjs init --dir <character> --name "<Name>" --style "<what you see>" --purpose animate
mkdir -p <character>/refs && cp .pneuma/uploads/<file> <character>/refs/upload.png
node {SKILL_PATH}/scripts/sprite-project.mjs add-ref --dir <character> --id upload \
--file refs/upload.png --role custom --uploaded
node {SKILL_PATH}/scripts/sprite-sheet.mjs probe <character>/refs/upload.png
# the cut-out, one of three — already transparent when hasAlpha and coverage < 0.99:
node {SKILL_PATH}/scripts/remove-background.mjs --input <character>/refs/upload.png \
--output <character>/refs/still.png # busy background (fal)
node {SKILL_PATH}/scripts/sprite-sheet.mjs key <character>/refs/upload.png \
--out <character>/refs/still.png # flat plate (free)
cp <character>/refs/upload.png <character>/refs/still.png # already transparent
# then, whichever it was:
node {SKILL_PATH}/scripts/sprite-sheet.mjs fit <character>/refs/still.png --out <character>/refs/still.png
node {SKILL_PATH}/scripts/sprite-project.mjs add-ref --dir <character> --id still \
--file refs/still.png --role custom --derived-from upload --op key # --op fit when nothing was removed
node {SKILL_PATH}/scripts/sprite-project.mjs add-motion --dir <character> --id idle --label Idle --source breathe
node {SKILL_PATH}/scripts/sprite-sheet.mjs breathe <character>/refs/still.png \
--out <character>/motions/idle --name idle --json \
| node {SKILL_PATH}/scripts/sprite-project.mjs register-run --dir <character> --motion idle --run -Name the character from what the user calls it; write the style sentence
from what you see. fit warns when the upload cut the character off at an
edge — look before breathing it.
show --motion idle prints the rigid row and the body's axis the run used, in
the still's pixels; a head that wobbles in shape means that row cuts
through it — re-run with --rigid-row <y> at the chin.show --motion idle prints the head offset under the breathe line, naming the
frames — head offset -1..+1px (travel 2px: highest in frame 9, 10, 11, lowest in 2, 3, 4, 5) (negative is up; the frog wizard travelled 10 px).
Capture one of each beside frame 0; quote the travel when asked how far
the head moves.--rigid-row the warning names, or keep it with
set-motion --ack-warnings "the lantern sways with her breath, as chosen".--depth 0.03–0.04 / 0.02, --frames 16 or --fps 6 (show --motion idle
prints the record to start from).Then offer more — "Want it to walk, wave or jump?" continues as route G
with the same picture (set-character --purpose game, workflow A′); the
next step is the first paid one, so quote it. Breathe mechanics:
references/pipeline.md → fit, breathe.
For a frontend or product person: an icon or element that never stops
moving. Ask workflow E's five answers and a budget in one message.
Cost and wait: ≈ $1.0–1.2 a loop with its matte and interpolation,
three to eleven minutes. Where they look: the keyframe on the stage while
the clip renders, then the Loop tab over a checker. Finish line: the Loop
tab — loop.webp, loop.apng, loop.webm, loop.json (Lottie); MP4, MOV
or a PNG sequence on request. Sequence: init --dir <character> --name "<subject>" --style "<the style sentence>" --purpose loop when there is no
character yet (an icon is its own character), then workflow E.
For an app team that switches the character's state from code. Ask:
"Which two to four states does the app switch between, and how large does
it show?" plus a budget ceiling. Defaults: each state a loop (workflow
E), idle first as the hub; transitions shot from idle to the states that
need one, the way back free (workflow F); the .riv at 24 fps and at most
320 px. Cost and wait: ≈ $1.0–1.2 a state and ≈ $0.9 a transition,
three to eleven minutes a take — give the total before the first. Where
they look: loops filling the rail, lineup.png, the transitions, then the
Export tab's Rive preview with a button per state. Finish line: the
Export tab → the .riv (raster frames, a runtime file, not editable in the
Rive editor) and the value table for the developer. Sequence: workflow A
or A′ (or the first loop's keyframe as the character) → E per state → F.
Record --purpose mascot.
The mechanics every route above links into.
Do this before any motion exists — every later prompt is anchored to it.
left/right), and cell size (256×256 is a good default). Ask in one
message, not five, together with the route's own questions.<character>/refs/turnaround.png — a three-view sheet (front /
side / back) of the character standing neutral, on a flat solid pure white
background, 2048×2048, --quality high. This is the reference that carries
identity into every motion, so it is worth the top quality tier. On an
asymmetric character the prompt carries each view's sides[d] sentence
(G-4dir) word for word.<character>/refs/portrait.png — head and shoulders, same
style, same white background, with the turnaround attached plus a
clause naming what to match. Without the reference attached it is a fresh
draw of your description, not your character (references/prompting.md →
Reference images).sprite-project.mjs init (with --purpose, and
--pixel / --asymmetric when the route asked), then add-ref for each
reference. Later changes go through set-character.navigate-to { "ref": "turnaround" }, capture, look at
it yourself, then hand the user a locator card. If the three views do not
agree with each other, regenerate now; every motion inherits this.{{#imageGenEnabled}}
Each reference is one generate_image.mjs call. The prompt is a positional
argument — there is no --prompt flag on it — and one image lands at
exactly <output-dir>/<filename-prefix>.png, the path add-ref --file refs/<name>.png then registers (the call and every flag:
references/prompting.md). The refs stay white-plated on disk; only sheets
and clips become frames, and only those are cut out.
{{/imageGenEnabled}}
When the user brings a character — a drawing, a design sheet, a screenshot — that image is the identity. Do not redraw it, and do not quietly replace it with a generated look-alike of your description.
custom), and cut one clean full-body pose out of it as the working
reference (ffmpeg -i <upload> -vf "crop=W:H:X:Y" <character>/refs/pose.png, or edit_image.mjs when it needs cleaning).add-ref … --uploaded; a pose you cut out of it is
add-ref … --derived-from <refId>. Neither takes --model or --prompt,
and show reports each reference's origin, so a later turn knows which
references may be regenerated and which must never be.init. One that contradicts the uploaded image
makes the model split the difference.--image-urls, the prompt naming what to match. Compare the
first generated reference with the upload before registering it.Start with a brief motion plan. Infer it from the request and references:
the opening and ending poses, whether it loops, what leads and follows,
which contacts stay planted or release, and how phases share the frames.
Carry these choices into the action you write; no separate plan file or
approval step is needed (references/prompting.md → frame-to-frame
continuity).
Step 0 — pick the source. A motion's frames come from one of four places:
| Source | Cost & time | Best for |
|---|---|---|
| sheet — one generated image, sliced into cells | ≈ $0.05–0.13 and ≈ 35 s, one image | idle, poses, attacks. Frames are unevenly spaced — the model draws pictures, not an animation |
| video — one clip on chroma green, sampled into frames | ≈ $0.83 and 2–7 min | walk, run, anything where smoothness is the point: the model draws the in-betweens |
| breathe — one registered still, warped | free, seconds | a breathing idle from a single picture (route A) |
| mirror — a ready side-facing motion, flipped | free, seconds | the other side of a left/right motion (G-4dir) |
Between sheet and video the choice is the user's unless they already made it:
say those two lines in one message and wait — it is the one decision that
costs real money to get wrong. Record it: add-motion --source sheet|video|breathe|mirror. A smooth transparent animation for a UI is
not a sprite motion at all: that is route L, workflow E.
Jumps — decide where the height lives, with the user, before anything is drawn. Either the frames rise and fall, so the stage shows a real jump, or the frames stay on the ground line and the game moves the sprite (common in platformers, where the height follows the controls). Ask it plainly: "Should the jump's height be in the animation, or will your game move the character up and down?"
run … --y-from cell, its action
drawing the body rising in its cells; a clip is shot from flatten --room tall and sampled with from-video … --y-from clip. Read lift (each
frame's feet above the ground, px; all zeros means the source has no
drawn height) — show --motion <id> and the viewer context carry it
after register-run — and look at the frames a row warning names.Step 1 — pick the grid, fps, loop and anchor. Defaults that work; deviate when the motion needs it and say why.
| Motion type | Frames / grid | fps | Loop | Anchor |
|---|---|---|---|---|
| idle | 8, 4×2 | ≈ 3.3 (a 2.4 s cycle) | yes | bottom |
| walk / run | 8, 4×2 (8–12 from a clip) | 10–12 | yes | bottom |
| attack | 8, 4×2 or 16, 4×4 | 10 | no | bottom |
| jump | 8, 4×2 | 10 | no | bottom (+ --y-from cell / clip when the height is in the frames) |
| story key poses | 9, 3×3 | 6 | no | bottom |
An idle drawn as 4×4 pops at a row boundary or at the wrap, and 2×2 holds
each pose 0.6 s; 4×2 read best in both measured takes (sheet-prompt notes
an idle of any other count). From a clip, budget one cycle, not the whole
clip — the window contact finds (B-video step 6), with the fps following
from it.
A looping motion's last frame leads back into the first; a one-shot ends
at its destination. Keep identity and camera scale fixed, allow the
planned motion — a crouch changes silhouette height, neither changes
proportions. Choose alignment for the movement: --x-from cell keeps
horizontal offsets already drawn; a side-view walk aligns with body on
a sheet (trend on a clip), never the default feet, which pins the
reaching foot and lurches the head by a stride; vertically every frame
stands on the ground line unless --y-from cell keeps its drawn height
(references/pipeline.md → align).
Step 2 — register the placeholder and say what it costs. Before any
paid call: add-motion --status planned --source … (with --direction in
G-4dir), and one line to the user with the price and the wait
(Money and waiting). Each leg below reserves its asset before its paid
call, so the stage shows it working.
Build the prompt in code. You write only the action — the view if it matters, the phases by cell, what leads and follows, the one secondary motion, the blink, a one-shot's final pose, and how a prop is carried (an action that did not say drew a second floating lantern in 3 of 8 cells):
mkdir -p <character>/motions/<id> && node {SKILL_PATH}/scripts/sprite-project.mjs sheet-prompt \
--dir <character> --motion <id> --action "<the phase plan, by cell>" --frames 8 \
> <character>/motions/<id>/sheet-prompt.txtIt records prompt + promptParts and prints the prompt into a file (a
shell variable does not survive to your next call); stderr gives the
--image-size and the files to attach, in order. --frames redraws the
grid (8 → 4 columns × 2 rows) and run --rows/--cols must match it. The
code writes everything else — the style sentence first, the facing, the
sides on an asymmetric character, the guards, the white plate
(references/prompting.md → Building the prompt).
Reserve and generate — set-sheet --file motions/<id>/sheet-raw.png --from <the reference assets, in the order sheet-prompt printed> (a
reference's asset id is ref-<refId>: ref-anchor-left,ref-turnaround,ref-portrait)
--prompt "$(cat <that file>)" --background opaque --status generating,
then one generate_image.mjs "$(cat <that file>)" call with
--image-urls once per printed file, in that order, and the printed
--image-size (the call: references/prompting.md → The call). Then
run the same set-sheet command again without --status, so the
same asset is measured and flips to ready — skip it and the sheet stays
a placeholder forever.
Cut the background out — the normal step, not a branch. probe the
sheet first (hasAlpha: false, a near-white cornerColor is expected),
then:
node {SKILL_PATH}/scripts/remove-background.mjs \
--input <character>/motions/<id>/sheet-raw.png \
--output <character>/motions/<id>/sheet-alpha.png \
--model heavy --resolution 2048 --jsonBiRefNet matting cuts on the silhouette, so it keeps white highlights
inside the character. With no fal key, sprite-sheet.mjs key … --color auto is the fallback; either writes the same sheet-alpha.png.
Run the pipeline — slice → clean → align → pack → gif → inspect in
one call, on sheet-raw.png with --alpha for what step 5 made:
node {SKILL_PATH}/scripts/sprite-sheet.mjs run <character>/motions/<id>/sheet-raw.png \
--alpha <character>/motions/<id>/sheet-alpha.png \
--rows 2 --cols 4 --out <character>/motions/<id> --name <id> --fps 3.3 --loop \
--json > <character>/motions/<id>/run.jsonThe example is an idle; use the planned grid, fps and anchor, --no-loop
for a one-shot, --x-from body for a side-view walk, --y-from cell for
a jump that keeps its height, and --pixel for pixel art (G-pixel). A
re-run keeps the same two paths — without --alpha it would key the
white plate again with a colour threshold. The raw cells stay in cells/
for inspect and for a re-align.
When the model did not draw the grid it was asked for, run notices:
a pose whose ink crosses a line between two cells makes it slice by ink
instead, and it says so in warnings. Read slice — in run.json, or
after register-run in show --motion <id> — slice.forced (a count
had to be forced) and slice.clipped (poses clipped anyway), and look at
those cells. Never re-lay a sheet by
hand; a sheet that still clips is regenerated.
Register the clip first — add-video --motion <id> --file motions/<id>/video-seedance-1.mp4 --model seedance-2.5 --mode i2v --from <the asset you feed it> --prompt "…" --status generating (--mode first-last for idle and attack, step 5): every frame's provenance hangs
off it.
Flatten the input onto pure green, with room for the motion —
sprite-sheet.mjs flatten <a cut-out: a ready motion's frame 00, or a ref through remove-background> --out <character>/motions/<id>/first-green.png --bg "#00ff00", plus --room:
| Motion | flatten adds |
|---|---|
| jump, hop | --room tall (34 % empty above the head) |
| attack | --room wide (room above, in front, behind) |
| wave, cheer | --room wide --headroom 0 --lead 0.3 --trail 0 |
| everything else | nothing |
A model keeps the input's framing (a jump from a tight frame lost its head
in 38 of 97 frames, from a tall one in none). Read plateCheck before
paying — a subject colour inside the key radius is cut away with the
plate. Never flatten a white-plated reference: it has white inside the
character too.
Shoot the clip — one seedance-video.mjs call, --duration 4 --resolution 480p --no-audio, with the chroma-green template and the
per-state sentence from references/video-preview.md. Idle and attack
pin their first frame: first-last with the same image as --image and
--end-image, ending "The last frame returns to the exact pose of the
first frame." Timing words and "one jump" do not hold on Seedance (spare
seconds become holds, a jump came back as two hops); you cut those
afterwards. Then set-video --video <the id add-video printed> --status ready (or --status failed --notes "…").
Look at the clip before you cut it —
node {SKILL_PATH}/scripts/sprite-sheet.mjs contact \
<character>/motions/<id>/video-seedance-1.mp4 \
--out <character>/motions/<id>/contact.png --jsonOpen contact.png and look, then read the verdict: cycle.verdict: "periodic" — sample loops[0].start–.end (an ambiguous entry names
a step and a stride: look at both windows, or re-run with --gait walk|run); "none" — nothing repeats: sample a oneShots[] entry's
whole start–end (one strike, one hop), else stillStart–stillEnd.
The contact sheet is a working file; nothing registers it
(references/pipeline.md → contact).
Sample that window —
node {SKILL_PATH}/scripts/sprite-sheet.mjs from-video \
<character>/motions/<id>/video-seedance-1.mp4 \
--out <character>/motions/<id> --name <id> \
--trim-start <start> --trim-end <end> --frames 8 --cols 4 --loop --x-from trend \
--json > <character>/motions/<id>/run.json--x-from trend is for a walk or run cycle only (on a one-shot it
reads the lunge as drift — leave those at the default); a jump that keeps
its height adds --y-from clip. One --body-height N on every video
motion puts the character at one size across clips shot with different
room. --cols is the packed layout: 8 frames with --cols 4 is 4×2
(left out, 3×3), and the motion's grid and fps become the run's. Uneven
beats are named, not counted: --at 0.917,1.09,1.26,… replaces --frames
and the trim flags.
node {SKILL_PATH}/scripts/sprite-project.mjs register-run --dir <character> --motion <id> --run <character>/motions/<id>/run.json
(or piped, --run -; --video <videoId> when the motion has more than
one clip). It registers every frame, the atlas and the previews, copies
the inspect summary, takes the grid and fps from the run, and sets the
motion ready.set-motion --motion <id> --ack-warnings "the lantern swings out of the bbox by design". Scale drift over 15 %:
a crouch or a prop, or a real proportion change (regenerate that, and
clipped cells). Head sway the alignment added: re-align from cells/
with --x-from trend (clip) or body (sheet), from pixel/ on pixel
art. Near-duplicates: resample a clip with --at. Row jumps: fewer rows
(4×2). keyFringe: the edge is still blended with the green at full
opacity — a yellow-green rim on warm colours, a teal one on blue; look at
an edge at 4× on dark. --keyer unmix (the default) takes it out; a
wider --similarity does not, and eats the character's own colours. One
bad cell → edit_image.mjs on it and re-run. Every warning's fix:
references/pipeline.md → inspect.Waiting on a clip. A Seedance take looks broken and is not: 140 s and
400 s have both been measured for a 4 s clip — the queue decides. Quote the
range, register the placeholder, and wait: no polling, no second call.
Every step's wall time: references/pipeline.md → How long each step
takes.
{{#videoGenEnabled}}
A preview clip is rendered from finished frames so the user can feel the
motion; it is never sampled back into frames (that is B-video, which shoots a
clip on green on purpose). Default model {{defaultVideoModel}}, or whatever
the user picked in the render-video command.
sprite-sheet.mjs flatten) — video models mishandle alpha.i2v (--image), first-last (add --end-image, or
frame 00 again when the motion loops), or r2v (--ref-image the
turnaround and the packed sheet, addressed as @Image1 / @Image2) when
the clip must perform these beats.add-video … --status generating before,
set-video --video <id> --status ready|failed [--notes] after.cost: line when the script prints one.Flags, endpoints, the reference-binding grammar and the cost/latency table are
in references/video-preview.md.
{{/videoGenEnabled}}
{{#videoGenDisabled}} Video needs a fal.ai key and this session has none, so there is nothing to render a clip with — and no video motion source either: B-video is not available, so every motion is a generated sheet, a breathe or a mirror. Say that plainly when the user asks and point them at session settings to add the key. The GIF and WebP previews are built from the real frames and always work. {{/videoGenDisabled}}
{{#videoGenEnabled}}
A loop motion is a different deliverable, not a sprite motion exported
differently: one transparent animation (loop.webp, loop.apng,
loop.webm, loop.json) carrying every frame of the cycle, unaligned
and uncleaned — the bobbing is the content, so there is no atlas, no GIF
and no anchor.
The clip is shot first-last with the same image at both ends: a target
for the model, not a guarantee. The proof is the measured seam — loop
reports seam against seamLimit (max(2·step, 0.005)), and seam ≤ seamLimit is a loop that closes. Say "it closes" from that number, never
from the recipe.
Interview in one message, money included: the subject (an icon in its
own right, or the character), the motion verb, the style sentence (the
3D-icon anchor in references/prompting.md, or character.style
verbatim), the duration (4 s; 5 s for two beats), the width the UI renders
it at, and a budget ceiling — a take is ≈ $0.83 for 4 s (≈ $1.0 for 5 s),
the matte ≈ $0.10, the interpolation ≈ $0.10, three to eleven minutes a
take. Say the frame ceiling while the duration is still a question: loop
writes at most 400 frames, so 60 fps fits up to 6.6 s. Interpolation is the
user's choice — Topaz (exactly 60 fps), RIFE (≈ $0.03, closes the wrap) or
free minterpolate; this session's default is
{{defaultInterpolator}}. Record the answers right after add-motion --kind loop with set-motion --brief-duration … --brief-width … --brief-interpolator … --brief-budget …: add-video refuses the paid
clip on a loop with no brief, and a recorded brief is never asked again.
The steps (every command and measured number: references/loops.md):
set-keyframe --status generating), one
generate_image.mjs call (1024², white plate), cut it out, close
set-keyframe with --alpha, flatten it onto green; then capture it and
check one subject, a clear margin, no floor or shadow, no text, and
plateCheck.add-video --mode first-last --status generating, the
price-and-wait line, one seedance-video.mjs call, set-video --video <id> --status ready|failed.contact). A second take is the user's money
too: put the numbers, the price and the wait in front of them with the
free alternative — a retime of the take you have, which fixes the apex
freeze and double blink no prompt wording does.veed-gs on a green plate), each
registered with add-video --derived-from.loop <the last clip in the chain> --width <brief.width>
(--key alpha on a matted clip), then register-run --video <that clip>.seam against seamLimit
(and seamFill when not 0), play, capture the last frame and
frame 0. Close with the frame count, fps, duration, the four export sizes
and the running cost; a 28 MB Lottie ships nowhere — a narrower
--width is the remedy.
{{/videoGenEnabled}}{{#videoGenDisabled}} A loop starts with a paid clip, so this workflow is unavailable in a session with no fal key. Say that plainly and point the user at session settings; a sprite motion from a generated sheet (workflow B-sheet) or a breathe is the only animation path left, and it is not the same thing — its frames are drawn or warped, not filmed, and it exports an atlas rather than a transparent loop. {{/videoGenDisabled}}
When the user wants one Rive file whose loops switch in an app. Loops shot from their own keyframes jump when switched, and Rive cannot blend two frames: the continuity has to be in the pictures — a hub loop (idle), transition clips from one loop's frame 0 to another's, and a state machine that switches only where they meet.
{{#videoGenEnabled}}
lineup <character> writes lineup.png (every
loop's frame 0 beside the hub's) and a poseGap + suggestion per loop.
Open it. Only hub → X is shot; X → hub is the entry played backwards,
free (transition --reverse-of).references/loops.md → Workflow F). Quote startGap, endGap and
step: gap ≤ 2·step lands; a later --trim-start or earlier
--trim-end is free, a new take is the user's money. Play every exit;
when a reversed exit reads wrong (a mug put down is not a mug picked up
backwards), say so and offer a real exit take at an entry's price.rive <character> --include-loops, registered; open the Export tab (navigate-to with
"tab": "export"), capture it, press a loop's button in its Rive preview
and watch the state line. Report the routes, each loop's
wait and every direct cut with its poseGap from stateMachine.Say the limits plainly. Leaving a loop waits for the end of its cycle
(stateMachine.waits; 3.8–5.1 s on tanka). Routes go through the hub. A pair
with no transition cuts, and the report says where and how far apart.
{{/videoGenEnabled}}
{{#videoGenDisabled}}
The transition clips are paid Seedance takes, so this workflow needs the fal
key. Without one, lineup still runs and rive still routes through the
hub: say which switches will cut, and how far apart their poses are.
{{/videoGenDisabled}}
What a motion's own run made — a sprite motion's GIF, WebP, sheet + atlas; a loop's four files; baked colourways — is already a download and needs no export step. The Export tab lists them beside everything else a ready motion can be made into. Match the format to where it is going:
| Where it goes | Format | How |
|---|---|---|
| Editing software — Premiere, Final Cut, After Effects, DaVinci | MOV, ProRes 4444 with alpha | export --format mov |
| Anywhere a plain video plays — chat, slides, social | MP4, H.264 on a solid colour | export --format mp4 --bg "#rrggbb" |
| A web page | WebM (VP9 with alpha), or the animated WebP the run made | export --format webm |
| Lossless frame animation; an app that plays Lottie | APNG; Lottie (raster frames) | export --format apng / lottie |
| A game engine | the sheet + atlas the run made; an Aseprite-format sheet of one motion or the whole character; a PNG sequence | export --format aseprite (motion or <character>) / png-seq — see Game engines |
| Product or app animation driven from code — a mascot switching states | Rive — the whole character: a number input picks the loop, a trigger per one-shot | rive <character> (loops and transitions: --include-loops or --motions) |
Each export is one command and one registration, piped — the same for
export <character> --format aseprite and rive <character> --motions …:
node {SKILL_PATH}/scripts/sprite-sheet.mjs export lumi/motions/attack --format mp4 --bg "#ffffff" --json \
| node {SKILL_PATH}/scripts/sprite-project.mjs register-export --dir lumi --report - --jsonFiles land in <character>/motions/<id>/exports/ (a motion) or
<character>/exports/ (the whole character); exporting again replaces the
file and its record. A failed export prints its ERROR: and registers
nothing. Re-running a motion retires the exports made from its old frames
(register-run says so): export again if the user still wants them.
Quote the size and duration from the report (every video is probed, and
refused when it is not what it claims); a looping motion's video repeats
until it lasts at least 3 s unless --repeat N says otherwise — tell the
user the length. --shadow casts a ground shadow on request (the tab has
no switch): into the frames of a video, whose canvas grows — the default
slant nearly doubles a tall figure's width, so offer a smaller
--shadow-shear — or as a separate shadow sheet beside an Aseprite export.
Every flag: references/pipeline.md → export.
Every sprite run already made sheet.png + atlas.json (TexturePacker
JSON-hash), each frame's anchor / pivot on where the feet stand — Phaser
and PixiJS 8 load it (engine notes: references/pipeline.md →
atlas.json). The Aseprite-format sheet — one motion, or every ready
sprite motion of the character on one sheet with a tag per motion and each
frame's duration — is a PNG and a JSON, not an editable .aseprite file;
loops and transitions are left out, mirrors go in as ordinary tags. Hand the
developer:
this.load.aseprite('lumi', 'lumi.png', 'lumi.json');
this.anims.createFromAseprite('lumi');
sprite.play({ key: 'idle', repeat: -1 }); // looping is not in the file: repeat: -1Pixel art exports at whole-number --scale; a colourway is the same atlas
over another sheet — swap the directory.
Rive — say these plainly, every time: the frames are raster
images, and the .riv plays in every Rive runtime but cannot be edited
in the Rive editor. It holds every ready sprite motion; loops go in only
when asked (--include-loops, or --motions a,b,c — ask which states the
app switches between), resampled to 24 fps and at most 320 px:
quote each motion's frames, fps, width × height from the report.
Say estimatedDecodeBytes, the memory once opened (over 128 MB warns, over
768 MB is refused — offer a lower --fps, a smaller --max-size or fewer
motions). Give the developer the wiring: State Machine 1, a number input
motion naming the loop (values in stateMachine.inputs), a trigger
play_<motionId> per one-shot, and that setting motion moves the
character at the end of the current cycle; say how many direct cuts
stateMachine.cuts lists and the largest poseGap. Every flag and field:
references/pipeline.md → rive.
The user can press four kinds of button on the stage. Each arrives as a notification naming the selected motion — or, for a whole-character file, the character.
render-video — they picked a model and a mode in the popover. Use
their choices, not your defaults, and follow workflow C.regenerate-motion — redraw the selected motion, folding in any note
they attached, from the source it already has (motion.source), under the
same id: register-run replaces the old frames, never duplicates them. Say
the price first when it is paid. A breathe is free: the same pipe with
the parameter the note asks for, the rest from show --motion <id>. A
mirror's drawing lives in its source: regenerate that and mirror again
(say so). A pixel motion keeps --pixel, then rebakes its colourways.
A loop gets a new take from the keyframe it has (workflow E, the clip
step); redraw the keyframe only when the note asks for a different look —
a new keyframe is a new subject the user never approved. Say which of the
two you are doing before you spend the clip.fix-alignment — the character swims or jumps between frames (not
offered on a breathe or a mirror: their frames share one footing by
construction). Read the inspect warnings against the motion plan:
unintended bodyDrift → re-run align from cells/ with --x-from feet
or cell; head sway the alignment added (headDrift over
sourceHeadDrift) on a walk → --x-from trend for a clip, body for a
sheet; unintended maxJump → --smooth or the other anchor. Pixel frames
re-align from pixel/. Check whether scaleDrift is a pose or prop change
before regenerating; clipped cells need a drawing fix. Preserve intended
movement. references/pipeline.md has the table and alignment limits.export — they pressed Generate (or Regenerate) on a row of the Export
tab. The facts line names character, motion (absent for a
whole-character file), format and, for mp4, the background. Run
exactly that export — the colour quoted (--bg "#1a2b3c"; unquoted, the
shell reads # as a comment) — and register it (Exporting). format: aseprite with scope: whole character is export <character> --format aseprite. For riv the facts line lists motions and any transitions —
run rive <character> --motions <the motions>,<the transitions> at the
defaults; a transition is never a Rive file of its own. Report the file and
its size, and for Rive the caveats above. Never swap in another format; if
the export refuses, say why in their words.<!-- pneuma:end -->
| Topic | File |
|---|---|
sheet-prompt, the sheet grammar and state guards, the idle recipe, worked prompts, the 3D-icon keyframe, fixing one cell, reference images, direction anchors and the sides | references/prompting.md |
Every sprite-sheet.mjs / sprite-project.mjs subcommand, flag, report field and warning, the atlas schema, and the measured numbers behind every default | references/pipeline.md |
Workflow E and F command by command: the brief, the keyframe, the clip, retime, interpolation and matting, loop, transitions and their reverses | references/loops.md |
| The chroma-green source clip, per-state motion sentences, the loop and transition clip templates, Seedance and H3 Max flags, video matting and interpolation, cost, latency (needs the fal key) | references/video-preview.md |
The project.json schema — the sprite sidecar, loop and transition motions, derived clips, exports, asset id conventions | references/project-json.md |
© pandazki, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 43 other files (scripts, references) in modes/sprite/skill of pandazki/pneuma-skills.
Open the folder on GitHubat commit 0023d3c
Pneuma Sprite next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Pneuma Sprite this skillpandazki/pneuma-skills | 161 | — | ~16k | Automated safety check: Pass | MIT | |
| Sprite Genaldegad/sprite-gen | 2.7k | — | ~4.9k | Automated safety check: Pass | Apache-2.0 | |
| Asset Pipelinerehan-remade/universal-modder | 6.1k | — | ~2k | Automated safety check: Pass | MIT | |
| Spine AnimationGenielabsOpenSource/spine-animation-ai | 521 | — | ~17k | Automated safety check: Pass | CC-BY-NC-4.0 | |
| AI Game Art Pipelineybuild-ai/ai-game-art-pipeline-skill | 297 | — | ~1.1k | Automated safety check: Pass | MIT | |
| Godot 2D Essentialsjame581/GodotPrompter | 805 | — | ~2.3k | Automated safety check: Pass | MIT |
aldegad/sprite-gen
Generates images and game sprites through GPT or Grok with guided provider choices, separate saved defaults, automatic cleanup and optional curation.
rehan-remade/universal-modder
Turn generated or hand-made art into exactly what a game engine loads.
GenielabsOpenSource/spine-animation-ai
Create Spine 2D skeletal animations from pre-existing character assets (separated body-part PNGs, atlas spritesheet, or a full character image).
ybuild-ai/ai-game-art-pipeline-skill
Provider-neutral open-source skill for planning and producing game-runtime art assets and animation: static props/icons, canonical character sheets, combat sprites, 3D/video motion references…
jame581/GodotPrompter
Covers Godot 4.3+ 2D systems with GDScript and C# examples: canvas layers, draw order, TileMaps, parallax, lights and shadows, particles and custom drawing.
thedivergentai/GD-Agentic-Skills
Expert patterns for 3D level design using GridMap with MeshLibrary, CSG constructive solid geometry, occlusion, and runtime GridMap builders.
pandazki/pneuma-skills
Explain something by writing it on a board. An agent skill from pandazki/pneuma-skills.
pandazki/pneuma-skills
AI-orchestrated video production on @pneuma-craft. An agent skill from pandazki/pneuma-skills.
pandazki/pneuma-skills
Pneuma Lucid Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.
pandazki/pneuma-skills
Pneuma Plotwise workspace guidelines. An agent skill from pandazki/pneuma-skills.
pandazki/pneuma-skills
Pneuma WebCraft Mode workspace guidelines with Impeccable.style design intelligence.
pandazki/pneuma-skills
A goal-driven Chinese long-form writing partner. An agent skill from pandazki/pneuma-skills.
Categories
Pneuma Sprite Mode workspace guidelines. An agent skill from pandazki/pneuma-skills. Pneuma Sprite is an agent skill from pandazki/pneuma-skills. Pneuma Sprite Mode workspace guidelines.
Pneuma Sprite fits situations like: ANY task in this workspace: telling which route the user is on (a game character; A picture brought to life; A looping animation for a page; A mascot for an app).
Run `npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a claude-code`. Or copy the skill folder (modes/sprite/skill in pandazki/pneuma-skills) into .claude/skills/pneuma-sprite in your project. Claude Code loads it when a task matches its description.
Run `npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a codex`. Or copy the skill folder (modes/sprite/skill in pandazki/pneuma-skills) into .agents/skills/pneuma-sprite in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/pneuma-sprite, .gemini/skills/pneuma-sprite, .github/skills/pneuma-sprite and .opencode/skills/pneuma-sprite in your project.
Going by SKILL.md and its folder, Pneuma Sprite needs TypeScript and JavaScript for the scripts in its folder and the command-line tools its instructions call (node). Our summary lists: Node.js.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Pneuma Sprite is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 16k tokens (SKILL.md is roughly 62k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 100k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Pneuma Sprite: Sprite Gen (aldegad/sprite-gen, 2.7k stars), Asset Pipeline (rehan-remade/universal-modder, 6.1k stars), Spine Animation (GenielabsOpenSource/spine-animation-ai, 521 stars) and AI Game Art Pipeline (ybuild-ai/ai-game-art-pipeline-skill, 297 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
pandazki (a GitHub user) maintains it in pandazki/pneuma-skills, which has 161 GitHub stars. The repository holds 30 skills in this directory. The repository was last updated on October 9, 2026.
Source: pandazki/pneuma-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.