Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The two sprite pipelines are ordered generation flows. Tool groups contain indep
flowchart LR
subgraph A["A · atlas rows"]
direction LR
a1[prepare] --> a2["gen · gen-set"] --> a3[extract] --> a5[compose-atlas]
a1[prepare] --> a2["gen · gen-set"] --> a3[extract] --> a5[compose-atlas] --> a6[compact-atlas optional]
a5 -.-> a4["curation (optional)"]
a4 --> a5
end
Expand All @@ -75,7 +75,7 @@ flowchart LR

| Pipeline / tool group / workflow | What goes in → what comes out | Docs |
|---|---|---|
| **A · atlas rows** | one still + a list of states → `sprite-sheet-alpha.png` + `manifest.json.frame_layout`, with **Breathe** baked on idle poses | [run-contract](docs/run-contract.md) · [breathing](docs/breathing.md) |
| **A · atlas rows** | `prepare` · `gen` · `gen-set` · `extract` · `compose-atlas` · `compact-atlas` · `curation`; one still + states → runtime atlas pages + `manifest.frame_layout` | [run-contract](docs/run-contract.md) · [compact-atlas](docs/compact-atlas.md) · [breathing](docs/breathing.md) |
| **B · video → loop** | one still → per state, a seamless transparent GIF / WebP / strip, animated by Grok Imagine and cut at its true period | [video-pipeline](docs/video-pipeline.md) · [video](docs/video.md) |
| **C · utilities** | an imported image or grid sheet → clean transparent cuts; a finished atlas → a curator-ready run | [sheet-slicing](docs/sheet-slicing.md) · [curation](docs/curation.md) |
| **D · post-processing** | a finished sheet → deterministic colourways, rig layer composites, Aseprite / Phaser / Flame exports | [recolor](docs/recolor.md) · [layer-tracks](docs/layer-tracks.md) · [engine-export](docs/engine-export.md) |
Expand Down Expand Up @@ -112,6 +112,7 @@ sprite-gen prepare --out-dir <run> --character-id <id> --base-image base.png #
sprite-gen gen-set --run-dir <run> --provider codex # every state row, 4 at a time
sprite-gen extract --run-dir <run> # chroma → transparent frames
sprite-gen compose-atlas --run-dir <run> # sprite-sheet-alpha.png + manifest.json
sprite-gen compact-atlas --run-dir <run> --page-size 2048 --max-pages 4 # pages + manifest.compact.json
sprite-gen curation --run-dir <run> # (optional) pick, nudge, breathe
```

Expand Down
5 changes: 3 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: sprite-gen
version: 2.5.3
version: 2.6.0
description: "Generates images and game sprites through GPT or Grok with guided provider choices, separate saved defaults, automatic cleanup and optional curation. Handles sprite requests, ordinary image generation/editing, standalone image-to-video clips (i2v, animate this still, 그록 영상, 이매진 비디오, 스틸 움직여줘, first/last frame, reference-to-video, 영상 이어붙이기, 영상 편집, extend/edit a clip), chroma removal, animation atlases, video loops, 큐레이션뷰, image candidates, 팔레트 스왑, palette swap, recolor, rig layers, engine exports, repeating backgrounds, projected shadows, motion/contact inspection and optional scene composition from existing assets."
license: Apache-2.0
depends_on:
Expand All @@ -23,6 +23,7 @@ depends_on:
- scripts/extract_sprite_row_frames.py
- scripts/interpolate_frames.py
- scripts/compose_sprite_atlas.py
- scripts/compact_sprite_atlas.py
- scripts/preview_animation.py
- scripts/compose_selected_cycle.py
- scripts/compose_sprite_gif.py
Expand Down Expand Up @@ -86,7 +87,7 @@ front-facing observations also remain unchanged. Mirroring does not preserve lef

| Task | Entry | Contract |
|---|---|---|
| GPT image sprites | `prepare`, `gen-set --provider codex`, `extract`, compose and QA | [atlas-workflow](docs/atlas-workflow.md) |
| GPT image sprites | `prepare`, `gen-set --provider codex`, `extract`, compose, optional compact packing and QA | [atlas-workflow](docs/atlas-workflow.md) · [compact-atlas](docs/compact-atlas.md) |
| Standalone video / animate a still, pin a last frame, reference images | `video` (`--image`, `--last-frame`, `--reference`) | [video](docs/video.md) |
| Continue or edit an existing clip | `video-extend`, `video-edit` | [video](docs/video.md) |
| Grok video sprites | `video-set` | [video-pipeline](docs/video-pipeline.md) |
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ the code domains describe ownership rather than an execution order.
flowchart LR
subgraph A["A · atlas rows"]
direction LR
a1[prepare] --> a2["gen · gen-set"] --> a3[extract] --> a5[compose-atlas]
a1[prepare] --> a2["gen · gen-set"] --> a3[extract] --> a5[compose-atlas] --> a6[compact-atlas optional]
a5 -.-> a4["curation (optional)"]
a4 --> a5
end
Expand Down Expand Up @@ -36,7 +36,7 @@ flowchart LR

| Pipeline / tool group / workflow | Entry doc | Verbs |
|---|---|---|
| **A · atlas rows** — one still becomes a runtime sprite sheet | [run-contract.md](run-contract.md) | `prepare` → `gen` / `gen-set` → `extract` → `compose-atlas`; optional `curation` and recompose |
| **A · atlas rows** — one still becomes runtime atlas pages | [run-contract.md](run-contract.md) · [compact-atlas.md](compact-atlas.md) | `prepare` → `gen` / `gen-set` → `extract` → `compose-atlas` → optional `compact-atlas`; optional `curation` and recompose |
| **B · video → loop** — one still becomes transparent motion loops | [video-pipeline.md](video-pipeline.md) | `video-canvas` → `video` → `video-frames` → `video-loop`, `video-set` |
| **C · utilities** — imported images in, clean cuts out | [sheet-slicing.md](sheet-slicing.md) | `cutout`, `slice-sheet`, `unpack-atlas` |
| **D · post-processing** — finished sheets, refined | [recolor.md](recolor.md) | `recolor`, `recolor-palette`, `compose-layers`, breathing (compose), `export-pngs`, `export-aseprite` |
Expand Down
5 changes: 5 additions & 0 deletions docs/atlas-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ $SPRITE_GEN_ROOT/.venv/bin/sprite-gen prepare --out-dir /absolute/run \
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen gen-set --run-dir /absolute/run --provider codex
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen extract --run-dir /absolute/run
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen compose-atlas --run-dir /absolute/run
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen compact-atlas --run-dir /absolute/run \
--page-size 2048 --max-pages 4 --max-empty-percent 25
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen compose-gif --run-dir /absolute/run --out-dir /absolute/run/previews
$SPRITE_GEN_ROOT/.venv/bin/sprite-gen inspect --run-dir /absolute/run
```

