# Run Contract — pipeline stages · run-dir folder tree · curation-view display (SSoT) > Owns: The atlas pipeline's normative contract: stages, a flag against the file that holds the same key (every verb), the run-dir folder tree, curation-view display, atomic extract, concurrency scope · Index: [docs/README.md](README.md) > Status: **contract** (normative). This doc is the single source of truth for > three things every sprite-gen run must satisfy so that **any agent who serves a > view gets the same experience** — the base reference row, the per-state > generation-material chips, the pixel grid, and the original-quality toggle. > > Precedence (no overlap, so no contradiction): > - [`user-workflow.md`](user-workflow.md) owns the conversation; [`atlas-workflow.md`](atlas-workflow.md) owns row execution. > - **This doc** owns the **structural** contract — the stage I/O table, the run-dir > folder tree, the curation-view display payload, and the import-run source rule. > These are the parts the scripts enforce. > - [`architecture.md`](architecture.md) explains **how** the code realizes both; it is > always description, never contract. > > If the three ever disagree: behavior → the workflow docs win; structure/display → this > doc wins; architecture.md is the bug. ## Contents - [1. Pipeline stages](#1-pipeline-stages) - [2. Run-dir folder contract](#2-run-dir-folder-contract) - [2-b-2. Reading a run never writes to it](#2-b-2-reading-a-run-never-writes-to-it) - [2-b-3. A run never writes over, moves or deletes its inputs](#2-b-3-a-run-never-writes-over-moves-or-deletes-its-inputs) - [2-c. External consumers install from `curated/`, never from `frames/`](#2-c-external-consumers-install-from-curated-never-from-frames) - [3. Curation-view display contract](#3-curation-view-display-contract) - [4. Import-run source rule (`--pngs-dir`)](#4-import-run-source-rule---pngs-dir) - [5. Conformance status](#5-conformance-status) - [6. Failed extract is atomic — no partial generation in `frames/`](#6-failed-extract-is-atomic-no-partial-generation-in-frames) - [7. Guarantee boundary — atomicity & concurrency (honest scope)](#7-guarantee-boundary-atomicity-concurrency-honest-scope) - [Related](#related) ## 1. Pipeline stages One image-gen call **per state row** is the only AI step; every stage after it is deterministic (same input → same output). Each script does one job and reads/writes canonical files, not hidden imports. | Stage | Script | Input | Output | |---|---|---|---| | Prepare | `prepare_sprite_run.py` | base image + request flags/JSON | `sprite-request.json`, per-state layout guide, per-state prompt, empty `raw/`+`frames/` | | Generate | `sprite-gen gen` (`generate_sprite_image.py`) | `prompts/.txt` + refs | verified `raw/.png` strip + audit raw/report | | Extract | `extract_sprite_row_frames.py` | `raw/.png` | on success: `frames//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` | | Engine export (opt) | `sprite-gen export-aseprite` (`sprite_gen/compose/export_aseprite.py`) | composed `manifest.json` + existing atlas | `exports/aseprite.json`, or `exports/aseprite/.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/.png`, optional `variants/.manifest.json`, `variants/recolor.report.json` | | QA | `preview_animation.py` | `frames/` | `qa/-contact.png`, `qa/.gif` | | Inspect | `inspect_sprite_run.py` | `sprite-request.json`, `raw/` or `frames/` | `sprite-inspect.report.json` | | Score | `score_sprite_run.py` | `sprite-inspect.report.json` | `sprite-score.report.json`, correction hints | | Correction loop | `run_correction_loop.py` | run dir + optional provider command | `correction-loop.report.json`, per-attempt inspect/score/hints | | GIF export | `compose_sprite_gif.py`, `gif_utils.py` | selected frames | clean transparent GIF into `exports/` | | Selected cycle | `compose_selected_cycle.py` | `curation.json` / `--frames` | selected-cycle manifest + QA | | Inverse / import | `unpack_atlas_run.py` | finished atlas **or** `--pngs-dir` | curator-ready run dir (§4) | | Export stills | `export_curated_pngs.py` | curated `frames/` | named PNGs under `curated/` | | Chroma guard | `scripts/dev/check_visible_magenta.py` | screenshot | leakage warning | **A flag and the file that holds the same key.** A key given both as a flag and in the file a verb reads with its flags — `prepare --request` / `--request-json`, `pixel-draw --spec` — takes the flag, and one stderr line names every flag the file also set: `[prepare] --style, --cell-size given both as a flag and in --request-json: the flag wins`. A value the file holds is never replaced in silence, and no verb answers this question another way (`gen.base.flags_over_spec`). For `prepare` a flag replaces a cell measure however the request spelled it (`--cell-size`, `--cell-width` and `--cell-height` the width and height whether the request said `size`, `width` or `cell_width`; `--safe-margin` both margins). The happy path is `prepare -> gen -> extract -> compose`, followed by delivery and an optional curation view. Curation is opened only when requested or enabled in saved defaults; edits are composed again for export. Stage internals (chroma removal, connected components, pixel-unfake path, the `.sprite-gen.lock` single-writer rule, the inspect/score/loop split) are described in [`architecture.md`](architecture.md) §2, §6 — this table is the contract, that doc is the explanation. ## 2. Run-dir folder contract **One worker owns exactly one character folder.** This is the canonical tree — do not restate it elsewhere; point here. ```text /assets/generated/sprites// sprite-request.json # numeric SSoT (cell, chroma, states, fit) — every stage reads this base-source. # identity truth; drives the view's base reference row (§3) references/layout-guides/.png # per-state layout guide (motion only) references/anchors/-anchor-x8.png # DERIVED CACHE: the curated direction-anchor frame, baked # ×8 NEAREST for row generation. `sprite-gen anchor` # (SSoT sprite_gen/curate/anchor.py) rewrites it on demand — # never hand-edit it and never treat it as truth. references/imported// # imported-run generation material → chips (§4); real runs use raw/ anchors instead prompts/.txt # generated row prompt (frame count, safe margin, anchor lock) raw/.png # one horizontal image-gen strip per state (the only AI output) frames//frame-N.png # extracted transparent cells — PRE-CURATION. # **Not the deliverable.** These are the extractor's own # output; the human's picks, pixel edits, transforms, # deletions and clones live in `curation.json` and are # applied downstream. An app that copies from here # silently ships the un-edited image (see §2-c). # ── 파일 택소노미 (layout: taxonomy/v1 — 신규 런 기본, maintainer 확정 2026-07-14) ── # 방향 계약(directions) 런은 위 두 경로가 방향/자세 폴더로 나뉜다. 상태 ID 는 그대로 # _, 파일 경로만 분리. 리졸버 SSoT = sprite_gen/spec/layout.py. # 이미 추출된 프레임을 읽는 소비자는 frames-manifest 의 row.files 경로를 따른다 # (row_frame_rel) — 패턴 조립 금지. layout 필드 없는 legacy 런은 flat 유지. # 프로젝트별 정비 방식이 지침으로 오면 지침이 우선한다. raw//.png # 택소노미: 생성 스트립 raw//.takes/