Sprite Artist
Turns GDD entities into Phaser-ready sprite sheets and a manifest the codesmith consumes by name. Uses GPT Image 2 (gpt-image-2) — OpenAI's state-of-the-art image generation model — by default. Available via fal.ai (default provider, requires FAL_KEY) or directly through the OpenAI Images API (requires OPENAI_API_KEY).
When to use
After game-designer produces a GDD. The orchestrator passes the entities array; you produce sheets + manifest.
Two modes
| Mode |
Trigger |
Cost |
Output |
| GPT Image 2 |
FAL_KEY or OPENAI_API_KEY set, --placeholder not passed |
~$0.01-0.05 per sheet at low quality |
Vivid pixel-art sprites |
| Procedural fallback |
No key, or --placeholder flag |
Free |
Flat-color placeholder cells |
Both modes produce identical manifests, so downstream stages don't care which was used.
Manifest contract
Each sheet entry:
{
"sheet": "<absolute path>",
"relSheet": "assets/<filename>.png", // relative to project root, used in Phaser load
"rows": ["<ENTITY_ID>", ...], // top-to-bottom
"cols": ["idle","walk","attack","hurt"], // left-to-right
"cell": <pixel size of one cell>,
"bg": "magenta" | "transparent",
"textureKey": "entities-<N>" // Phaser texture key
}
The complete manifest:
{
"sprites": [<entry>, ...],
"tiles": <set by tile-artist>
}
Process
- Read GDD entities from
game-state.json.
- Decide states-per-sheet: union of
entity.states across all entities, ordered as [idle, walk, attack, hurt, run, jump, cast, block, death, victory] then alphabetical for any extras.
- Batch entities into sheets of ≤ 9 rows each.
- For each batch:
a. Image-gen mode: shell out to
~/.all-skills/sprite-sheet/scripts/generate.mjs with --bg magenta. Then run chroma-key (magenta → alpha) via sharp.
b. Procedural mode: paint a cellPx × cellPx colored cell per (row, col) with state-specific tint/silhouette.
- Append entries to
manifest.sprites.
- Write
public/assets/manifest.json (will be merged with tile-artist output later).
- Update
game-state.json assets.sprites.
GPT Image 2 mode details
- Uses GPT Image 2 (
gpt-image-2). See references/gpt-image-2.md for endpoint, request shape, and provider options.
- Default provider: fal.ai (
https://fal.run/openai/gpt-image-2). Drop-in alternative: OpenAI Images API (https://api.openai.com/v1/images/generations).
- Default quality:
low — always start here for prototyping and framework iteration. Bump to medium/high only when explicitly asked.
- Background MUST be magenta (
#FF00FF) — better chroma key fidelity than black against dark sprites.
- Image dimensions must satisfy: multiples of 16; ratio ≤ 3:1; total px ∈ [655360, 8294400]. Auto-pick
cellPx to satisfy.
- After generation, post-process: every pixel where
R>200 && G<80 && B>200 → alpha=0. Done with sharp.
Visual quality target
The procedural generator targets NES / early GBA pixel art quality — the visual bar set by Double Dragon, Shovel Knight, and Link's Awakening:
- Characters: ellipse-based heads, distinct clothing layers (tunic, belt, boots), visible arms with hands, walk cycle with ±2 px leg offset between frames.
- Outlines: 1-pixel dark border around entire sprite silhouette using the two-pass rule below.
- Cell size: 48 px (gives enough pixels for readable faces, clothing detail, accessories).
- Palette discipline: 4-8 colors per character — highlight, midtone, shadow, outline + 1-2 accent colors.
- Transparent backgrounds: sprite buffers init to
alpha=0; only drawn pixels get alpha=255. The two-pass outline rule below keeps backgrounds clean.
Two-pass outline rule (CRITICAL)
Never draw outline pixels in-place during the same buffer scan that reads opaque pixels — each newly-opaque outline pixel triggers another outline pixel on the next iteration, cascading a dark flood-fill across the entire right/bottom of the cell.
// CORRECT — collect first, draw second:
function addOutline(buf, W, x0, y0, sz, oR, oG, oB) {
const toFill = [];
for (let y = y0; y < y0 + sz; y++)
for (let x = x0; x < x0 + sz; x++) {
if (buf[(y * W + x) * 4 + 3] < 200) continue;
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
const nx = x + dx, ny = y + dy;
if (nx < x0 || nx >= x0 + sz || ny < y0 || ny >= y0 + sz) continue; // clamp to cell
if (buf[(ny * W + nx) * 4 + 3] < 200) toFill.push(nx, ny);
}
}
for (let i = 0; i < toFill.length; i += 2)
pix(buf, W, toFill[i], toFill[i + 1], oR, oG, oB);
}
Call addOutline() once per cell, after all other pixels are drawn.
Procedural mode details
- Cell size: 48 px (Phaser scales to display size via
setDisplaySize — cell size doesn't change hitboxes).
- Per (entity, state):
- Body color from entity's
color (named-color phrase → hex via lookup, falls back to gray).
- Humanoid characters: ellipse head, rectangular torso, arm + leg pairs. Idle = symmetric. Walk = ±2 px leg X offset between the two walk frames.
- Non-humanoid enemies: shape appropriate to description (blob, quadruped, flying creature).
- Eyes for player/enemy/boss/npc kinds.
- Call
addOutline() once per cell, after all pixels are drawn, using the two-pass approach above.
- Outline color: darkest shade of character's primary color (not pure black — e.g. dark brown for a tan character).
Scripts
scripts/generate_sheets.mjs <project-dir> [--placeholder] [--quality=low|medium|high]
scripts/chroma_key.mjs <png-file> — magenta → alpha post-processor (used internally and exposed for ad-hoc use).
References
references/gpt-image-2.md — GPT Image 2 endpoint, request body, constraints, content-filter notes, provider options (fal.ai default + OpenAI direct).
Dependencies
sharp — image post-processing (npm dep).
- External (optional, for full-fidelity prompt template):
~/.all-skills/sprite-sheet/ skill provides one battle-tested wrapper. The skill's GPT Image 2 mode also runs without it via the bundled direct-call script.
1---2name: sprite-artist3description: Generates pixel-art sprite sheets for a game's entities using GPT Image 2 (gpt-image-2) and emits a manifest (row=entity, col=animation state). Always defaults to low quality; post-processes magenta to alpha via sharp. Falls back to procedural placeholder cells when no API key is available. Use after game-designer has produced entities.4---56# Sprite Artist78Turns GDD entities into Phaser-ready sprite sheets and a manifest the codesmith consumes by name. Uses **GPT Image 2** (`gpt-image-2`) — OpenAI's state-of-the-art image generation model — by default. Available via fal.ai (default provider, requires `FAL_KEY`) or directly through the OpenAI Images API (requires `OPENAI_API_KEY`).910## When to use1112After `game-designer` produces a GDD. The orchestrator passes the entities array; you produce sheets + manifest.1314## Two modes1516| Mode | Trigger | Cost | Output |17|---|---|---|---|18| **GPT Image 2** | `FAL_KEY` or `OPENAI_API_KEY` set, `--placeholder` not passed | ~$0.01-0.05 per sheet at `low` quality | Vivid pixel-art sprites |19| **Procedural fallback** | No key, or `--placeholder` flag | Free | Flat-color placeholder cells |2021Both modes produce identical manifests, so downstream stages don't care which was used.2223## Manifest contract2425Each sheet entry:2627```jsonc28{29 "sheet": "<absolute path>",30 "relSheet": "assets/<filename>.png", // relative to project root, used in Phaser load31 "rows": ["<ENTITY_ID>", ...], // top-to-bottom32 "cols": ["idle","walk","attack","hurt"], // left-to-right33 "cell": <pixel size of one cell>,34 "bg": "magenta" | "transparent",35 "textureKey": "entities-<N>" // Phaser texture key36}37```3839The complete manifest:40```jsonc41{42 "sprites": [<entry>, ...],43 "tiles": <set by tile-artist>44}45```4647## Process48491. Read GDD entities from `game-state.json`.502. Decide states-per-sheet: union of `entity.states` across all entities, ordered as `[idle, walk, attack, hurt, run, jump, cast, block, death, victory]` then alphabetical for any extras.513. Batch entities into sheets of **≤ 9 rows** each.524. For each batch:53 a. Image-gen mode: shell out to `~/.all-skills/sprite-sheet/scripts/generate.mjs` with `--bg magenta`. Then run chroma-key (magenta → alpha) via sharp.54 b. Procedural mode: paint a `cellPx × cellPx` colored cell per (row, col) with state-specific tint/silhouette.555. Append entries to `manifest.sprites`.566. Write `public/assets/manifest.json` (will be merged with tile-artist output later).577. Update `game-state.json` `assets.sprites`.5859## GPT Image 2 mode details6061- Uses **GPT Image 2** (`gpt-image-2`). See `references/gpt-image-2.md` for endpoint, request shape, and provider options.62- Default provider: fal.ai (`https://fal.run/openai/gpt-image-2`). Drop-in alternative: OpenAI Images API (`https://api.openai.com/v1/images/generations`).63- **Default quality: `low`** — always start here for prototyping and framework iteration. Bump to `medium`/`high` only when explicitly asked.64- Background MUST be magenta (`#FF00FF`) — better chroma key fidelity than black against dark sprites.65- Image dimensions must satisfy: multiples of 16; ratio ≤ 3:1; total px ∈ [655360, 8294400]. Auto-pick `cellPx` to satisfy.66- After generation, post-process: every pixel where `R>200 && G<80 && B>200` → `alpha=0`. Done with `sharp`.6768## Visual quality target6970The procedural generator targets **NES / early GBA pixel art quality** — the visual bar set by Double Dragon, Shovel Knight, and Link's Awakening:71- **Characters**: ellipse-based heads, distinct clothing layers (tunic, belt, boots), visible arms with hands, walk cycle with ±2 px leg offset between frames.72- **Outlines**: 1-pixel dark border around entire sprite silhouette using the two-pass rule below.73- **Cell size**: 48 px (gives enough pixels for readable faces, clothing detail, accessories).74- **Palette discipline**: 4-8 colors per character — highlight, midtone, shadow, outline + 1-2 accent colors.75- **Transparent backgrounds**: sprite buffers init to `alpha=0`; only drawn pixels get `alpha=255`. The two-pass outline rule below keeps backgrounds clean.7677## Two-pass outline rule (CRITICAL)7879**Never** draw outline pixels in-place during the same buffer scan that reads opaque pixels — each newly-opaque outline pixel triggers another outline pixel on the next iteration, cascading a dark flood-fill across the entire right/bottom of the cell.8081```js82// CORRECT — collect first, draw second:83function addOutline(buf, W, x0, y0, sz, oR, oG, oB) {84 const toFill = [];85 for (let y = y0; y < y0 + sz; y++)86 for (let x = x0; x < x0 + sz; x++) {87 if (buf[(y * W + x) * 4 + 3] < 200) continue;88 for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {89 const nx = x + dx, ny = y + dy;90 if (nx < x0 || nx >= x0 + sz || ny < y0 || ny >= y0 + sz) continue; // clamp to cell91 if (buf[(ny * W + nx) * 4 + 3] < 200) toFill.push(nx, ny);92 }93 }94 for (let i = 0; i < toFill.length; i += 2)95 pix(buf, W, toFill[i], toFill[i + 1], oR, oG, oB);96}97```9899Call `addOutline()` once per cell, **after** all other pixels are drawn.100101## Procedural mode details102103- Cell size: **48 px** (Phaser scales to display size via `setDisplaySize` — cell size doesn't change hitboxes).104- Per (entity, state):105 - Body color from entity's `color` (named-color phrase → hex via lookup, falls back to gray).106 - Humanoid characters: ellipse head, rectangular torso, arm + leg pairs. Idle = symmetric. Walk = ±2 px leg X offset between the two walk frames.107 - Non-humanoid enemies: shape appropriate to description (blob, quadruped, flying creature).108 - Eyes for player/enemy/boss/npc kinds.109 - Call `addOutline()` once per cell, after all pixels are drawn, using the two-pass approach above.110 - Outline color: darkest shade of character's primary color (not pure black — e.g. dark brown for a tan character).111112## Scripts113114- `scripts/generate_sheets.mjs <project-dir> [--placeholder] [--quality=low|medium|high]`115- `scripts/chroma_key.mjs <png-file>` — magenta → alpha post-processor (used internally and exposed for ad-hoc use).116117## References118119- `references/gpt-image-2.md` — GPT Image 2 endpoint, request body, constraints, content-filter notes, provider options (fal.ai default + OpenAI direct).120121## Dependencies122123- `sharp` — image post-processing (npm dep).124- External (optional, for full-fidelity prompt template): `~/.all-skills/sprite-sheet/` skill provides one battle-tested wrapper. The skill's GPT Image 2 mode also runs without it via the bundled direct-call script.