Agent skill

Pneuma Sprite

by pandazki in pandazki/pneuma-skills

Pneuma Sprite Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.

MITAuto-check passedGame Development

Install Pneuma Sprite

skills CLI
$ npx skills add pandazki/pneuma-skills --skill pneuma-sprite -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install pandazki/pneuma-skills pneuma-sprite --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
pneuma-sprite
GitHub stars
161
Token cost
~16k tokens
SKILL.md length
8,886 words
Files
44 (incl. scripts, references)
Skills in repo
30
Repo updated
First seen
Licence
MIT

At a glance

Pneuma Sprite Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.

  • Works in 3 steps: Diagnose — sprite-sheet.mjs inspect:… → Look — get-playback-state + capture: the… → Verify — play, then pause + capture at…
  • ANY task in this workspace: telling which route the user is on (a game character
  • SKILL.md covers Talk to the user in their…, Scene, Core rules and Money and waiting, plus 11 more sections
  • Runs TypeScript and JavaScript scripts from its folder; calls node

What it does

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.

When your agent uses it

  • 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)

Example prompts

  • “/pneuma-sprite”

Requirements

  • Node.js

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. Diagnose — sprite-sheet.mjs inspect: deterministic, free, no model.
  2. Look — get-playback-state + capture: the only way to see what the
  3. Verify — play, then pause + capture at two or three frames.

What it can do on your machine

Read from SKILL.md and the folder at commit 0023d3c. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    Ships 13 files in scripts/ (TypeScript and JavaScript, from the files we listed), which the agent can run.

    Shell commands in SKILL.md call:

    • node

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~199
When it runs · the whole SKILL.md, loaded when a task matches
~16k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~115k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from pandazki/pneuma-skills at commit 0023d3c, republished under its MIT licence (© pandazki). 8,886 words, ~15,593 tokens.

Download SKILL.mdSave it as .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.
name
pneuma-sprite
description
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 exporting to a game engine (atlas, Aseprite), video, frame animation or Rive. Defines the project.json contract, the pipeline scripts, and how to look through the viewer before claiming a motion is done. Consult before your first generation in a new conversation.

Pneuma Sprite Skill

<!-- pneuma:start -->

Talk to the user in their language, about what they will see

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.

Scene

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.

Core rules

  • Never 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).
  • Your own working files stay inside the workspace, in <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.
  • Frames, atlases and previews are written only by 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.
  • Frames come from four places, and only these: a generated sheet (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.
  • Every sheet is generated with the character references attached, in the order 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.
  • A sheet's background is asked for in words, then cut off afterwards: the prompt asks for a flat solid pure white background, the call passes --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).
  • Never pass --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.
  • Scripts retry upstream failures themselves. 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.

Money and waiting

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 stepPriceWait
a reference image (2048², --quality high)≈ $0.1130–40 s
a direction anchor (1024²)≈ $0.07–0.08≈ 25 s
a sheet (2048 wide, references attached)≈ $0.05–0.1320–35 s
a cut-out (remove-background.mjs, fal BiRefNet)≈ $0.0006 — under a cent6–20 s
a 4 s 480p square clip (Seedance); 5 s≈ $0.83; ≈ $1.02–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.

Look before you claim

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.

  • Every motion, every time. After 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.
  • A jump: capture the peak frame beside frame 0 and compare where the feet are. Feet on the same line mean the frames have no jump height (workflow B, Jumps).
  • Props are held. In every capture: the hand on the handle, the strap on the shoulder, nothing floating beside the body.
  • One size across a set is measured, not read off scaleDrift (the spread inside one motion only): run sprite-sheet.mjs sizes <character> and look at sizes.png before you say the motions match.
  • Before the handoff, look at the Export tab the user will open: 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.
  • Say what you measured. "The loop wraps within one normal frame step", "the feet move less than a pixel", "the heights differ by 5 %" are claims you can stand behind; "you can't see a seam" and "the feet don't move at all" are not. Never say you played or checked every motion unless you did.

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.

Viewer contract

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.

What the user can select

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.

ViewerAddress vocabulary
KeyKindMeaning
contentSetframework-reservedThe character directory ("lumi"). There is no separate character key — the character is the content set.
motioncoarseMotion id inside the character ("idle", "walk-left").
refcoarseReference image id ("turnaround"). Mutually exclusive with motion.
framefine0-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.

Actions you can invoke
  • 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]" }.
Three sensing layers, in cost order
  1. Diagnose — 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.
  2. Look — get-playback-state + capture: the only way to see what the user sees.
  3. Verify — play, then pause + capture at two or three frames.

