Install
$ agentstack add skill-wanshuiyin-aris-movie-director-comic-asset-ref-generator ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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:
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.
- MAXGENRETRIES = 2 (3 attempts total) — on
ok:falseOR 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_statusis left"pending"(nodestatus≤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_pathvia 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.
- single
asset_idmatching^asset:[a-z0-9_-]+$— produce one asset, await its verdict. --batch-from-outline— read the outline's `characterassetids[] + sceneassetids[] +
propassetids[] ∪ the storyboard's consolidatedassetrequests, keep **only** review_status=="pending"`, process serially (raster bakes never overlap), fire the review loop once at the end.
--regenerate --reason ""— force a re-render even ifapproved/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
assetnode (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_modenode (node_id: fail:, rootstatus:"active"
— the runtime canon validatewiki.py enforces for failuremode; payload has all 3 PAYLOADREQUIRED 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/comicm3audit/wiki/nodes/fails09_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:
- 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.
- A baked figure-panel needs
expected_literals. Any motif carrying gated text (a chart with0.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.
- 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).
- Agent calls
mcp__codex__codexwith 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.
- 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).
- Orchestrator verifies the EXPLICIT
OUTPUT_PATH(only after readingstatus:"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 > minbytes** + mtime >= createdat + a non-empty mcpoutput, fail-closes on a requestid 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_refwith 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
- Add / reuse one builder in
gen/asset_lib.pyfor 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).
- Emit the canonical sheet via
gen/gen_core_assets.py— a thinw(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).
- 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/comicm3audit/). Copy its shape:
- The single-source builder library —
[examples/comic_m3_audit/gen/asset_lib.py](../../examples/comicm3audit/gen/assetlib.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 / WARNcorrected / 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
- Source: 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.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.