# Comic Asset Ref Generator

> Phase-1 (S4) of a comic movie — PRODUCE the canonical reusable references the whole spiral conditions on. Per asset it bakes ONE canonical 1:1 white-bg identity ref via codex gpt-image-2 (conditioned, never hand-pasted) OR, for a deterministic motif (clock/chart/stamp/star-map), emits a single-source parametric SVG from a python generator (asset_lib.py). Hashes + base64-encodes the bake into the…

- **Type:** Skill
- **Install:** `agentstack add skill-wanshuiyin-aris-movie-director-comic-asset-ref-generator`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [wanshuiyin](https://agentstack.voostack.com/s/wanshuiyin)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [wanshuiyin](https://github.com/wanshuiyin)
- **Source:** https://github.com/wanshuiyin/ARIS-Movie-Director/tree/main/skills/comic-asset-ref-generator
- **Website:** https://wanshuiyin.github.io/ARIS-Movie-Director/comic/

## Install

```sh
agentstack add skill-wanshuiyin-aris-movie-director-comic-asset-ref-generator
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# comic-asset-ref-generator — the Reference Producer (Phase 1 · S4)

The **missing Layer-2 of comic-author**: take the storyboard's consolidated `ASSET_REQUESTS` and produce, for
**every** declared asset, the one canonical artifact that **every downstream panel bake will condition on** —
so the film never grows two visual dialects and every panel of the same character/prop/motif reads as the same
thing. This is precisely the producer of `comic.json`'s `identity_refs` (e.g. `duo_canonical_ref_v001.png` +
per-character locks like `executor.hoodie #1D4684` / `reviewer.beard true`) that the proven comic created **by
hand**. It is the upstream sibling of [`comic-blueprint-author`](../comic-blueprint-author/SKILL.md) (which
authors per-panel content-SVGs) and feeds [`comic-asset-review-loop`](../comic-asset-review-loop/SKILL.md) and
the [`comic-director`](../comic-director/SKILL.md) spiral.

Two asset classes, two production routes — **this is the load-bearing fork**:

```text
storyboard.consolidated_asset_requests
   ├── identity / scene / prop  ──▶  RASTER ref:  codex gpt-image-2, CONDITIONED on a labeled white-bg
   │      (a face, a hoodie,           condition + real identity refs → 1:1 (or 16:9 scene) PNG
   │       an empty room)              → output_ref{file_path,data_url,sha256,width,height,mime}  (ALL 6)
   │
   └── deterministic motif      ──▶  SVG SOURCE:  ONE parametric builder in asset_lib.py
          (clock / chart / stamp        (ddl_chip / stamp / mug / curve_panel / tokyo_chip / starmap …)
           / mug / star-map)            the "ref" IS the single-source SVG — NOT an image bake
                                        → records generator_script, owner_script, file_sha256

   both routes → review_status:"pending"  (NEVER "locked" here)  → collision gate → asset-review-loop
```

The battle lesson, landed as the fork above: a clock, a chart, a verdict stamp, a star-map is **deterministic
content** — you do NOT bake it as a fuzzy image and hope the digits land; you build it once from a python
generator (`asset_lib.py`) so all 18 instances of the DDL timeline, all 7 verdict stamps, both the labeled and
the wordless star-map render from the **same coordinates** and never diverge. An identity (a face, a costume) is
**not** deterministic — that you bake once via gpt-image-2, *conditioned* on a labeled white-bg reference and
the real identity refs, never a free prompt, never a hand-paste.

## Constants
- **GENERATOR** (raster route) = `mcp__codex__codex`, `model: "gpt-5.5"`, `model_reasoning_effort: "high"`,
  **`sandbox: "workspace-write"`**, `approval-policy: "never"`, `config{ include_image_gen_tool: true }`. This
  exact shape is the **empirical v3.5 lesson**: without `workspace-write` + `approval=never` +
  `include_image_gen_tool=true`, Codex falls back to writing *descriptive text* (or an SVG renderer) instead of
  firing `image_gen`. `image_gen` is **incompatible with `minimal`** effort; `high` is the upper bound. Codex
  must reply exactly **one JSON line**: `{"ok":true,"path":...,"model":"gpt-image-2","width":...,"height":...,"bytes":...}`
  or `{"ok":false,"error":...}`.
- **SVG SOURCE** (deterministic route) = a pure-python parametric builder living in `gen/asset_lib.py`, emitted
  by a thin `gen/gen_core_assets.py` writer. The palette is **pinned to `comic.json` ui_tokens** — never
  re-typed per asset. This route calls **no image model** and spends **zero credits**.
- **MAX_GEN_RETRIES** = 2 (3 attempts total) — on `ok:false` OR a missing / zero-byte / invalid PNG
  (`file ` check). Append `"[retry {n}: previous failure was: {reason}]"` to the prompt each retry.
- **VERSIONING** = monotonic 3-digit `_v{NNN}` (`duo_canonical_ref_v001.png`, `ddl_widget_template_v1.svg`).
  Version **only grows**; the prior artifact **stays on disk** for the audit cascade; a `supersedes` self-edge
  links the new version to the old (record `prev_sha256:` + `prev_version:N` in `tags`).
- **SUBDIR** by `asset_kind`: `characters | scenes | props | text_panels` (raster) under `/`; SVG sources
  under `gen/` + `assets/`.
- **NEVER LOCK** — `review_status` is left `"pending"` (node `status` ≤ `under_review`). The executor that
  **produces** an asset is **forbidden** from approving it; locking belongs to the cross-model
  `comic-asset-review-loop` (a different model family). Setting `review_status:"locked"` here is a contract
  violation.
- **ONE RUNNER PER PROJECT** — each raster bake writes its **own explicit `out_path`** via the
  `.bakereq.json`/`.bakestatus.json` sidecar seam, so there is **no global-dir cross-pollination** between
  concurrent bakes. There is **no `/tmp/aris_imagegen.lock`** in agent mode — the agent wrapper itself
  serializes the `mcp__codex__codex` calls. The real race surface is two runners on the **same project**
  colliding on the per-asset `.bakereq.json`/`.bakestatus.json` sidecars + the shared `out_path`, so keep
  **one runner per project**. (The global generated-images dir + newest-after-marker pickup that could
  cross-pollinate is a hazard of the **legacy exec path ONLY**, retired for real bakes.)

## Input contract (3 modes)
This skill **never invents an asset id**. If the outline/storyboard did not declare it, the operator adds it at
the outline layer (`comic-outline-creator`) — not here.

1. **single** `asset_id` matching `^asset:[a-z0-9_-]+$` — produce one asset, await its verdict.
2. **`--batch-from-outline `** — read the outline's `character_asset_ids[] + scene_asset_ids[] +
   prop_asset_ids[]` ∪ the storyboard's `consolidated_asset_requests`, keep **only** `review_status=="pending"`,
   process **serially** (raster bakes never overlap), fire the review loop **once** at the end.
3. **`--regenerate  --reason ""`** — force a re-render even if `approved`/`rejected`; prefix the
   generation prompt with `"[regen reason: ...]"`, bump `_v{NNN}`, write a `supersedes` self-edge.

## What it reads / what it writes (wiki nodes & edges)
Per [`schemas/node_schema.json`](../../schemas/node_schema.json) (`node/comic/3.0`):

- **READS** `storyboard_spec` (`payload.consolidated_asset_requests`, `payload.global_policies`) and
  `outline_spec` (`payload.character_asset_ids / scene_asset_ids / prop_asset_ids`, `payload.global_style_bible`).
- **WRITES / MUTATES** the **`asset`** node (`node_id: ^asset:[a-z0-9_-]+$`, `node_type:"asset"`). Required
  payload fields (schema `oneOf → asset`): `asset_kind`, `name`, `visual_description`, `identity_lock`,
  `ref_requirements`, `review_status`, `version`. This skill additionally writes the **produced** ref onto the
  node (see the two contracts below) and leaves `review_status:"pending"`, node `status:"under_review"`.
- **EDGES** ([`schemas/edge_schema.json`](../../schemas/edge_schema.json), `src/dst/type`): on a regenerate,
  emit a `supersedes` **true self-edge** with the **bare** `node_id` on BOTH endpoints (the node_id pattern
  `^(...|asset|...):[a-z0-9_-]+$` forbids `@`/`{`/`}`, and there is ONE `asset:` node per asset — no
  per-version node files — so any `@v{N}` endpoint would dangle and fail `cli/validate_wiki.py` line 154-156):
  `{src: asset:, dst: asset:, type:"supersedes", evidence:"regen v{N}->v{N+1}: ; prev_sha256:; prev_version:N"}`.
  The cross-version lineage lives in the artifact filename `_v{NNN}` + `prev_sha256`/`prev_version` in `tags`
  (per [`output-versioning`](../../protocols/output-versioning.md)) — NOT in the edge endpoints. (Only if you
  truly need a versioned snapshot node, encode the version IN the slug — `asset:_v002`, which matches the
  pattern — AND actually write that second node file so both endpoints resolve.)
- **FAILURE** — on retry exhaustion write a `failure_mode` node (`node_id: fail:`, root **`status:"active"`**
  — the runtime canon validate_wiki.py enforces for failure_mode; payload has **all 3** PAYLOAD_REQUIRED fields
  `layer:"asset_ref"`, `affected_shot_ids:[]` (empty — this is the UPSTREAM asset gate, no shots yet; or the
  downstream panel/asset ids if known), `active:true`; plus `repair_pattern:""`,
  `tags:["image_gen_unavailable"]`) so the spiral routes around it, and exit non-zero. Mirror the live shape
  [`examples/comic_m3_audit/wiki/nodes/fail_s09_a01.json`](../../examples/comic_m3_audit/wiki/nodes/fail_s09_a01.json).
  Do **not** trigger the review loop on an empty file.

## The two fail-closed engine contracts (honor these or the director refuses to run)
These are the same fail-closed invariants the spiral enforces — produce assets so they **cannot** violate them:

1. **Every panel needs a `content_svg`.** Every deterministic motif a panel will use MUST be produced as a
   single-source SVG in `asset_lib.py`, so the panel's blueprint can name a real `content_svg`. If a recurring
   motif has no parametric source here, the downstream blueprint has nothing to bake-condition on and the
   director's render step fails closed. **One parametric builder per recurring motif — no exceptions.**
2. **A baked figure-panel needs `expected_literals`.** Any motif carrying gated text (a chart with `0.71→0.66`,
   a stamp reading `REJECT`, a DDL chip reading `T-16:05`) must render those literals **deterministically** so
   the panel can declare ascii-tokenizable `expected_literals` for the blind token-diff gate. Conversely, a
   **zero-text** asset (the wordless constellation) must contain **zero glyphs** — assert it at build time
   (`assert "/{subdir}/{name}_v{NNN}.png`); `size` = `1024x1024` (1:1 default) or `1280x720`
(scene 16:9), background opaque/fully white, `n:1`.

1. **Orchestrator writes** `.bakereq.json` =
   `{prompt_text, content_png, identity_ref, out_path:OUTPUT_PATH, model:"gpt-5.5", config:{model_reasoning_effort:"xhigh", include_image_gen_tool:true}, sandbox:"workspace-write", cwd, created_at, min_bytes:500000, aspect, request_id:""}`
   (atomic `.tmp → mv`), pre-deleting any stale `out_path` + status so a prior bake can't be silently reused.
   `request_id` is a per-bake uuid4-hex nonce the orchestrator mints (see step 3).
2. **Agent calls** `mcp__codex__codex` with **exactly that** request (the reference + output paths are LITERAL in
   `prompt_text` — the `mcp__codex__codex` schema has no `-i` image param); codex writes the native PNG to
   `OUTPUT_PATH`. The `config` MUST carry **both** `model_reasoning_effort:"xhigh"` and
   `include_image_gen_tool:true`, else the wrapper won't hand codex the native image tool and no bake fires.
3. **Agent writes** `.bakestatus.json` =
   `{status:"ok"|"fail", failure_kind, mcp_output:"", request_id:""}`.
   `mcp_output` is **MANDATORY on BOTH ok and fail** (the HARD-VETO scans it for `import struct`/`zlib.compress`/
   ``/`matplotlib`/… traces of a hand-drawn fallback); `request_id` is copied **VERBATIM** from the
   `.bakereq.json` (a missing/mismatched id is a stale/foreign bake and fail-closes).
4. **Orchestrator verifies the EXPLICIT `OUTPUT_PATH`** (only after reading `status:"ok"`) via
   ```
   python3 /scripts/pickup_image.py --out-existing --out OUTPUT_PATH \
     --min-bytes 500000 --aspect  --created-at  \
     --request-id  --transcript .bakestatus.json
   ```
   which checks PNG sig + IHDR dims + size **strictly > `min_bytes`** + `mtime >= created_at` + a non-empty
   `mcp_output`, fail-closes on a `request_id` mismatch, and **HARD-VETOES** `struct`/`zlib`/PIL/``/
   `matplotlib` markers in the transcript (a clean PNG sig NEVER overrides a fallback marker). **There is no
   newest-pickup** — pickup verifies the one explicit `OUTPUT_PATH` this bake wrote.

**RETRY** per `MAX_GEN_RETRIES`. After exhaustion: keep the best valid PNG if any, else write the `failure_mode`
node + exit non-zero (do **not** call the review loop on an empty file). **Never** patch a failed bake by
hand-pasting the missing trait — re-condition and re-bake.

> **Grid guard.** A 2×2 / contact-sheet / variation-grid output is a **failed attempt** → retry (the
> "single coherent image, not a grid" line is the primary mitigation; a PIL aspect-vs-canvas check + an optional
> codex "is this a grid?" sanity reply is the backstop).

### P3r · RASTER route — hash, encode, update (atomic), honor the 6-field contract
- `SHA256 = shasum -a 256 `; `(W,H)` via PIL; `DATA_URL = "data:image/png;base64,$(base64 -b 0  || base64 -w 0 )"`
  (macOS `-b` / GNU `-w`, cross-platform). `data_url` is a **v4 invariant** — Layer-3 multi-ref composition
  reads it directly; a missing `data_url` silently breaks downstream.
- Mutate the node `output_ref` with **ALL SIX** fields `{file_path, data_url, sha256, width, height, mime}`.
  **A partial write is a schema violation — reject it.** Write `.tmp` then `mv` (atomic; the wiki must validate
  even if interrupted). Leave `review_status:"pending"`, node `status:"under_review"`. Append a `log.md` line.

### P1s–P3s · SVG-SOURCE route — one parametric builder per motif
1. **Add / reuse one builder in `gen/asset_lib.py`** for the motif (`ddl_chip`, `stamp`, `mug`, `curve_panel`,
   `tokyo_chip`, `mini_stamp_glyph`, `verdict_card`, the star-map coord tables). Palette pinned to `comic.json`
   ui_tokens at the top of the file. Encode every cross-panel reuse contract into the **docstring** (e.g. *S11
   REJECT ↔ S16 ACCEPT instantiate the SAME `verdict_card` — mirror pair*; *the star-map JSON is the single
   coordinate truth for S16b labeled + S22 wordless*).
2. **Emit the canonical sheet via `gen/gen_core_assets.py`** — a thin `w(name, content)` writer that imports
   **only** from `asset_lib`. Its docstring maps each output filename → the storyboard `ASSET_REQUEST #`.
   Enforce single-source **inline**: if a specialized generator (e.g. `gen_b06`) takes ownership of a richer
   variant, **delete** the generic duplicate here (don't keep two).
3. **Coordinate-truth motifs** (the star-map): write the **JSON** (`wiki_starmap_nodes_v1.json`) as the *only*
   truth source; both the labeled SVG (S16b) and the wordless SVG (S22) **derive from it programmatically** —
   `禁目测` / never re-layout by eye. The zero-text twin asserts `assert " **Why no numeric `approve` here:** the executor that *produced* the asset is not allowed to *judge* it
> (cross-model independence). This gate only proves the artifact is *well-formed and single-source*; whether it
> is *good* (identity fidelity, on-bible look) is decided by `comic-asset-review-loop` with a different model
> family.

## STYLE-BIBLE / banned-vocab filter (mandatory before any raster bake)
The aris_movie source bans camera/lens vocab that conflicts with downstream synth and silently regresses
fidelity (`8K, 4K, photorealistic, cinematic, 50mm, dolly, push-in, bokeh, rim light, masterpiece, trending on
artstation, close-up, wide shot, …`). **For the comic, this filter reads `ART_BIBLE.md`** and additionally
enforces the bible's own terms: the **two-world warm-real / dark-cyber palette** (warm = Edison/wood/window;
digital-ARIS = `dark_navy_void #0A0E27`), the **voxel ban** (`禁止 voxel 立体块` — pixel-flat only), and the
**per-character hex/feature locks** (executor blue hoodie `#1D4684` + brown hair + NO beard; reviewer green
hoodie `#30582D` + near-black hair + beard). A surviving banned/off-bible term ⟹ **refuse to call `image_gen`**.
Identity is BRING-YOUR-OWN: swap these locks for whatever the project's `ART_BIBLE.md` + `identity_lock`
declare; the ARIS chibi duo is only the worked example.

## Worked example (copy this exact pattern)
The proven comic is [`examples/comic_m3_audit/`](../../examples/comic_m3_audit/). Copy its shape:

- **The single-source builder library** —
  [`examples/comic_m3_audit/gen/asset_lib.py`](../../examples/comic_m3_audit/gen/asset_lib.py): **one
  parameterized builder per recurring token** — `ddl_chip(x,y,t,state,skin,…)` (the **sole** renderer for the
  whole 18-instance DDL timeline, 3 skins × {amber,red,green} + a `SUBMITTED` variant), the `STAMPS` table +
  `stamp(...)` (DUP / SURVIVES / REJECT / ACCEPT / WARN_corrected / SUBMITTED / AUDIT — **one** stamp di

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [wanshuiyin](https://github.com/wanshuiyin)
- **Source:** [wanshuiyin/ARIS-Movie-Director](https://github.com/wanshuiyin/ARIS-Movie-Director)
- **License:** MIT
- **Homepage:** https://wanshuiyin.github.io/ARIS-Movie-Director/comic/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-wanshuiyin-aris-movie-director-comic-asset-ref-generator
- Seller: https://agentstack.voostack.com/s/wanshuiyin
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