Pick the route

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.

RouteForWhat you hear
G · a game charactersomeone 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 lifeanyone with one picture and no planan image attached with "make it move", "animate this", "让它动起来"
L · a looping animation for a pagea frontend or product person"animated icon", "loading animation", "a flame that flickers on the landing page", "Lottie", "WebP"
M · a mascot for an appan 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.

Route G — a game character

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:

  1. "What must it do — stand, walk, run, attack, jump? Does it need to face several directions?"
  2. "About how big is it on screen, and is it pixel art?"
  3. "What budget should I stay under?"

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.

G-pixel — pixel art

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>.
  • The height is checked, never forced — and never redeclared on your own. Measured once: asked for 32, the model drew 53. When the run warns that the frames stand at another height, put the choice to the user: regenerate at the height they asked for (say the price), or accept what was drawn (set-character --pixel <measured>), which holds every later motion to it.
  • One palette for every motion, pinned by the first pixel run's 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.
  • Colourways (a red team and a blue team): offer them once a motion is ready. The original counts as one of the colours ("three colours" is the original plus two colourways), and the outline, eyes and highlights stay as drawn unless the user asks. 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.
G-4dir — several facings

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:

FacingThe character's own right sideTheir own left side
fronton the left of the pictureon the right of the picture
backon the right of the pictureon the left of the picture
leftthe far side — turned away, partly hidden by the bodythe near side, toward the viewer
rightthe near side, toward the viewerthe 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.

  1. 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).

  2. 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.

  3. 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

    bash
    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 - --json

    On 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.

  4. 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.

Route A — bring my picture to life

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.

bash
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.

  • The head rides the breath. Nothing above the rigid row changes shape, but the head is not still: it rides up and down with the body as one piece. Say it with the measured numbers — "the head bobs gently with the breath, about 5 px up and down" — never "the head stays still". 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.
  • Capture the extremes the run measured, not frames you guess. 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.
  • A prop across that row — Lumi's lantern — is the one warning that asks the user a question. In their words: "The lantern crosses the line where her body stops breathing and everything above moves as one piece. I can keep the lantern whole, and then her chest stops breathing above that line — or let it sway a little with her breath. Which do you prefer?" Re-run with the --rigid-row the warning names, or keep it with set-motion --ack-warnings "the lantern sways with her breath, as chosen".
  • "More", "less", "slower" is a free re-run of the same pipe: --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.

Route L — a looping animation for a page

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.

Route M — a mascot for an app

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.

Workflows

The mechanics every route above links into.

A. Design a character

Do this before any motion exists — every later prompt is anchored to it.

  1. Interview briefly — name, one paragraph of description, a style sentence (the exact words that will open every prompt), facing (left/right), and cell size (256×256 is a good default). Ask in one message, not five, together with the route's own questions.
  2. Generate <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.
  3. Generate <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).
  4. Record it — 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.
  5. Show them — 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}}

A′. Start from the user's own image

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.

  1. Validate it in one look. One character, full body head to feet, limbs unobstructed, a plain or flat background. A design sheet with several views is the best case: register the whole sheet as one reference (role 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).
  2. Register it honestly. A file the user handed you is 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.
  3. Still hold the interview — the name and, above all, the style sentence. Write it from what you see (line weight, shading, palette, proportions), read it back to the user in the same message as the other questions, and only then init. One that contradicts the uploaded image makes the model split the difference.
  4. Fill the gaps with the upload attached. A missing portrait or view is generated the way workflow A generates its second reference: the uploaded image on --image-urls, the prompt naming what to match. Compare the first generated reference with the upload before registering it.
  5. Show them, as in A step 5.
B. Add a motion

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:

SourceCost & timeBest for
sheet — one generated image, sliced into cells≈ $0.05–0.13 and ≈ 35 s, one imageidle, 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 minwalk, run, anything where smoothness is the point: the model draws the in-betweens
breathe — one registered still, warpedfree, secondsa breathing idle from a single picture (route A)
mirror — a ready side-facing motion, flippedfree, secondsthe 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?"

  • In the frames: a sheet runs with 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.
  • In the game: the default alignment stands every frame on the ground line; say at the handoff that the jump plays in place on the stage and takes its height from their code.

Step 1 — pick the grid, fps, loop and anchor. Defaults that work; deviate when the motion needs it and say why.

Motion typeFrames / gridfpsLoopAnchor
idle8, 4×2≈ 3.3 (a 2.4 s cycle)yesbottom
walk / run8, 4×2 (8–12 from a clip)10–12yesbottom
attack8, 4×2 or 16, 4×410nobottom
jump8, 4×210nobottom (+ --y-from cell / clip when the height is in the frames)
story key poses9, 3×36nobottom

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.

