# Comic Panel Prompt Builder

> Phase-1 step of comic-author — the DETERMINISTIC compiler (搬运工原則) that turns ONE gate-approved panel_spec + its status:locked blueprint into the EXACT fixed-section bake string for the spiral engine via the shipped scripts/build_prompt.py (+ the canonical scripts/_validate.py vetoes). No Codex call in the happy path; it only emits, the engine bakes. Composes style_prefix + condition_string, injec…

- **Type:** Skill
- **Install:** `agentstack add skill-wanshuiyin-aris-movie-director-comic-panel-prompt-builder`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [wanshuiyin](https://agentstack.voostack.com/s/wanshuiyin)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **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-panel-prompt-builder
- **Website:** https://wanshuiyin.github.io/ARIS-Movie-Director/comic/

## Install

```sh
agentstack add skill-wanshuiyin-aris-movie-director-comic-panel-prompt-builder
```

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

## About

# comic-panel-prompt-builder — the Deterministic Bake Compiler (搬运工原則)

The **last authoring step before a pixel ever gets baked**. Given ONE upstream-approved `panel_spec` and its
`status: locked` `blueprint`, this skill COMPILES — it does not brainstorm — the *exact* message
[`comic-director`](../comic-director/SKILL.md) (Phase 2/3) will hand to `codex image_gen`, plus the locked
identity-ref list. It is the comic twin of the framework's `shot-prompt-builder`: a pure Layer-3 transform
with **no Codex call in the happy path** — it only emits a `prompt_bundle`; the spiral engine submits it.
Everything the generation backend ever sees passes through here, so this is where the **搬运工原則** is
enforced at emit time: the backend (Codex/gpt-image-2) owns optics; `ART_BIBLE.md` owns style; **no camera /
lens / lighting / "8K hyperrealistic cinematic" padding leaks into the bake**, and **no bubble text gets
baked** unless the panel is explicitly `text_mode: "baked"`. The author (storyboard/blueprint layers) decides
*what*; this skill decides *nothing* — it transcribes a locked spec into a backend-legal string and FAILS
(it never papers over) when the spec is malformed.

```text
  panel_spec (status:locked) ─▶ ① RESOLVE  — load panel_spec + its blueprint + ART_BIBLE style_prefix
        + blueprint (status:locked)    enforce 4 prerequisites (node_type, blueprint status:locked, REAL refs, style_prefix resolves)
                              ▼
                        ② COMPOSE  — fixed-section condition_string (order is load-bearing, do NOT refactor)
                              ▼   style_prefix + [BAKED DIALOGUE only if text_mode==baked] + SCENE + FIXED ELEMENTS
                              ▼   + ALLOWED CHANGE vs prior panel + FORBIDDEN + SCREEN-TEXT WHITELIST(expected_literals)
                              ▼
                        ②·5 INJECT (retry only) — active failure_mode positive-invariants to the FRONT
                              ▼
                        ③ VALIDATE  — banned-vocab + length + no-baked-bubbles + real-refs on the COMPOSED string
                              ▼          (the hard gate — ANY hit = fail-closed, push the fix UPSTREAM)
                              ▼
                        ④ EMIT  — write the prompt_bundle node; route html_bubbles[] to the JSON viewer ONLY
                              ▼
                   pass? ─ no ─▶ exit 4 (banned/length) | exit 7 (failure_mode trigger still fires) | exit 3 (prereq)
                              │ yes
                              ▼
                        ⑤ stdout one-line JSON {prompt_bundle_id, panel_id, source_blueprint_id}
```

## Role
You are a **fail-closed transcriber**, not an author. You take a `panel_spec` that an upstream layer already
gate-approved and its `blueprint` that the cross-layer gate already flipped to `status: locked`, and you
produce the single exact `composed_prompt` (style_prefix + a fixed-section condition string) + the locked
identity-ref paths that the spiral engine will bake — together with the deterministic proof (a clean
`banned_vocab_scan`) that the message is backend-legal. You **never** invent a scene element, a number, a cast
member, or a style adjective; you **never** silently fix a malformed spec (you FAIL and point upstream); and
you **never** call Codex on the happy path (a Codex call only ever appears as a reported upstream bug). The
cross-model *judgement* of the baked pixels is not yours — that lives downstream in `comic-director`'s
`panel_gate`; keeping you deterministic is what stops the executor from self-acquitting.

## Node — what this skill reads / writes (`schemas/node_schema.json`)
- **Reads `panel_spec`** (node prefix `panel:`) — payload fields used: `source_storyboard_id`, `page_id`,
  `panel_id`, `sequence_index`, `page_type`, `world`, `asset_ids[]`, `text_mode`, `expected_literals[]`,
  `content_blueprint`, `bubbles[]`, `side_narration`, `motifs`. The `panel_spec` must be its layer's
  gate-approved output (`status: locked`), not a draft.
- **Reads the upstream `blueprint`** (node prefix `blueprint:`, found via the panel's `content_blueprint`) —
  payload fields used (the schema's 9 `blueprint` required fields, verbatim from `validate_wiki.py`
  PAYLOAD_REQUIRED): `source_panel_id`, `content_svg`, `expected_literals[]`, `safe_zones[]`,
  **`html_bubbles[]`**, `crop`, `negative_space_policy`, `generator_script`, `file_sha256`. **Refuse unless
  the blueprint is `status: locked`** — that is the schema signal that the cross-layer gate signed off (the
  gate's ⑥ FLIP writes `status → locked` on advance; `comic-blueprint-author` emits blueprints at
  `status: locked`). The `blueprint` node has **NO `review_status` field** (only the `asset` node does, and
  `"approved"` is not even a legal `status` enum value) — never gate on `review_status` here. `html_bubbles[]`
  is the JSON viewer-overlay payload — it is **routed to the viewer, NEVER concatenated into the bake string**.
- **Writes one `prompt_bundle`** (node prefix `prompt:`, node_id a lowercase `[a-z0-9_-]+` slug of the
  `panel_id`) — payload required by the schema (the 6 `prompt_bundle` PAYLOAD_REQUIRED fields, verbatim):
  `source_panel_id`, `source_blueprint_id`, `style_prefix`, `composed_prompt`, `banned_vocab_scan`,
  `identity_ref_paths`. Status canon: author `draft`/`pending` → `under_review` → **`locked`** on a clean
  validate (or `rejected`). Write **LEGAL** edges (every `type` ∈ `validate_wiki.py` EDGE_TYPES):
  `derived_from` (prompt_bundle → panel_spec) and `uses_blueprint` (prompt_bundle → blueprint). On retry, the
  failure_mode trace uses a **legal `derived_from`** edge (prompt_bundle → failure_mode, with
  `evidence.consulted_failure_mode` + score); if a trigger still fires after repair, a legal **`failure_of`**
  edge (failure_mode → prompt_bundle — `failure_of` points failure_mode → target, matching the gate + the script,
  trigger in `evidence`). There is **NO `generated_from` /
  `consulted_failure_mode` / `violates_failure_mode` edge type** in EDGE_TYPES — those would fail the release
  gate. Emit **node IDs only** to stdout — the orchestrator re-reads the node file from disk (no filename guessing).

## Two engine contracts to compile to (fail-closed)
These are the same two contracts `comic-author` authors to — this skill is where they are *enforced at emit
time*, on the resolved spec, before any credit is spent:
1. **Every panel needs a content_svg.** On the wiki node this is **`blueprint.payload.content_svg`** (top-level
   on the blueprint payload — NOT nested under any `condition.` key; the `condition.content_svg` prefix exists
   ONLY in the runtime `comic.json` the engine reads). The blueprint's `content_svg` is the content authority +
   the bake's layout reference. If it is `null` / missing / not on disk (or its `file_sha256` mismatches) →
   **refuse** (exit 3). A scene-only panel still has a *layout* blueprint SVG; there is no such thing as a
   panel with no `content_svg`.
2. **A `baked` figure-panel must declare non-empty, ascii-tokenizable `expected_literals`.** If
   `text_mode == "baked"` and the blueprint carries a figure but `expected_literals` is empty → **refuse**
   (exit 3). Those literals become the **SCREEN-TEXT WHITELIST** block (below) and are exactly what
   `panel_gate`'s blind token-diff later verifies. A scene panel with no audited numbers must be
   `text_mode: "html"` (dialogue is an HTML overlay, no baked-literal contract).

## Procedure (numbered — an agent can execute this step by step)
**The shipped entrypoint (do NOT hand-roll the validator):**
```bash
python3 skills/comic-panel-prompt-builder/scripts/build_prompt.py   \
    [--retry --reason ""]
```
`scripts/build_prompt.py` IS this procedure made executable — it reads the `panel_spec` + `blueprint` nodes
from `/wiki/nodes/`, resolves the identity lock + refs, composes the fixed-section message, and
calls the **single canonical** `scripts/_validate.py` (which owns `MAX_MESSAGE_CHARS = 4000`, the ~80-pattern
`BANNED_VOCAB`, `no_baked_bubbles`, `real_refs` — imported, never duplicated). It writes the trace files
`wiki/prompt_build//{_resolved.json,_meta.json,_validation.json}` and, on a clean validate, the
`prompt_bundle` node + its legal edges. `--retry` **REQUIRES** `--reason` (exit 2 otherwise) — a retry must
name the active failure it is repairing. Run `python3 scripts/_validate.py --composed  --text-mode 
--bubbles ... --refs ...` standalone for a quick re-check of an already-composed message.

0. **Phase −1 · parse + scaffold.** Parse `$2` (the `panel_id` or `panel:slug` node_id); make
   `wiki/prompt_build//`. `--retry` without `--reason` → **exit 2**. The emitted node_id is
   `prompt:` (the schema `node_id` pattern is `[a-z0-9_-]+`; the display
   `panel_id` may carry case). Never guess output filenames.
1. **Phase 0 · resolve + prerequisites (refuse to proceed unless ALL hold).** Load into one
   `_resolved.json`: the `panel_spec` node; its `blueprint` node (via `panel_spec.payload.content_blueprint`);
   and the `style_prefix` from `ART_BIBLE.md` (the world-keyed `STYLE_PREFIX[]:` line — §0 register +
   §0.5 two-world palette). Enforce:
   - (a) `node_type == "panel_spec"` and `status == "locked"` — else **exit 2** (input missing/malformed).
   - (b) the upstream **`blueprint.status == "locked"`** — the schema signal that the cross-layer gate signed
     off (the gate's ⑥ FLIP writes `status → locked`). The `blueprint` node has **no `review_status` field**;
     gating on `review_status == "approved"` is unreachable (not even a legal `status` value) → never do it.
     Optionally also assert the legal `decision` node / `decides` edge for this blueprint exists as gate proof.
     Else **exit 3**.
   - (c) **every ref is a REAL locked asset path**, not a placeholder: `blueprint.payload.content_svg` exists on
     disk (and `file_sha256` matches), and each identity ref — resolved by following
     `panel_spec.payload.asset_ids[]` to its identity-sheet `asset` node (the asset must be `status: locked`,
     and publishes a real `.png` via its `ref_requirements`) — is a real `.png` (NOT `pending:*`, NOT `null`
     where one is required). A `null` ref is allowed ONLY as the documented "use project canonical" fallback;
     an unresolved `pending:*` → **exit 3**.
   - (d) the `style_prefix` resolves and is sane: ≤ 200 chars (hard), ≤ 32 words (warn). Else **exit 3**.
   Also resolve asset **names** (scene/prop/identity `asset_ids[]` → display names) for the whitelist/reviewer
   context only — names go in the whitelist, raw IDs never go in the prompt.
2. **Phase 1 · compose the condition string DETERMINISTICALLY in this EXACT load-bearing layout** (order is
   load-bearing — it empirically drives baked-text fidelity; do NOT refactor headings or order without an A/B
   test). Front = strongest conditioning weight.
   - **STYLE PREFIX** — from `ART_BIBLE.md`: tone + two-world palette + texture, world-keyed by
     `panel_spec.world` (e.g. `warm` / `seam` / `dark-cyber` / `starfield`). **NO camera/lens/lighting/quality
     vocab.**
   - **[BAKED DIALOGUE]** — **only if `text_mode == "baked"`** lift the panel's `bubbles[]` text into the
     prompt (so the image model draws legible balloons). **If `text_mode == "html"` (or `code`): SKIP this
     block entirely** — the bubbles are the viewer's job (see Phase 4). This is the single switch that decides
     baked-vs-overlay text.
   - **SCENE COMPOSITION NARRATIVE** — the scene staging authority is the **`blueprint.payload.content_svg`**
     layout (the content blueprint already lays out the scene) summarized with the panel's
     `panel_spec.payload.side_narration`. **There is NO `condition.scene` / `condition.characters` /
     `chibi_action` on the panel_spec node** — those keys live ONLY in the runtime `comic.json` (do not read
     them off the node). The world's warm/cold contrast is *by design*, not drift.
   - **"FIXED ELEMENTS (must NOT change):"** — the identity lock, obtained by following
     `panel_spec.payload.asset_ids[]` to each identity-sheet `asset` node and reading its
     **`asset.payload.identity_lock`** (the canonical-cast hex/beard/silhouette lock — there is NO
     `identity_desc` field on the panel_spec node; the node-model identity authority is the asset's
     `identity_lock`) + the locked props the motif ledger pins for this panel.
   - **"ALLOWED CHANGE (vs the prior panel):"** — from the panel's `motifs` delta (the comic reinterpretation
     of "ALLOWED MOTION": what may legitimately differ from the previous panel).
   - **"FORBIDDEN (must NOT happen):"** — the FIXED baseline `["no new character entering frame", "no off-model
     drift", "no scene/world recolor"]` + any panel-specific `must_not_add`.
   - **"SCREEN-TEXT WHITELIST (exact characters to preserve):"** — the `expected_literals[]` from the
     blueprint (e.g. `["REJECT","37","T-16:05"]`) + any baked-in signage/label names. Even when HTML owns the
     bubbles, baked-in figure text (chips, stamps, code) still needs char-exact preservation — this whitelist
     is the upstream of `panel_gate`'s literal diff.
   Write `_meta.json` (character/element counts) for the trace.
3. **Phase 1·5 · failure_mode positive-invariant injection (retry only — the spiral active-memory hook).**
   On `--retry`, query the wiki for **ACTIVE** `failure_mode` nodes scoped to this panel via
   `target_layer ∈ {prompt_pattern, visual_transition, global}`; rank by `recency*severity`
   (recency `= 1.0/age_days`, severity default 3); take the top 10. Match the composed string against each
   mode's `semantic_signature` by weighted Jaccard
   (`0.35*assets + 0.25*cterms + 0.20*kw + 0.20*frags`); **threshold 0.55** fires a match. For a matched mode,
   inject its `repair_pattern` **ONLY if `encoding_style == "positive_invariant"`** (else WARN + skip —
   negatives like "no missing ears" make diffusion fixate on the negated concept). Inject each as a
   **"POSITIVE INVARIANTS (must be present in every panel, highest priority):"** block at the **FRONT** of the
   message. Record the consult as a **legal `derived_from`** edge (prompt_bundle → failure_mode) with
   `evidence.consulted_failure_mode: true` + the match score (there is no `consulted_failure_mode` edge type).
   This is exactly the `--reason` a retry must name.
4. **Phase 1·6 · trigger re-scan.** Re-scan the FULL message against the `trigger_patterns` of EVERY active
   banlist mode (not just injected ones). If a matched mode is a `positive_invariant` whose `repair_pattern`
   isn't present yet → append it in-place into the POSITIVE INVARIANTS block. If any violation STILL remains
   → write a **legal `failure_of`** edge (failure_mode → prompt_bundle — failure_mode → target, trigger in `evidence`; there is no
   `violates_failure_mode` edge type) and **exit 7** (a regenerate signal to the orchestrator).
5. **Phase 2 · VALIDATE = the hard gate (runs on the FULL composed message; this is the rubric).** Delegate to
   `scripts/_validate.py` (the single source): `passes_all = length_ok AND no_banned_vocab AND no_baked_bubbles
   AND real_refs_ok`. ANY failure → preserve `_validation.json`, set the panel's
   `review_gates.storyboard_json_gate = "fail"`, and **exit 4** — see the EXACT gate below. **Do NOT silently
   add quotes / fix the spec** — a malformed spec FAILS validation so the fix is pushed back to the authoring
   layer.
6. **Phase 3 · emit.** Write the `prompt_bundle` node (payload = the schema's 6 required fields exactly:
   `{source_panel_id, source_blueprint_id, style_prefix, composed_prompt, banned_vocab_scan,
   identity_ref_paths}`); set its status `locked`. Write the **LEGAL** edges (every `type` ∈ EDGE_TYPES):
   `derived_from` (prompt_bundle → panel_spec) and `uses_blueprint` (prompt_bundle → blueprint) — **NOT
   `generated_from`** (illegal, fails the release gate). **Route `html_bubbles[]` to the JSON viewer overlay
   only

…

## 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-panel-prompt-builder
- 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%.