The runtime consumes `manifest.json.frame_layout`; it does not infer a grid from alpha. AI operates only at generation. Frame cleanup and atlas assembly remain deterministic, with one writer per run and atomic publication. [architecture](architecture.md) explains these boundaries. [subject-profiles](subject-profiles.md) covers effects and sparse subjects. [breathing](breathing.md), [recolor](recolor.md) and [layer-tracks](layer-tracks.md) own optional post-processing; none is an extra default question for every sprite request.

Use [compact-atlas](compact-atlas.md) only after the last compose. It writes a separate
`manifest.compact.json`, so curation/recompose remains non-destructive.
31 changes: 31 additions & 0 deletions docs/compact-atlas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Compact atlas pages

`compact-atlas` is a deterministic post-compose tool. It trims transparent pixels
without resampling, packs the resulting rectangles into equal square pages, and writes
the page index plus logical-cell offsets into a new manifest.

```bash
sprite-gen compact-atlas --run-dir <run> \
--page-size 2048 --max-pages 4 --gutter 2 --alpha-padding 1 \
--max-empty-percent 25
```

The default policy keeps every animation clip on one Texture2DArray slice. Empty space
is measured against all allocated fixed-size slices, including gutter. If that ratio is
above `--max-empty-percent`, the report records `thresholdExceeded: true` but still keeps
clip locality.

Add `--allow-clip-split-over-threshold` to opt into a second global MaxRects pass only
when the threshold is exceeded. That pass may place frames from one clip on multiple
slices. `compact-atlas.report.json.clipsSpanningPages` lists every affected clip, so the
choice is observable rather than implicit.

Outputs are non-destructive:

- `<page-prefix>-0.png` through `<page-prefix>-N.png`, each exactly `page-size` square.
- `manifest.compact.json`, whose frame rects contain `page`, `x/y/w/h`, and
`sourceX/sourceY`.
- `compact-atlas.report.json`, including occupancy, policy, page count and split clips.

