# Aseprite

> Parse Aseprite files (.ase/.aseprite) — layers, cels, tags, slices, palettes, tilesets — into JSON, durations and bounds for engines and tools.

- Skill: `kyh/aseprite` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add kyh/aseprite`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kyh/aseprite/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: kyh (https://skillmd.com/u/kyh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kyh/aseprite

---


# Aseprite Inference

An `.ase`/`.aseprite` file is a structured timeline of layered pixel (or tilemap) cels. Read and verify the file rather than hard-coding expectations; output what you know _and_ what you assumed.

**Before inferring, ask:**

- Authoring intent (tags/slices/user data) or render intent (visible pixels/bounds/ordering)?
- Do you need pixels (decompress cel images), or is structure-only (layers/timing/tags) enough?
- Is the sprite RGBA / Grayscale / Indexed / Tilemap — and does that change transparency/bounds logic?

**Core principles**

1. **Chunk-driven:** skip unknown chunks by `chunk_size`, don't crash.
2. **Per-frame timing:** `header.speed` is deprecated; each frame has its own duration (fallback to speed only when a frame duration is 0).
3. **Separate decode modes:** fast metadata pass first; pixel/tile decode only when needed.

## Quick Start

Turn a file into JSON — needs nothing but `node`:

```bash
# This skill's directory. Claude Code substitutes CLAUDE_SKILL_DIR (project, global
# or plugin install); other agents fall back to wherever `skills add` put it.
SKILL="${CLAUDE_SKILL_DIR}"
[ -d "$SKILL" ] || for d in .agents/skills .claude/skills ~/.agents/skills ~/.claude/skills; do
  [ -d "$d/aseprite" ] && SKILL=$d/aseprite && break
done
```

```bash
node $SKILL/scripts/aseprite-inspect.mjs path/to/sprite.aseprite --json
```

Opt into pixel-derived inference (e.g. tight bounds):

```bash
node $SKILL/scripts/aseprite-inspect.mjs path/to/sprite.aseprite --json --decode-cels
```

Add `--pretty` to indent the JSON. Decoding is capped by `--max-decompress-mib`
(default 64), so a hostile file can't make it allocate.

## What You Can Infer Reliably

Structure-only: frame count + per-frame durations + total timeline; layer hierarchy (child levels), blend modes, opacities, flags, optional UUIDs; per-frame/per-layer cel placement, linked cels, z-index, opacity; tags (ranges + direction/repeat); slices (frame-keyed rects, optional 9-slice + pivot); tilesets/tilemaps (tile dims, count, masks with ID + flips); palettes + transparency index; user data on layers/cels/tags/tilesets.

With `--decode-cels`: tight per-cel/frame bounds (non-transparent extents), empty-frame detection, sprite-sheet packing hints.

## Common Workflows

### 1) Build engine metadata (JSON)

Inspect structure-only, add `--decode-cels` if you need tight bounds. Emit `frames[]`, `layers[]`, `tags[]`, `slices[]`, normalized `frameMs[]`. For deterministic render order use z-index rules (cel header) + layer ordering. For character grounding, emit authoring anchors (slice/pivot/user-data) but validate final foot placement against exported PNG alpha bounds (via `asset-pipeline`) before locking runtime offsets.

### 2) Debug "why is this invisible?"

Check layer visibility + opacity; whether a cel is **linked** to another frame; for indexed sprites, the transparent index + background-layer semantics.

### 3) Convert slices to hitboxes/anchors

Use slice keys per frame for runtime hitboxes. Use pivot when present; otherwise infer (e.g. slice center) and record it as a fallback.

## Exporting for Phaser

`aseprite -b` → `spritesheet.png` + `spritesheet.json`, then `this.load.aseprite(key, png, json)` + `this.anims.createFromAseprite(key)`. The rules that keep it correct:

- **Hide editor-only layers with a Lua script, not `--ignore-layer`** — the CLI flag silently no-ops on some files. Match the usual suspects: `bg`, `BG`, `Background`, `Text`, `Reflection`, `Layer 1`. Recipe in `references/inference-recipes.md` §9.
- **Frame 0 is often a blank guide spacer.** Check `--decode-cels` empty-frame detection and drop it from the tag range or the export, or every clip starts with a hole.
- **`--format json-array`, `--sheet-type packed` or `rows`, NO `--trim`.** `createFromAseprite` looks frames up by index (`frames[i.toString()]`), which only the array layout gives; trim moves the feet per frame, so bottom-anchored origins drift. Keep the canvas fixed.
- **Build anims from the exported per-frame `duration`s** — `createFromAseprite` does this; hand-rolled anims must pass `frames: [{ key, frame, duration }]` with no `frameRate`.
- **Never pass `duration` to `play()`** — freezes on frame 1. Retime with `sprite.anims.timeScale`. Depth in the `phaser` skill, `references/spritesheets-and-textures.md`.

## Anti-Patterns

- **"speed" is authoritative** — it's deprecated; frame duration is the timeline. Apply the speed fallback only when a frame duration is 0.
- **Palette always exists / always 256 entries** — parse palette chunks when present; indexed sprites still need transparency-index handling.
- **Hard-failing on unknown chunks** — skip by chunk size; store a summary for debugging.
- **Decompressing everything by default** — decode only what you need; cap large sprites.
- **Treating indexed pixels as RGBA** — they're palette indices; transparency is by transparent index (header). Only map to RGBA once you've parsed a palette (and record missing-palette cases).
- **Ignoring linked cels** — you'll lose frames or infer empty bounds. Resolve links in a post-pass before pixel/bounds inference.
- **Assuming UI grouping equals render grouping** — group compositing depends on header flags + blend/opacity validity. Treat groups as structural unless building a renderer.
- **Conflating authoring vs render intent** — tags/slices/user data describe intent, pixels describe appearance; they can disagree. Output both, don't "correct" one with the other unless asked.

## References & Scripts

- `scripts/aseprite-inspect.mjs` — binary parser + JSON; optional cel/tile decode
- `references/aseprite-format-cheatsheet.md` — chunk map + gotchas
- `references/inference-recipes.md` — how to compute bounds/timing/order safely