Show full SKILL.md (3,467 more words)Show less
B-sheet — the generated sheet
  1. 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):

    bash
    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.txt

    It 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).

  2. 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.

  3. Cut the background out — the normal step, not a branch. probe the sheet first (hasAlpha: false, a near-white cornerColor is expected), then:

    bash
    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 --json

    BiRefNet 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.

  4. Run the pipeline — slice → clean → align → pack → gif → inspect in one call, on sheet-raw.png with --alpha for what step 5 made:

    bash
    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.json

    The 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.

B-video — the sampled clip
  1. 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.

  2. 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:

    Motionflatten 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 elsenothing

    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.

  3. 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 "…").

  4. Look at the clip before you cut it —

    bash
    node {SKILL_PATH}/scripts/sprite-sheet.mjs contact \
      <character>/motions/<id>/video-seedance-1.mp4 \
      --out <character>/motions/<id>/contact.png --json

    Open 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).

  5. Sample that window —

    bash
    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.

Every source ends the same way
  1. Record the run — 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.
  2. Read the numbers, then look (Look before you claim). Report the inspect values, not "no warnings"; check identity, contacts, direction and held props in the captures; the last-to-first transition of a loop or the final pose of a one-shot.
  3. Keep it or fix it, and say which. A warning you keep is acknowledged in one sentence the user reads: 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.

C. Render a preview clip

{{#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.

  1. Flatten frame 00 onto a colour that suits the character (sprite-sheet.mjs flatten) — video models mishandle alpha.
  2. Pick the mode: 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.
  3. Register around the call: add-video … --status generating before, set-video --video <id> --status ready|failed [--notes] after.
  4. Say the price (≈ $0.83 for 4 s at 480p on Seedance) and the wait, then leave it alone; quote the 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}}

E. A seamless loop for the UI

{{#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):

  1. The keyframe — reserve it (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.
  2. The clip — add-video --mode first-last --status generating, the price-and-wait line, one seedance-video.mjs call, set-video --video <id> --status ready|failed.
  3. Look before cutting (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.
  4. Optional, interpolate, then matte (veed-gs on a green plate), each registered with add-video --derived-from.
  5. Cut it — loop <the last clip in the chain> --width <brief.width> (--key alpha on a matted clip), then register-run --video <that clip>.
  6. Read the seam, then look at it — report 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}}

F. Connect the loops for Rive

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}}

  1. Look first, free — 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).
  2. Price it and ask for a budget — per entry one take ≈ $0.83 and one matte ≈ $0.10 (no interpolation: a Rive file plays at 24 fps); five entries ≈ $4.7, three to eleven minutes a take. Keep a running total.
  3. Register, brief, shoot, matte and cut each entry (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.
  4. Export and look through the preview — 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}}

Exporting

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 goesFormatHow
Editing software — Premiere, Final Cut, After Effects, DaVinciMOV, ProRes 4444 with alphaexport --format mov
Anywhere a plain video plays — chat, slides, socialMP4, H.264 on a solid colourexport --format mp4 --bg "#rrggbb"
A web pageWebM (VP9 with alpha), or the animated WebP the run madeexport --format webm
Lossless frame animation; an app that plays LottieAPNG; Lottie (raster frames)export --format apng / lottie
A game enginethe sheet + atlas the run made; an Aseprite-format sheet of one motion or the whole character; a PNG sequenceexport --format aseprite (motion or <character>) / png-seq — see Game engines
Product or app animation driven from code — a mascot switching statesRive — the whole character: a number input picks the loop, a trigger per one-shotrive <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 …:

bash
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 - --json

Files 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.

Game engines

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:

js
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: -1

Pixel 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.

Commands

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 -->

References — read when you need depth on the topic

TopicFile
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 sidesreferences/prompting.md
Every sprite-sheet.mjs / sprite-project.mjs subcommand, flag, report field and warning, the atlas schema, and the measured numbers behind every defaultreferences/pipeline.md
Workflow E and F command by command: the brief, the keyframe, the clip, retime, interpolation and matting, loop, transitions and their reversesreferences/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 conventionsreferences/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

Files

