Pokemon sheets are built in the SpriteCollab checkout and copied in whole: a folder per pokemon holding one layout, one binary of frames and a PNG per coat. Applies whenever writing or reviewing anything that reads a sheet, imports one, or draws a species sprite.
Scanned 9/20/2026
Install to Claude Code
npx -y skills add lxsmnsyc/overwander --skill sprite-sheet-layout --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sprite Sheet Layout?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lxsmnsyc-sprite-sheet-layout)More formats (shields.io, HTML) on the badges page.
---
name: sprite-sheet-layout
description: >
Pokemon sheets are built in the SpriteCollab checkout and copied in
whole: a folder per pokemon holding one layout, one binary of frames
and a PNG per coat. Applies whenever writing or reviewing anything
that reads a sheet, imports one, or draws a species sprite.
---
**A sheet is a bag of pictures, not a grid.** Every distinct picture sits wherever the packer put it, at whatever size its own content needs. Nothing is laid out in rows and columns, and **a clip does not own a region** — the pictures belong to the pokemon, and two animations holding the same drawing point at one copy of it.
**A pokemon is a folder**, `{region}/{species}/`, holding:
- `sheet.json` — the layout: `sheet.pictures` as `[x, y, width, height]`, an `anims` entry per animation saying how long each frame is held, a `sprites` entry per grid saying where its frames are, the coats that were drawn and who drew them.
- `frames.bin` — the frames themselves, deduplicated: one record per distinct frame, and a stream of indices pointing at them.
- `regular.png`, `shiny.png`, and `female.png` / `shiny_female.png` where a female was drawn.
**Every coat is packed to one layout.** A shiny is a second PNG over the same frames, and so is a female: swapping the image changes nothing else. `spriteImagePath(species, shiny, female)` picks the file, `spriteSheetPath` and `spriteFramesPath` the pair beside it.
**The anchors are on the frame, in the frame box's own pixels.** Shadow, body centre, head and both hands, from the collection's `-Offsets` images, and any of them may be absent (`-32768` in both slots of `frames.bin`). They may fall outside the frame: a flying pokemon's shadow is on the ground below everything drawn, and nothing clamps them.
**Read the format from the source.** `../SpriteCollab/compact/README.md` is the authority on `sheet.json` and `frames.bin`, down to the column order of the frame table; [`sprite-sheet.ts`](../../../src/canvas/sprite-sheet.ts) is this game's reader of it and nothing else.
**Two tiers of clip, and the collection names both.** The **bare minimum** six, `Idle` `Attack` `Walk` `Sleep` `Hurt` `Hop`, are what a sheet cannot be put on screen without; four more, `Charge` `Double` `Rotate` `Swing`, complete the ten a renderer may assume. They are [`MINIMUM_CAST` and `COMMON_CAST`](../../../src/data/constants/cast.ts), a cast list may end on any of the ten, and `pickCast` gives up onto two of the six. A sheet short of one of the six is unfinished art worth reporting; short of one of the other four it is drawn a little plainer.
`SpriteAnim`'s numbers are the collection's own and are **append-only**. A new animation takes the next free number; nothing already there ever moves.
## Rules
- **The clip's box is `frameWidth` × `frameHeight`.** It is the union of every frame of every direction and of every coat, it does not change while a clip plays, and **every anchor is in its coordinates**. It is what a sprite is placed, measured and hit-tested by, and what the DOM sizes its element to.
- **`sourceFrameWidth`/`sourceFrameHeight` are the cell the artist drew in.** They are authored generously, a Hop is given room for a jump twice the height anything reaches, so they are never a layout measure. They are for the shadow's width and the sparkle's spread, which are facts about the pokemon's size. A coat drawn past its own cell wins: Heracross' Attack box is a column wider than the eighty its `AnimData.xml` declares.
- **Read a frame through the description, never by arithmetic on the grid.** `frame * frameWidth` is wrong: two frames of a clip are different sizes and sit in different places, and the picture may be somewhere another clip put it.
- **A mirrored frame keeps its own place.** `flip` says the picture's pixels are reversed about the picture's own axis; `at` is still measured from the left of the box, and only a caller-requested flip turns it round.
- **Sheets are filed by region.** `public/sprites/pokemon/{region}/{species}` — `kanto` and `johto` for the first 251 and their forms, `unknown` for Missingno, the egg and the substitute. Which region a pokemon is in comes from its dex number ([`getSpeciesRegion`](../../../src/data/species/regions.ts)), never from a list beside the files; `coats.json` stays at the root and says nothing about regions.
- **Never assume a clip past the ten.** Ask the sprite, `SpeciesSpriteAnimation.has`, rather than keeping a table of which species owns what; a preference list is how a move asks for better and settles for less.
- **A clip the game has no number for does not load.** That is a gap in `SpriteAnim` rather than a bad sheet, and `pnpm import-sprites` says so when it finds one.
## Why
Cropping is most of a sheet, and sharing across clips is most of what is left. A clip's box has to hold its widest lunge, so every quieter frame rattles around inside a box drawn for one reach, and once cropped, two thirds of what remains turns out to be a drawing another clip already has. The 281 sheets that ship weigh 6.3MB, coats and all.
## Importing
```bash
pnpm import-sprites # from ../SpriteCollab
pnpm import-sprites --dry-run # say what it would copy
pnpm compact-sprites # record them, and shrink any PNG that can be
```
[`scripts/import-sprites.ts`](../../../scripts/import-sprites.ts) replaces the tree rather than writing over it, so a pokemon no longer built stops being served. It renames the collection's `{region}/{dex}/{form}` to the species id this game knows it by, skips a form there is no id for, and rewrites `coats.json` with a fresh stamp per pokemon. Without that stamp browsers go on drawing yesterday's sheet against today's description, which draws nothing at all.
Nothing is repacked on the way in. A sheet that needs repacking is repacked in the SpriteCollab checkout and imported again.
## Source art
Pokemon sprites come from **SpriteCollab**, the PMD sprite collection. The working copy is the `SpriteCollab` checkout beside this repository (`../SpriteCollab`), a fork of [PMDCollab/SpriteCollab](https://github.com/PMDCollab/SpriteCollab) at [lxsmnsyc/SpriteCollab](https://github.com/lxsmnsyc/SpriteCollab), with the upstream on the `upstream` remote.
Go there for anything about which sprites exist and what shape they are in: `sprite/{dex}/` per pokemon with its `AnimData.xml` and the three images per animation, `compact/` for what this game reads, `tracker.json` for what is drawn and by whom, `credit_names.txt` for attribution. Read the answer out of that checkout rather than guessing from what is already packed under `public/sprites`, and pull the fork before deciding a sprite is missing.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!