agentsclimarketplace

Pixellab

Skill freema/ai-skills/skills/pixellab

Pixel-art asset generation for game projects, MCP-first via the connected pixellab MCP server (mcp__pixellab__* tools). Triggers on: "generate pixel art", "make a pixel sprite/tile/character", "animate this sprite", or any pixel-art asset request for app/games/**. Falls back to scripts/pixellab.mjs only for animated WebP assembly or as a REST emergency path.From its SKILL.md

Install
npx -y skills add freema/ai-skills --skill pixellab

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

15.4 KB, as published. Nobody here has run it

PixelLab Skills (MCP-first)

Pixel-art asset generation for app/games/**. The PixelLab MCP server is the default path — it is connected this session as mcp__pixellab__* tools, lands every asset in the PixelLab dashboard (reusable, style-chainable), and bills against our subscription. Use the local scripts/pixellab.mjs only for the one thing the MCP can't do (assemble an animated WebP) and as an emergency REST fallback.

Official docs / disambiguation: mcp__pixellab__agent_help (a docs-search agent — ask it ≤500-char questions). MCP tool reference: https://api.pixellab.ai/mcp/docs · REST reference: https://api.pixellab.ai/v2/llms.txt (+ full param/cost detail in …/v2/openapi.json).


Golden rules

  1. MCP-first. Generate through mcp__pixellab__*. Reach for scripts/pixellab.mjs only for webp assembly or if the MCP server is unreachable.
  2. Write BIG, exact descriptions. PixelLab rewards long, fully-specified prompts: subject, view angle, palette, lighting, material, silhouette, and an explicit list of what to EXCLUDE. Vague prompts waste generations. (See "Writing prompts".)
  3. Style-lock related assets with create_object_state / create_character_state on a completed parent — never cold-generate siblings (loses palette/identity). See [[feedback-pixellab-card-state-parent]], [[feedback-claimer-sprite-refs]].
  4. Never save binary with the Write tool — it corrupts PNGs to zero-filled garbage. curl -L the rotation URL, then file-verify.
  5. Bump the filename on any asset replacement (-v3 → -v4) — public/assets/** is immutable-cached 1y + 30d nginx. See [[feedback-nginx-static-cache]].
  6. PixelLab only — no PixelForge fallback for game sprites. See [[feedback-pixellab-only-for-games]].

⚠️ Tool-name reality check

There is NO create_object and NO vary_object tool — those names (in older memory) were never real. The actual primitives on the connected server are:

You want…Real MCP tool
One static sprite / prop / iconcreate_1_direction_object
Same object from 8 angles (rotations)create_8_direction_object
A variant in the same style as an objectcreate_object_state (source object_id + edit_description)
Throwaway object (auto-deletes 8h) + inpaintcreate_map_object
Rigged character (4/8 dir, walk cycles)create_character → create_character_state → animate_character
Motion on an existing objectanimate_object (mode v3 = cheap default)
Terrain autotilingcreate_topdown_tileset / create_sidescroller_tileset
Tile variants / isometriccreate_tiles_pro / create_isometric_tile
Poll any of the aboveget_object / get_character / get_*_tileset
Pick from a review candidate gridselect_object_frames / dismiss_review
Balanceget_balance

Canonical flow: create → poll → download (verified)

Generation is asynchronous. The create call returns an id immediately; you poll until completed, then download the PNG from a public URL.

1. create_1_direction_object({ description, size }) → returns object_id
2. get_object({ object_id, include_preview: false })   # poll every ~10–30s
     status: processing  → progress + ETA, keep polling
     status: review      → candidate grid (see "Review gate") — select or dismiss
     status: completed   → has `rotations:` + `download:`
     status: failed      → error + retry hint
3. Download the PNG. `get_object` (completed) returns, exactly:
     rotations:
       <dir>: https://backblaze.pixellab.ai/file/pixellab-characters/objects/<grp>/<id>/rotations/<dir>.png
     download: https://api.pixellab.ai/mcp/objects/<id>/download   # zip archive of all frames
   For a 1-direction object the rotation key is literally `unknown`.
4. curl -L "<rotation-url>" -o public/assets/games/<game>/<name>-v1.png   # NO auth header needed
5. file public/assets/games/<game>/<name>-v1.png   # MUST say "PNG image data, WxH, 8-bit/color RGBA"

Download facts (verified live): rotation URLs are public Backblaze links — a plain curl -L with no Authorization header returns the real PNG (HTTP 200). The MCP never returns raw bytes and never writes files; it only hands back URLs (+ an optional inline preview when include_preview is true — for eyeballing, not for saving). Poll budgets: 1-dir/tiles ~15–90s, 8-dir/characters ~2–4 min, tilesets ~100s.

Review-status gate (don't lose your asset)

create_1_direction_object with size ≤ 170 returns multiple candidates in review status (≤42→64 candidates, ≤85→16, ≤170→4). get_object shows them inline; you must then call select_object_frames({ object_id, indices: [n] }) to promote the good one(s) into their own completed objects, or dismiss_review to discard. Leave it and the asset never finalizes. Use size ≥ 171 to get a single candidate with no review step.


Style consistency: object-states & style-refs

Build a family of related sprites by editing a completed parent, not by re-prompting:

create_1_direction_object({ description: "<the first/base sprite>", size: 48 })  → parentId
create_object_state({ object_id: parentId, edit_description: "make it a green shell instead" })
create_object_state({ object_id: parentId, edit_description: "make it a lightning bolt instead" })

All variants share palette/outline/identity via an automatic group_id. This is the proven pattern for the mau-mau card faces (parent 06f843eb-abc3-4b2e-a7ee-c2974acbebb1 — create_object_state ONLY, never a fresh create_*) and the claimer directional sprites. For style-matching across a totally new object, pass style_images to create_1_direction_object (largest style image's size sets output size — don't also pass size).


Writing prompts (the single biggest quality lever)

PixelLab interprets a long, exact description far better than a terse one. Always specify: subject + view + palette + lighting/shading + material/texture + silhouette + EXCLUSIONS.

✅ Good: "Single top-down pixel-art banana-peel power-up icon, 48×48, lineless flat shading, transparent background. Glossy yellow curved peel lying flat as if dropped on a track, small brown bruise tip, soft inner neon-yellow rim glow, dark drop-shadow blob beneath. Centered, no text, no border, no frame."

❌ Bad: "a banana item" — model guesses size, view, palette, adds a background/border.

  • outline: "lineless" almost always (user hates borders).
  • For opaque scenes add "edge to edge, full bleed, no border, no frame, no padding" or you get a white-canvas vignette. For "no characters" say so explicitly — models populate scenes by default.
  • detail: "medium detail", shading: "medium shading" are good defaults. Use seed for reproducibility.

Cost model (dual bucket — the old "16 credits/call" rule is WRONG)

get_balance shows two separate buckets:

  • Subscription generations — the primary bucket (Tier 1 = 2000 per billing period). Each call decrements this first.
  • USD credits — a fallback only, used when generations are exhausted.

Cost is per call, by endpoint + size — NOT per image. One v2 call can return a grid of up to 64 images for the same generation cost. So:

  • Standard object/character = 1 generation. v3 character = 2–9.
  • create_1_direction_object / create_8_direction_object use Pro Tools = ~20–40 generations per call by size.
  • The only real cost cliff is Pro-mode animation: 20–40 generations PER DIRECTION (a full 8-dir Pro animation = 160–320 = ~6–12 of those empties the monthly allowance). Prefer mode: "v3" (the cheap default, often higher quality). For Pro animate_*, call without confirm_cost first to get the quote, surface it, then re-call with confirm_cost: true.
  • enhance_prompt silently adds 0.05 generations.

Check get_balance before a big batch; budget by calls, not by samples.


File-saving rules

  1. Never use the Write tool for image data (corrupts binary → zero-filled file).
  2. Download via curl -L "<rotations.<dir>>" -o <path> (no auth header). The download: URL is a zip archive of all frames — use it only when you want every rotation/frame at once.
  3. Always file <path> — must report PNG image data, …, RGBA. If it says data/JPEG/RIFF, it's broken; regenerate, don't patch in place.
  4. Optimize after: pngquant --force --quality=60-85 --ext .png <path> (60–85% is indistinguishable for pixel art; a 256×384 bg drops ~130KB → ~50KB).
  5. Replacing an existing asset? Bump the filename suffix and update the loader key (nginx cache).

Animation & animated thumbnails

  • In-engine motion: animate_object({ object_id, animation_description, mode: "v3" }) (1 gen/dir, frame_count 4–16, default 8 → stores 9 incl. a ref frame). Poll get_object; completed response lists animations: with downloadable frame URLs (Backblaze, same as rotations).
  • Animated thumbnails / menu loops: download the frames, then assemble locally with scripts/pixellab.mjs webp --in-dir <frames> --out <x>.webp --duration 120. The MCP/REST cannot assemble a WebP — this is the one irreplaceable local step. See [[feedback-pixellab-animate-webp]].
  • Pixel-exact thumbnails (compose the game's REAL sprites with overlay sparkle/pulse, then WebP): prefer this when identity must be preserved — generative animate-with-text-v3 reinterprets the concept and will NOT keep your exact pixels. (The reason is pixel-identity, not cost.) Full PIL recipe in [[feedback-pixellab-animate-webp]] / the api-v2-extra reference.

scripts/pixellab.mjs — fallback + WebP assembler (NOT broken)

The audit confirmed its commands are valid REST calls; it is superseded for static work, not broken. Keep it. Reads PIXELLAB_API_KEY from .env (already set).

CommandStatus
webpLOAD-BEARING — local img2webp (libwebp); no MCP/REST equivalent.
sprite / background / styleValid generate-image-v2/generate-with-style-v2 fallback. Default to MCP create_1_direction_object / create_object_state instead (dashboard + style-lock).
animateGenerative (breaks pixel identity) — prefer MCP animate_object v3.
balanceRedundant with MCP get_balance.

Hard host prerequisite for webp: img2webp from libwebp (brew install webp). One genuine use where the script wins: a wide panoramic banner (e.g. a skybox strip) — MCP object tools pad toward square, so pixellab.mjs sprite with an explicit wide --size is the right tool there. ⚠️ But generate-image-v2 caps aspect at ~688×384 (16:9) — extreme strips (e.g. 1024×192) exceed it; generate a seamless tile within the cap and let the engine TileSprite repeat it.


Tilesets — Wang (top-down) PREFERRED for terrain

create_topdown_tileset → 16 Wang tiles (23 if transition_size: 1.0), 32×32, outline: "lineless". lower_description = ground, upper_description = elevated, transition_description = edge blend. ~100s. Chain seamless multi-terrain via the returned base tile IDs.

Download: curl -L --fail (302 redirect → 0 bytes without -L).

Wang index + Phaser integration (vertex-terrain algorithm, frame lookup, RenderTexture layering) — unchanged and correct; see the detailed recipe in the api-v2-extra reference and below.

Wang index = NW×8 + NE×4 + SW×2 + SE×1   (upper=1, lower=0)
Frame      = (bbox.y/32)*4 + (bbox.x/32)
Vertex(vr,vc) = 0 if ANY surrounding cell is target terrain, else 1
  • Sidescroller platform tiles: create_sidescroller_tileset — see sidescroller-tilesets.md.
  • Tiles Pro / isometric: create_tiles_pro (number each variant: "1). grass 2). dirt …", n must match) / create_isometric_tile (tile_shape thin/thick/block). Small pickups look BAD as tiles — use Graphics primitives for tiny in-game objects; reserve tiles for 32×32+ decor.
  • Corner-tile orientation is often mislabeled — verify at 10× zoom, or just use Wang.

Characters & enemies

  • Humanoid / quadruped: create_character (body_type, mode: standard|v3|pro, n_directions 4|8). Standard = 1 gen; v3 = 2–9 (always 8-dir, top quality); pro = 20–40. Canvas is ~40% larger than the requested size (leaves animation room) — slice sheets accordingly.
  • Variants (armor color, sitting, damaged): create_character_state (consistent across all rotations).
  • Animate: animate_character — template_animation_id (walk/run/idle… 1 gen/dir) or action_description (custom, v3). South = default facing; West = East with setFlipX(true).
  • Blobs / slimes / non-humanoid: NOT create_character (forces legs). Use create_1_direction_object / create_map_object per pose, describe "no legs, no arms, blob body".

Gotchas

  • Base64 image inputs use { "type": "base64", "base64": "<png-bytes-b64>", "format": "png" } — base64-encoded PNG, not raw RGBA, not a data URL.
  • Size limits vary per endpoint: generic min 16×16; animation/rotation max 256×256; generate-image-v2/style/ui square ≤512 (16:9 ≤688×384); animate-with-text v1 locked to 64×64; tiles-pro 16–128. Let the API's 400 error tell you the real max for your aspect ratio.
  • One v2 image call returns a GRID (up to 64 tiny images), not a single file — parse accordingly.
  • no_background defaults FALSE on legacy create-image-*, TRUE on the v2 Pro endpoints.
  • Raw-RGBA vs PNG: some REST responses are base64 raw-RGBA, not PNG; pixellab.mjs auto-wraps these. MCP rotation URLs are always real PNGs (verified). Always file-check.
  • Destructive deletes (delete_object/_character/_animation) need a confirm=false-then-true two-step; tileset/tile deletes take just the id.
  • create_map_object auto-deletes after 8h — download immediately.

General

  • get_balance is the live source of truth for budget (not the REST balance command).
  • Backblaze rotation/B2 URLs are permanent and public — safe to reference while iterating.
  • Always check status with get_* before downloading; jobs are auto-cleaned after completion (404).

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.