SKILL.md and 43 other files (scripts, references) in modes/sprite/skill of pandazki/pneuma-skills.

  • SKILL.md
  • references/loops.md
  • references/pipeline.md
  • references/project-json.md
  • references/prompting.md
  • references/video-preview.md
  • scripts/aseprite.d.mts
  • scripts/aseprite.mjs
  • scripts/breathe.d.mts
  • scripts/breathe.mjs
  • scripts/canvas.d.mts
  • scripts/canvas.mjs
  • scripts/chroma.d.mts
  • scripts/chroma.mjs
  • scripts/cycle.d.mts
  • scripts/cycle.mjs
  • scripts/drift.d.mts
  • scripts/drift.mjs
  • scripts/frame-steps.d.mts
  • … and 25 more

Open the folder on GitHubat commit 0023d3c

Compare with similar skills

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.

Pneuma Sprite compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Pneuma Sprite this skillpandazki/pneuma-skills161—~16kAutomated safety check: PassMIT
Sprite Genaldegad/sprite-gen2.7k—~4.9kAutomated safety check: PassApache-2.0
Asset Pipelinerehan-remade/universal-modder6.1k—~2kAutomated safety check: PassMIT
Spine AnimationGenielabsOpenSource/spine-animation-ai521—~17kAutomated safety check: PassCC-BY-NC-4.0
AI Game Art Pipelineybuild-ai/ai-game-art-pipeline-skill297—~1.1kAutomated safety check: PassMIT
Godot 2D Essentialsjame581/GodotPrompter805—~2.3kAutomated safety check: PassMIT

Similar skills

  • 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.

    2.7k GitHub stars~4.9k tokensUpdated yesterday
    Game DevelopmentAuto-check passed
  • Asset Pipeline

    rehan-remade/universal-modder

    Turn generated or hand-made art into exactly what a game engine loads.

    6.1k GitHub stars~2k tokensUpdated today
    Game DevelopmentAuto-check passed
  • Spine Animation

    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).

    521 GitHub stars~17k tokensUpdated 1 mo ago
    Game DevelopmentAuto-check passed
  • AI Game Art Pipeline

    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…

    297 GitHub stars~1.1k tokensUpdated 3 mo ago
    Game DevelopmentAuto-check passed
  • Godot 2D Essentials

    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.

    805 GitHub stars~2.3k tokensUpdated yesterday
    Game DevelopmentAuto-check passed
  • Godot 3D World Building

    thedivergentai/GD-Agentic-Skills

    Expert patterns for 3D level design using GridMap with MeshLibrary, CSG constructive solid geometry, occlusion, and runtime GridMap builders.

    821 GitHub stars~3.3k tokensUpdated 1 mo ago
    Game DevelopmentAuto-check passed

More from pandazki/pneuma-skills

All 30 skills in this repo
  • Pneuma Bansho

    pandazki/pneuma-skills

    Explain something by writing it on a board. An agent skill from pandazki/pneuma-skills.

    161 GitHub stars~6.9k tokensUpdated yesterday
    Auto-check passed
  • Pneuma Clipcraft

    pandazki/pneuma-skills

    AI-orchestrated video production on @pneuma-craft. An agent skill from pandazki/pneuma-skills.

    161 GitHub stars~7.5k tokensUpdated yesterday
    Auto-check: notes
  • Pneuma Lucid

    pandazki/pneuma-skills

    Pneuma Lucid Mode workspace guidelines. An agent skill from pandazki/pneuma-skills.

    161 GitHub stars~4.6k tokensUpdated yesterday
    Auto-check: warnings
  • Pneuma Plotwise

    pandazki/pneuma-skills

    Pneuma Plotwise workspace guidelines. An agent skill from pandazki/pneuma-skills.

    161 GitHub stars~8.9k tokensUpdated yesterday
    Auto-check passed
  • Pneuma Webcraft

    pandazki/pneuma-skills

    Pneuma WebCraft Mode workspace guidelines with Impeccable.style design intelligence.

    161 GitHub stars~7.5k tokensUpdated yesterday
    Auto-check: notes
  • Pneuma Wordtaste

    pandazki/pneuma-skills

    A goal-driven Chinese long-form writing partner. An agent skill from pandazki/pneuma-skills.

    161 GitHub stars~7.5k tokensUpdated yesterday
    Auto-check passed

Questions about Pneuma Sprite

What does Pneuma Sprite do?

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.

When should I use Pneuma Sprite?

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).

How do I install Pneuma Sprite in Claude Code?

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.

How do I install Pneuma Sprite in Codex?

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.

Can I use Pneuma Sprite in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Pneuma Sprite need to run?

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.

Does Pneuma Sprite access the network?

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.

Is Pneuma Sprite safe to install?

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.

What licence does Pneuma Sprite use?

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.

How many tokens does Pneuma Sprite use?

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.

What are the alternatives to Pneuma Sprite?

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.

Who maintains Pneuma Sprite?

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.