Comic asset ref generator
Skill wanshuiyin/ARIS-Movie-Director/skills/comic-asset-ref-generator
Agentic, long-horizon visual generation: a fuzzy story → a cross-model-audited image-based movie. Brings ARIS's research-wiki + multi-agent debate to multimodal generation (intelligence lives in the agent; the diffusion model just renders). Image-based today, video next.
npx -y skills add wanshuiyin/ARIS-Movie-Director --skill comic-asset-ref-generatorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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 the agent mcp__codex__codex sidecar bake (Codex native image_gen — 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 asset node's output_ref (all 6 fields or it's a schema violation), versions every ref _v{NNN} with a supersedes self-edge, and runs a single-source collision gate. GENERATES ONLY — it NEVER self-locks/approves (that is the cross-model asset-review-loop's job, a different model family). Identity is BRING-YOUR-OWN; the ARIS chibi duo is only the worked example.
SKILL.md
34.2 KB, as published. Nobody here has run it
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 (which
authors per-panel content-SVGs) and feeds comic-asset-review-loop and
the comic-director spiral.
Two asset classes, two production routes — this is the load-bearing fork:
storyboard.consolidated_asset_requests
├── identity / scene / prop ──▶ RASTER ref: agent mcp__codex__codex sidecar bake, 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 the sidecar bake (Codex's native image tool), conditioned on a
labeled white-bg reference and the real identity refs, never a free prompt, never a hand-paste.
Constants
- GENERATOR (raster route) = the agent's
mcp__codex__codexsidecar bake:model: "gpt-5.5",config{ model_reasoning_effort: "xhigh", include_image_gen_tool: true },sandbox: "workspace-write", plusapproval-policy: "never"— an mcp-call argument, NOT a.bakereq.jsonfield (the sidecar payload carries no such key). This exact shape is the empirical v3.5 lesson: withoutworkspace-write+approval=never+include_image_gen_tool=true, Codex falls back to writing descriptive text (or an SVG renderer) instead of firing its native image tool.image_genis incompatible withminimaleffort; the shipped bake runsxhigh— the samegpt-5.5 + xhighsingle compat default pinned inrun_comic.get_bake_plan()(contractbakereq/v1, the digest the p0_proof cert binds to; a config-driven model/effort override is planned, not yet implemented). Honest scope: this Phase-1 raster ASSET bake is paid and runs PRE-P0 — the p0_proof cert gates the Phase-2/3 PANEL bakes only (no cert can exist beforecomic.jsoncompiles, and assets must lock first); the gate on THIS spend is the cross-modelcomic-asset-review-loop+ the single-source collision gate (P5), not P0. NB the Codex CLI reviewers elsewhere in the pipeline pin no model (they follow the local codex config — currentlygpt-5.6-sol) at effortxhigh; only the bake payload pinsgpt-5.5. The authoritative bake reply is the.bakestatus.jsonsidecar ({status, failure_kind, mcp_output, request_id}— P2r step 3) + the native PNG on disk; codex is never asked to emit a JSON-line self-report (that was a retired codex-exec-era contract). - SVG SOURCE (deterministic route) = a pure-python parametric builder living in
gen/asset_lib.py, emitted by a thingen/gen_core_assets.pywriter. The palette is pinned tocomic.jsonui_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:falseOR a missing / zero-byte / invalid PNG (file <path>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; asupersedesself-edge links the new version to the old (recordprev_sha256:<hex>+prev_version:Nintags). <refs>RESOLUTION + SUBDIR —<refs>=<project>/+movie.project.jsondirs.assets(defaultassets/); the canonical identity target resolves viamovie.project.jsonidentity_refwith fallbackassets/duo_canonical_ref_v001.png(the same resolutionrun_comic.py derive_paths()uses). Thecharacters | scenes | props | text_panelssubdir split is the RECOMMENDED layout for a new multi-asset project; the worked example predates it and keeps a FLAT layout — duo ref atassets/duo_canonical_ref_v001.png, extra cast underassets/identity/(researcher_chibi_canonical_ref_v001.png,trio_identity_sheet_v001.png). SVG sources undergen/+assets/. Placeholder key:<project>= the project dir (e.g.examples/comic_m3_audit);<refs>as above; the pickup verifier lives at the literal repo-relative pathskills/method-figure/scripts/pickup_image.py.- 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-modelcomic-asset-review-loop(a different model family). Settingreview_status:"locked"here is a contract violation. - ONE RUNNER PER PROJECT — each raster bake writes its own explicit
out_pathvia the.bakereq.json/.bakestatus.jsonsidecar seam, so there is no global-dir cross-pollination between concurrent bakes. There is no/tmp/aris_imagegen.lockin agent mode — the agent wrapper itself serializes themcp__codex__codexcalls. The real race surface is two runners on the same project colliding on the per-asset.bakereq.json/.bakestatus.jsonsidecars + the sharedout_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.
Pipeline position (the Phase-1 asset DAG — the documented contract): OUTLINE_DRAFT_VALID (the outline gate
validates narrative + continuity + safety and that every referenced asset_id is DECLARED with a complete,
generatable request — it does NOT require locked assets) → human outline approval → provisional
storyboard (structural pass; may reference draft assets) → consolidated_asset_requests → this skill (S4) +
comic-asset-review-loop (S5) generate and LOCK the assets → OUTLINE_FINAL_LOCK (cheap re-check: the locked
assets still match the approved outline) → storyboard FINAL asset-resolution validation → blueprints. The hard
locked-asset barrier sits before blueprint authoring — not at the outline gate (the old single-stage
contract deadlocked: an outline demanding locked assets that depend on a storyboard that depends on a locked
outline).
- single
asset_idmatching^asset:[a-z0-9_-]+$— produce one asset, await its verdict. --batch-from-outline <outline_id>— read the human-approved outline'scharacter_asset_ids[] + scene_asset_ids[] + prop_asset_ids[]∪ the provisional storyboard'sconsolidated_asset_requests(structural pass — the storyboard is not asset-resolved yet at this point in the DAG), keep onlyreview_status=="pending", process serially (raster bakes never overlap), fire the review loop once at the end.--regenerate <asset_id> --reason "<text>"— force a re-render even ifapproved/rejected; prefix the generation prompt with"[regen reason: ...]", bump_v{NNN}, write asupersedesself-edge.
What it reads / what it writes (wiki nodes & edges)
Per schemas/node_schema.json (node/comic/3.0):
- READS
storyboard_spec(payload.consolidated_asset_requests,payload.global_policies) andoutline_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 (schemaoneOf → 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 leavesreview_status:"pending", nodestatus:"under_review". - EDGES (
schemas/edge_schema.json,src/dst/type): on a regenerate, emit asupersedestrue self-edge with the barenode_idon BOTH endpoints (the node_id pattern^(...|asset|...):[a-z0-9_-]+$forbids@/{/}, and there is ONEasset:<slug>node per asset — no per-version node files — so any@v{N}endpoint would dangle and failcli/validate_wiki.pylines 165-167 (endpoint resolution; the node_id-pattern check is lines 113-114)):{src: asset:<id>, dst: asset:<id>, type:"supersedes", evidence:"regen v{N}->v{N+1}: <reason>; prev_sha256:<hex>; prev_version:N"}. The cross-version lineage lives in the artifact filename_v{NNN}+prev_sha256/prev_versionintags(peroutput-versioning) — NOT in the edge endpoints. (Only if you truly need a versioned snapshot node, encode the version IN the slug —asset:<slug>_v002, which matches the pattern — AND actually write that second node file so both endpoints resolve.) - FAILURE — on retry exhaustion write a
failure_modenode so the spiral routes around it, and exit non-zero. No asset_ref-layer example node ships in the repo (everyexamples/comic_m3_audit/wiki/nodes/fail_*.jsonis a downstream SHOT-level node — e.g.fail_s09_a01.jsoncarrieslayer:"panel_visual"+affected_shot_ids:["S09"]— do NOT mirror those for this upstream gate). Write this literal skeleton instead:
(root{ "node_id": "fail:<asset-slug>_image_gen_unavailable", "node_type": "failure_mode", "status": "active", "title": "asset_ref bake failed — <asset-slug>", "created_at": "<real UTC now>", "tags": ["image_gen_unavailable"], "payload": { "layer": "asset_ref", "affected_shot_ids": [], "active": true, "repair_pattern": "<tag-derived hint>" } }status:"active"is the runtime canon validate_wiki.py enforces for failure_mode; payload has all 3 PAYLOAD_REQUIRED fields —layer,affected_shot_ids(empty: this is the UPSTREAM asset gate, no shots yet),active.) 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 inasset_lib.py, so the panel's blueprint can name a realcontent_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 readingREJECT, a DDL chip readingT-16:05) must render those literals deterministically so the panel can declare ascii-tokenizableexpected_literalsfor the blind token-diff gate. Conversely, a zero-text asset (the wordless constellation) must contain zero glyphs — assert it at build time (assert "<text" not in svg). Producing a fuzzy raster of a chart breaks this contract; that is exactly why charts/clocks/stamps take the SVG route.
Procedure (numbered — an agent runs this top to bottom)
P0 · Resolve the target
Read the asset node. Assert node_type=="asset". Abort if review_status=="locked" ("immutable per asset gate") or review_status=="approved" unless --regenerate. Classify the asset by asset_kind into
its production route: character | scene | prop (with a non-deterministic appearance) → raster route (P1r);
a deterministic motif (clock/chart/stamp/mug/symbol/star-map, anything whose pixels are computable) →
SVG-source route (P1s). When in doubt: if the asset has gated literals or must be byte-identical across
instances, it is deterministic → SVG route.
P1r · RASTER route — build the bake prompt
From payload: asset_kind, name, visual_description, identity_lock.must_preserve[] (traits that MUST
appear — e.g. "blue hoodie #1D4684", "beard", "silver hair"), identity_lock.must_avoid[] (→ a
image-prompt "do not add" block — e.g. "celebrity likeness", "corporate branding", "voxel 3-D blocks"),
ref_requirements.{aspect_ratio,background,pose,isolated_reference,data_url_required}.
- Run the STYLE-BIBLE / banned-vocab filter (see below) over
visual_description; rewrite inline and record each substitution into the composed prompt. - Compose the prompt (template):
Pose defaults by kind:Render a single reference image for asset {id} ({kind}: {name}). Subject: {filtered visual_description} Identity lock — preserve exactly: {must_preserve[]} Identity lock — do not add: {must_avoid[]} Reference requirements: {aspect_ratio} canvas; pure {background} fully isolated, no shadow/halo; pose = {pose}; isolated reference (no scene around the subject). No labels / watermarks / text / captions / contact-sheet grids. Output a SINGLE coherent reference image — not a collage, not a variation grid. Treat composability (this becomes a reusable identity ref) as the primary goal.character= "three-quarter front view, neutral expression, upper-body or full-body framing";scene= "establishing wide of the empty location, no characters, neutral daytime lighting";prop= "isolated centred view, slight three-quarter angle";logo_free_symbol= "centred symbol on white, no scene". Provide the project's real identity refs as read-only condition images when the asset must match an existing cast member (never invent a face).
P2r · RASTER route — bake via the .bakereq/.bakestatus sidecar (CONDITION it, never hand-paste)
This is the same fail-closed bake seam run_comic.py / run_spiral.py use (no marker, no /tmp lock, no
newest-pickup — each bake writes its OWN explicit OUTPUT_PATH). OUTPUT_PATH is a deterministic
project-relative path (<refs>/{subdir}/{name}_v{NNN}.png — in the worked example's flat layout that is
assets/{name}_v{NNN}.png / assets/identity/{name}_v{NNN}.png); canvas guidance in the prompt = 1024x1024
(1:1 default) or 1280x720 (scene 16:9), background opaque/fully white, ONE image.
ONE agent, BOTH sidecar roles. Unlike
run_comic.py(a Python orchestrator serviced by an external agent wrapper), this skill ships no runner — the SAME agent running this SKILL plays the orchestrator (emit + verify) and the wrapper (callmcp__codex__codex, write the status). The one-liners below are transitional until the sharedcli/service_bake_requests.pybroker lands (planned — it does not exist yet); they import the canonical primitives fromskills/method-figure/scripts/pickup_image.py(the single source of truth) — never hand-assemble a.bakereq.jsonany other way.<ABS_OUT>= the absoluteOUTPUT_PATH;<ABS_PROJECT>= the absolute project dir; run from the repo root.
- Emit the request (orchestrator role). First write the composed P1r prompt body to
<ABS_OUT>.promptbody.txt, then emit.emit_bake_requestmints the per-bake uuid4-hexrequest_iditself (stamps it into the payload and RETURNS it), pre-deletes any staleout_path+ status so a prior bake can't be silently reused, and writes the sidecar atomically (.tmp → os.replace). Capture the printed id:python3 -c 'import sys, time; sys.path.insert(0, "skills/method-figure/scripts"); \ from pickup_image import build_bake_prompt, emit_bake_request; \ body = open("<ABS_OUT>.promptbody.txt").read(); \ pt = build_bake_prompt(body, "<ABS_CONDITION_PNG>", "<ABS_IDENTITY_REF or empty>", "<ABS_OUT>"); \ print(emit_bake_request("<ABS_OUT>", {"prompt_text": pt, "out_path": "<ABS_OUT>", "content_png": "<ABS_CONDITION_PNG>", "identity_ref": "<ABS_IDENTITY_REF or empty>", "model": "gpt-5.5", "config": {"model_reasoning_effort": "xhigh", "include_image_gen_tool": True}, "sandbox": "workspace-write", "cwd": "<ABS_PROJECT>", "created_at": time.time(), "min_bytes": 500000, "aspect": 1.0}))'aspect= W/H as a float (1.0for 1:1,1.7778for a 16:9 scene).build_bake_promptprepends the canonical "use your native image generation tool" header and embeds the reference + output paths LITERALLY inprompt_text(ref #1 = the labeled white-bg condition, ref #2 = the identity ref — this skill's own doctrine: never a free prompt, so ref #1 always exists). Also note the epoch you passed ascreated_at. - Bake (wrapper role). Call
mcp__codex__codexwithprompt_textverbatim as the prompt andmodel/config/sandbox/cwdexactly as in the.bakereq.json, plusapproval-policy: "never"(an mcp-call argument — NOT a bakereq field); codex writes the native PNG toOUTPUT_PATH(themcp__codex__codexschema has no-iimage param — the paths insideprompt_textare the only transport). TheconfigMUST carry bothmodel_reasoning_effort:"xhigh"andinclude_image_gen_tool:true, else codex is never handed the native image tool and no bake fires. - Write the status (wrapper role). Use the Write tool to create
<ABS_OUT>.bakestatus.json={status:"ok"|"fail", failure_kind:null|"throttle"|"other", mcp_output:"<the FULL raw mcp__codex__codex reply text, pasted verbatim>", request_id:"<the id step 1 printed, VERBATIM>"}.mcp_outputis MANDATORY on BOTH ok and fail and must be the COMPLETE reply — never a summary or a truncation (the HARD-VETO scans it forimport struct/zlib.compress/<svg/matplotlib/… traces of a hand-drawn fallback; an emptymcp_outputleaves the veto inert and fail-closes). A missing/mismatchedrequest_idis a stale/foreign bake and fail-closes. - Verify the EXPLICIT
OUTPUT_PATH(orchestrator role). Optionally re-read the status through the canonical poller (returns immediately in this single-agent flow; kept for parity with the split-role seam):
Then — only onpython3 -c 'import sys, json; sys.path.insert(0, "skills/method-figure/scripts"); \ from pickup_image import await_bake_status; print(json.dumps(await_bake_status("<ABS_OUT>", 600)))'status:"ok"— run the pickup verifier (literal repo-relative path):
which checks PNG sig + IHDR dims + size strictly >python3 skills/method-figure/scripts/pickup_image.py --out-existing --out <ABS_OUT> \ --min-bytes 500000 --aspect <W/H> --created-at <epoch from step 1> \ --request-id <the id step 1 printed> --transcript <ABS_OUT>.bakestatus.jsonmin_bytes+mtime >= created_at+ a non-emptymcp_output, fail-closes on arequest_idmismatch, and HARD-VETOESstruct/zlib/PIL/<svg/matplotlibmarkers in the transcript (a clean PNG sig NEVER overrides a fallback marker). There is no newest-pickup — pickup verifies the one explicitOUTPUT_PATHthis 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 <png>;(W,H)via PIL;DATA_URL = "data:image/png;base64,$(base64 -b 0 <png> || base64 -w 0 <png>)"(macOS-b/ GNU-w, cross-platform).data_urlis a v4 invariant — Layer-3 multi-ref composition reads it directly; a missingdata_urlsilently 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.tmpthenmv(atomic; the wiki must validate even if interrupted). Leavereview_status:"pending", nodestatus:"under_review". Append alog.mdline.
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 tocomic.jsonui_tokens at the top of the file. Encode every cross-panel reuse contract into the docstring (e.g. S11 REJECT ↔ S16 ACCEPT instantiate the SAMEverdict_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 fromasset_lib. Its docstring maps each output filename → the storyboardASSET_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 assertsassert "<text" not in svgat build time. - Record on the node:
generator_script,owner_script,file_sha256; paths project-relative. Leavereview_status:"pending".
P4 · Hand off to the review loop (NEVER self-acquit)
Single mode: trigger comic-asset-review-loop, await its verdict
(approve → loop locks it / regenerate → recursive --regenerate callback, bounded by
MAX_REVIEW_ROUNDS=3). Batch mode: fire the review loop once at the end. This skill never writes
review_status:"locked" — locking is the cross-model loop's exclusive right.
P5 · Single-source collision gate (CI / pre-handoff)
Run gen/check_asset_collisions.py (static scan of every gen_*.py for w("…") writes). Every output
filename must have exactly one generator owner. Two owners → exit 1 → fix (rename the specialized variant or
delete the dead duplicate) before any handoff. This is a deliberately-not-"skip-if-exists" guard (that would
hide the run-order bug).
EXACT gate — asset_generation_self_check (deterministic, ported from aris_movie)
This is the producer-side self-check the skill must pass before handing to the review loop. It is a set
of boolean predicates with vetoes — not a numeric rubric (the numeric visual rubric lives in
comic-asset-review-loop, the analog of the panel_gate). Verdict ∈ { generated | failed } — NO
approve/locked is ever emitted here.
- output_exists — the produced file exists and is non-zero bytes. Veto on fail.
- raster_six_field_complete — for a raster ref,
output_refhas ALL of{file_path, data_url, sha256, width, height, mime}. A partial write = schema violation. Veto. - sha256_matches_file — the recorded
sha256equalsshasum -a 256of the file on disk. Veto. - data_url_present —
data_urlis populated (v4 invariant for multi-ref composition). Veto on raster. - svg_path_project_relative — for an SVG/JSON source, the recorded path is project-relative (never an absolute machine path). Veto.
- single_source —
check_asset_collisions.pyexits 0: each output filename has exactly one generator owner. Veto. - zero_text_contract — a zero-text asset (the S22 constellation) contains no glyphs:
assert "<text" not in svg. Veto. - no_two_dialects — no motif has two visual dialects (e.g. two stamp families, two DDL renderers). The new
asset must match the already-baked instances (e.g. the stamp geometry must match the baked S15
WARN_corrected). Veto. - not_a_grid (raster) — PIL aspect-vs-canvas check (+ optional codex "is this a grid?" reply); a 2×2/contact-sheet is a failed attempt, not a pass → retry.
- banned_vocab_clear — refuse to call
image_genat all if any banned / off-bible term survives the filter.
Why no numeric
approvehere: 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 bycomic-asset-review-loopwith 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/. Copy its shape:
- The single-source builder library —
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} + aSUBMITTEDvariant), theSTAMPStable +stamp(...)(DUP / SURVIVES / REJECT / ACCEPT / WARN_corrected / SUBMITTED / AUDIT — one stamp dialect, geometry matching the baked S15),mug(...)(theML RESEARCHcup,hot|fading|cold),curve_panel(...)(thewandb · exact_parsecurve),tokyo_chip(...)(the{"city":"Tok|yo"}broken-JSON chip, multi-scale + arepaired=Truetwin), andSTARMAP_NODES / STARMAP_EDGES / STARMAP_LOOP(the single coordinate truth)- the shared
verdict_card(...)whose docstring states S11 REJECT and S16 ACCEPT instantiate the SAME card (mirror pair). Palette constants (RED/AMBER/GREEN/VOID/…) are pinned tocomic.jsonui_tokens at the top.
- the shared
- The thin canonical-sheet emitter —
examples/comic_m3_audit/gen/gen_core_assets.py: aw(name, content)writer that imports only fromasset_lib; its docstring maps every output filename → storyboardASSET_REQUEST #. It writeswiki_starmap_nodes_v1.jsonas the only truth source, then derives bothwiki_starmap_v1.svg(S16b, labeled — only the 4 gated literals are big) andconstellation_layout_v1.svg(S22, zero text) from the same coordinates, ending with the load-bearing guardassert "<text" not in svg, "constellation must contain ZERO text". It also shows the inline ownership transfer discipline: the genericwandb_exact_parse_060_071curve was deleted here becausegen_b06_blueprints.pyowns the richer two-series version (one canonical source per token). - The single-source collision gate —
examples/comic_m3_audit/gen/check_asset_collisions.py: regex\bw\(\s*["']([^"']+\.(?:svg|json|png))["']over everygen_*.py; each filename must have exactly one writer; exit 0"✓ no asset collisions — all N generator outputs are single-source"(verified clean: 26 outputs, exit 0). Deliberately not a "skip if file exists" guard.
The raster side is grounded by the same project's hand-built duo_canonical_ref_v001.png +
comic.json.identity_refs (per-character hex/beard/silhouette locks) — the artifact this skill automates
producing. (P0 reminder from the real run: a missing researcher_chibi_canonical_ref_v001.png was a flagged
P0 blocker — every cast member needs its canonical ref before the panels that use it can bake.)
Hard do / don't (earned lessons)
- DO condition the raster bake on a labeled white-bg reference + the real identity refs — and force the
native tool with
sandbox:workspace-write+approval:never+include_image_gen_tool:true, else Codex falls back to descriptive TEXT instead of firingimage_gen. - DO build every clock / chart / stamp / mug / star-map as ONE parametric SVG in
asset_lib.py— never bake deterministic content as a fuzzy image and hope the digits land. The "ref" for a motif IS its single-source SVG. - DO populate
data_urland all 6output_reffields atomically (.tmp → mv); a partial write is illegal. - DO version monotonically (
_v{NNN}), keep the prior artifact on disk, and write asupersedesself-edge on every regenerate (prev_sha256/prev_versionintags). - DO run
check_asset_collisions.pybefore any handoff — one canonical owner per token, no two dialects. - DON'T hand-paste a missing trait onto a finished bake (reads as pasted/fake) — re-condition and re-bake.
- DON'T ever set
review_status:"locked"/approvedhere — the executor never self-acquits; locking is the cross-modelcomic-asset-review-loop's exclusive right. - DON'T invent an asset id — if the outline didn't declare it, the operator adds it at the outline layer.
- DON'T run two runners on the same project at once. Each bake writes its own explicit
out_pathvia the.bakereq/.bakestatussidecar, so there is no global-dir cross-pollination and no/tmp/aris_imagegen.lockin agent mode (the agent wrapper serializes themcp__codex__codexcalls) — but two runners on one project still collide on the per-asset sidecars + sharedout_path, so keep one runner per project. (The cross-pollinating global generated-images dir is a hazard of the legacy exec path only.) - DON'T trigger the review loop on an empty/failed file — write a
failure_modenode + exit non-zero so the spiral routes around it.
Protocols (governance contracts this skill honors)
output-versioning— refs are versioned_v{NNN}monotonically; the prior artifact stays on disk and asupersedesself-edge records the lineage (prev_sha256/prev_version).reviewer-routing— the raster BAKE payload pinsgpt-5.5+xhigh(the single compat default inrun_comic.get_bake_plan(), contractbakereq/v1; a config-driven override is planned, not yet implemented), while the pipeline's Codex CLI reviewers pin no model (they follow the local codex config — currentlygpt-5.6-sol) atxhigh; the visual judging is delegated tocomic-asset-review-loopon a different model family — never downgrade the tier.artifact-integrity— the model that produces an asset does NOT judge whether it's good; this skill emits onlygenerated | failed(well-formed + single-source), and the cross-model review loop owns acquittal — the executor never self-acquits.