The original `sprite-sheet-alpha.png` and `manifest.json` remain compose/correction SOTs.
Installers copy the compact manifest under the runtime name only after QA.
1 change: 1 addition & 0 deletions docs/run-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ canonical files, not hidden imports.
| Extract | `extract_sprite_row_frames.py` | `raw/<state>.png` | on success: `frames/<state>/frame-N.png` (+ `.plain.png` twin on pixel-unfake runs), `frames/frames-manifest.json`; on failure: nothing in `frames/`, `extract-failure.json` instead (§6) |
| Curate (opt) | `sprite-gen curation` (`serve_curation.py`) + `curation.py` | `frames/` | `curation.json` sidecar |
| Compose | `compose_sprite_atlas.py` | `frames/` + `curation.json` | `sprite-sheet-alpha.png`, `manifest.json`, `*.report.json` |
| Compact (opt) | `compact_sprite_atlas.py` | composed atlas + `manifest.json.frame_layout` | fixed-size page PNGs, `manifest.compact.json`, `compact-atlas.report.json` |
| Engine export (opt) | `sprite-gen export-aseprite` (`sprite_gen/compose/export_aseprite.py`) | composed `manifest.json` + existing atlas | `exports/aseprite.json`, or `exports/aseprite/<state>.json` for Flame |
| Recolor (opt) | `sprite-gen recolor` / `recolor-palette` (`sprite_gen/effects/recolor.py`) | base sheet (default `sprite-sheet-alpha.png`) + recolor spec | `variants/<name>.png`, optional `variants/<name>.manifest.json`, `variants/recolor.report.json` |
| Layer bake (opt) | `sprite-gen compose-layers` (`sprite_gen/compose/compose_layers.py`) | `frames/` + `curation.json` + the request's `rig` / `layers` | `layers/<name>.png`, `layers/<name>.manifest.json`, `layers/layers.report.json` (published as one set) |
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
name = "sprite-gen"
# Release discipline: keep this package metadata version synchronized with
# SKILL.md's `version:` field in the same release commit.
version = "2.5.3"
version = "2.6.0"
description = "Component-row pipeline for clean 2D game sprites and animation atlases"
readme = "README.md"
license = "Apache-2.0"
Expand Down
17 changes: 17 additions & 0 deletions scripts/compact_sprite_atlas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Backward-compatible wrapper for sprite_gen.compose.compact_atlas."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import sprite_gen.compose.compact_atlas as _impl

globals().update({name: value for name, value in vars(_impl).items()
if name not in {"__name__", "__package__", "__loader__", "__spec__"}})


if __name__ == "__main__":
raise SystemExit(main())
5 changes: 3 additions & 2 deletions sprite_gen/_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'curation': 'curate',
'anchor': 'curate',
'compose_atlas': 'compose',
'compact_atlas': 'compose',
'compose_cycle': 'compose',
'compose_gif': 'compose',
'compose_layers': 'compose',
Expand Down Expand Up @@ -83,8 +84,8 @@
# The CLI help, the docs index and the README
# pipeline table are checked against THIS list; a verb named here must exist as a verb.
PIPELINES: list[dict[str, object]] = [
{"key": "A", "name": "atlas rows", "verbs": ["prepare", "gen", "gen-set", "extract", "compose-atlas", "curation"],
"chain": "prepare → gen (or gen-set) → extract → compose-atlas; optional curation and recompose", "doc": "docs/run-contract.md"},
{"key": "A", "name": "atlas rows", "verbs": ["prepare", "gen", "gen-set", "extract", "compose-atlas", "compact-atlas", "curation"],
"chain": "prepare → gen (or gen-set) → extract → compose-atlas → optional compact-atlas; optional curation and recompose", "doc": "docs/run-contract.md"},
{"key": "B", "name": "video → loop", "verbs": ["video-canvas", "video", "video-frames", "video-loop", "video-set"],
"chain": "video-canvas → video → video-frames → video-loop, or video-set", "doc": "docs/video-pipeline.md"},
]
Expand Down
7 changes: 6 additions & 1 deletion sprite_gen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from sprite_gen import _modules, gen
from sprite_gen.background import tile
from sprite_gen.curate import anchor
from sprite_gen.compose import compose_atlas, compose_cycle, compose_gif, compose_layers, export_aseprite, export_pngs
from sprite_gen.compose import compact_atlas, compose_atlas, compose_cycle, compose_gif, compose_layers, export_aseprite, export_pngs
from sprite_gen.qa import correction_loop, inspect, preview, score, motion
from sprite_gen.frames import cutout, extract, slice_sheet, unpack_atlas
from sprite_gen.gen import gen_set, prepare, video
Expand Down Expand Up @@ -244,6 +244,11 @@ def _add_correction_loop(p: argparse.ArgumentParser) -> None:
_add_compose_atlas,
compose_atlas.run,
),
"compact-atlas": (
"Trim and pack a composed atlas into Texture2DArray-compatible pages.",
compact_atlas.add_arguments,
compact_atlas.run,
),
"preview": ("Build motion-QA previews for a sprite-gen run.", _add_preview, preview.run),
"compose-cycle": (
"Compose a QA-approved manual frame subset into a selected cycle.",
Expand Down
Loading