Scenario Sprite Animation
Overview
A bare prompt to a generic image model for "a sprite sheet, walk cycle, 8 frames" returns a picture of one; a written grid spec on a high-adherence image model returns the sheet (the single-sheet row below). Frame sequences come from routes picked by the control the task needs, not sprite size alone. This skill routes between lanes and carries the frame math; depth lives with siblings: scenario-video (video model families), scenario-consistency and scenario-model-training (one character across an action set), scenario-game-assets (statics, pixel cleanup, upscaling; a sheet whose cells are one character's frames stays here), scenario-refine-loop (iterate to brief). Connection and the core loop: the scenario skill. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
Quick reference
The schema is the contract, read with model_schema_get on the pick: re-discover the generative members each session rather than hardcode them (the tool ids above are fixed). Per-lane schema notes, covering the Retro Diffusion canvas lock and returnSpritesheet seed pairing on the pixel lane, the single-sheet schema shape and prices, the video-lane control surfaces, the alpha routes, and how to read a deprecation tag: references/lane-schemas.md.
The single-sheet lane trades the pixel lane's native pixels, locked canvas and seed pairing for a grid honored as written; the pixel look is emulated, so the snapping pass is part of the lane. Unattended, the task decides between the table's first two rows: an exact grid, frame count or scripted beats in the brief takes the top-ranked general model over specialty.model_id, whose when_general_better names exactly those needs. Write the spec for the pipeline that will read it: rows and columns within the slicer's 6x6 (16 frames as 4x4, never 2 by 8, which is a local cut), a canvas the grid divides evenly, one scale, camera, facing and ground baseline, the beats per frame range with the last frame matching the first, generous margins, no labels. Leave GIF production lines out: an image model returns stills, and a hold is the same pose across extra frames. A background enum names no color, so a solid field is prompt wording; an existing character goes through the reference-image field; with no seed a re-run is a new character, so candidates come from the sample-count parameter and one sheet is approved before slicing. Read the delivered sheet before slicing: count the cells, read the cell edges, and count distinct poses, since a beat range naming a single end state can come back as one held pose. Then slice, seam-check the tiles per step 5, align the body across tiles, snap every kept tile with the same color count and seed, check that the snapped tiles share one size, and assemble. Alignment is a post step, not a prompt: the body drifts between rows (in one run the grounded feet sat 87 px higher in the last row than in the first, and neither anchoring language nor a layout template passed as reference image moved that), so measure each tile's body box, shift it onto one baseline and one horizontal center on a wider canvas so nothing clips, then upload_asset the re-laid sheet and slice it, since frames edited locally never become assets.
For reusable layouts, read the grid manifest and template usage guide. Resolve uploaded assets and publish new versions through the scenario skill's shared asset lifecycle. When none is accessible, the grid builder, run by a maintainer or an agent needing local geometry, creates the selected preset for MCP upload; it does not generate animation. Preserve the user's frame count and canvas over any preset. Templates do not replace the alignment, edge, and loop checks above.
For a character in several facings (isometric diagonals, side or top-down) with walk, run, idle and attack rows, follow the directional cycles lane: it covers the user's own art, the key-color check, mirroring and weapon hands, and engine import. Its three scripts, run by the agent, keep the MCP steps in the lane and do only the local geometry: cycle_prompts.py writes model-agnostic requests to map onto a discovered schema, cycle_frames.py makes first frames from any art, and cycle_sheets.py keys, loop-searches, registers and pixelates a character's clips into sheets, which are then uploaded with upload_asset.
The frames pipeline is tool models run with model_run, not separate MCP tools: a video-to-image-sequence extractor (extractAllFrames, or every Nth frame via frameInterval; the order of its frames is verified, never assumed, see below), a grid maker (images, up to 100, kept in input order; columns, default 3, and rows, computed from the count when unset, so a delivered 2 by 8 is the finished frames at columns: 8; backgroundColor takes a hex string, transparent, black or white), a grid slicer (image plus xSubdivisions and ySubdivisions, 1 to 6 each and defaulting to 2 when omitted, so 6x6 is the cap; no output-size parameter; tiles return in row-major order but not always at cell size, so check one with asset_get, and a resized tile or a sheet past 6x6 means cutting the sheet locally on the grid you counted), a sequence-to-video assembler (model_scenario-image-seq-to-video, a first-party id stable enough to name; fps 1 to 120, loopCount, pingpong, outputFormat gif or mp4), and a pixel snapper (re-detects the art's grid and quantizes to the requested color count, the one palette control in the cleanup chain; priced per tile, so probe one before the set). GIF frame delays quantize to 10 ms, so ask the assembler for an fps that divides 100 (10, 20 or 25; 8 lands at 125 ms and 12 at 83 ms, neither on the grid), or it re-times and duplicates frames to hold the duration. Fps is the only timing lever after generation, so plan frame counts up front and count what came back. Analysis is fine locally: differencing, seam math, edge reads, and window searches need pixels on disk.
Frame order is verified, never assumed. Extracted frames share one createdAt and carry no index in metadata, so their order exists only in the job row's assetIds, and that list has come back out of time order (a confirmed defect at authoring time). Before any stride math, slice, or assembly, pass the ids to the grid maker in returned order and read the contact sheet against the clip's free firstFrame and lastFrame (asset_get) and its motion: a frame that breaks continuity is out of place, and the corrected order is carried as your own id list from then on. Never rebuild order from listings, timestamps, or search.