# Comic Blind Comparison Review

> Phase-1 comic-author step (post-final eval) — a DOUBLE-BLIND A/B of two FINAL whole comics: our cross-model-audited progressive render (the comic-author + comic-director output) vs a naive single-shot baseline. A single sealed coin-flip hides which is which; two cross-model reviewers (Codex + Gemini) score both on a fixed rubric reading only a SHARED blind spec (intent + ART_BIBLE); only AFTER bo…

- **Type:** Skill
- **Install:** `agentstack add skill-wanshuiyin-aris-movie-director-comic-blind-comparison-review`
- **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-blind-comparison-review
- **Website:** https://wanshuiyin.github.io/ARIS-Movie-Director/comic/

## Install

```sh
agentstack add skill-wanshuiyin-aris-movie-director-comic-blind-comparison-review
```

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

## About

# comic-blind-comparison-review — Double-Blind A/B of Two Finished Comics (Phase 1, step S12, post-final)

The **"vs baseline" capability** the comic side otherwise lacks. The per-panel
[`comic-director`](../comic-director/SKILL.md) `panel_gate` and the cross-panel `assembly_gate` operate
**during** production on single units; this skill operates **after** production, on two **complete works** —
our cross-model-audited progressive comic (authored by the [`comic-author`](../comic-author/SKILL.md) suite,
baked by [`comic-director`](../comic-director/SKILL.md)) **vs a naive single-prompt one-shot baseline** — and
proves the spiral pipeline beats the one-shot, **under a blind so neither reviewer can favor the home team**.
It is a direct, faithful port of aris_movie's `blind-comparison-review` from video to a whole-comic A/B
(`outputs/aris_movie_progressive.mp4` vs `outputs/baseline_sd2_naive.mp4` → two rendered comic page sets), and
it is an **evaluation** skill, not an authoring one: it never edits a comic, it judges two finished ones.

> **Cardinal lesson baked in as a gate, not prose:** *the order of operations IS the integrity guarantee.*
> A single coin-flip seals `.ab_mapping.json` and `chmod 600`s it **before any reviewer is invoked**; the
> seal's mtime MUST precede both reviews; from seal-time until UNSEAL the orchestrator (Claude) may not read
> or quote the mapping; **any home-team token in a reviewer prompt is a HARD-ABORT, not a warning.** If the
> orchestrator reads the mapping before both reviews land, it may subconsciously phrase the synthesis to favor
> the home team — so the timestamp ordering is the audit trail, and "blinding integrity" is **REPORTED, never
> assumed**.

```text
  progressive comic  ┐                                            ┌─▶ comparison.md (Chinese deliverable)
  (our audited)      ├─▶ ⓪ SEAL .ab_mapping.json (1 coin-flip,    │
  baseline comic     ┘       chmod 600, mtime BEFORE any reviewer) │
  (naive one-shot)   │            ▼                                │
                     │      ① STAGE pages → A/ and B/ (sealed ids) │
                     │            ▼                                │
                     │      ② CODEX blind review (xhigh, RO)  ─────┤  (no home-team token; reads SHARED
                     │            ▼                                │   blind spec = intent + ART_BIBLE only)
                     │      ③ GEMINI blind review (per-page + ─────┤
                     │            synthesis, auto-gemini-3)        │
                     │            ▼                                │
                     └─▶  ④ UNSEAL (verify seal_ts /dev/null 2>&1 \
      || { echo "HALT: screenshot of page $i failed"; exit 2; }
  done
  echo "$2: rasterized $3 pages from the viewer"
}
# e.g.  rasterize_viewer "$PROG_PATH" "$PROG" "$NPAGES"   (NPAGES = len(comic.json pages[]))
```
If neither pre-rendered PNGs nor a headless renderer is available → **HALT** with that message; never feed the
reviewers a partial or wrong-DPI rasterize (a corrupted page set makes the A/B score garbage).

**Stage dispatch — the actual control flow (picks the form per artifact; never calls `stage()` blindly).**
```bash
stage_any() {  # $1=artifact (a DIR of pre-rendered *_pNN.png / a comic.json / a single-file .html viewer)   $2=sealed letter
  case "$1" in
    *.html)  # the SINGLE-FILE viewer (the common ship form, NO sibling comic.json): rasterize whole-page PNGs.
             # derive NPAGES from the viewer's EMBEDDED JSON island (the build inlines comic.json into a ).
      NP=$(python3 - "$1" ]*>\s*(\{.*?\})\s*', html, re.S):   # the inlined comic-IR island
    try:
        d = json.loads(blob)
        if isinstance(d.get("pages"), list): n = len(d["pages"]); break
    except Exception:
        pass
print(n)
PY
)
      [ -n "$NP" ] || { echo "HALT: cannot read page count from the viewer's embedded JSON island in $1"; exit 2; } ;
      rasterize_viewer "$1" "$2" "$NP" ;;
    *)
      if [ -d "$1" ] && ls "$1"/*_p[0-9]*.png >/dev/null 2>&1; then   # a DIR of pre-rendered whole-page PNGs
        mkdir -p "outputs/comparison-pages/$2"; i=0
        for f in $(ls "$1"/*_p[0-9]*.png | sort); do i=$((i+1)); cp "$f" "outputs/comparison-pages/$2/$2_p$(printf '%02d' "$i").png"; done
        echo "$2: staged $i pre-rendered page PNGs"
      else
        stage "$1" "$2"   # a comic.json file or a project dir — stage() normalizes + requires page-level renders
      fi ;;
  esac
}
stage_any "$PROG_PATH" "$PROG"   # route by the SEALED letter; no read of the seal file → blind holds until ④
stage_any "$BASE_PATH" "$BASE"
```

**Page-count parity gate (fail-closed).** After staging both sides, assert A and B staged the **same number of
pages** — an unequal A/B is not a fair head-to-head:
```bash
nA=$(ls outputs/comparison-pages/A/*.png 2>/dev/null | wc -l | tr -d ' ')
nB=$(ls outputs/comparison-pages/B/*.png 2>/dev/null | wc -l | tr -d ' ')
[ "$nA" -gt 0 ] && [ "$nA" = "$nB" ] || { echo "HALT: page-count parity failed (A=$nA B=$nB) — not a fair A/B"; exit 2; }
```
Page filenames embed only the sealed letter + a page index (`A_p03.png`) so reviewers cite by page, never by
home-team identity.

### ② CODEX blind review (read-only, xhigh)
A **fresh** `mcp__codex__codex` call (NOT `codex-reply`), `config {"model_reasoning_effort":"xhigh"}`,
`sandbox: "read-only"`. It sees the two page dirs (`comparison-pages/A`, `comparison-pages/B`) + the SHARED
blind spec (the identity-stripped intent + `ART_BIBLE.md`). Scrub the prompt with the **banned-token scan**
(`Constants`) BEFORE submission. The prompt asks Codex to read the pages itself and score **each comic 0–5 on
every A/B rubric dimension**, cite page filenames as evidence, and output **JSON only**. Save verbatim to
`outputs/blind_review_codex_raw.json`. Required JSON shape:
```json
{"comic_A": {"": {"score": 0-5, "evidence": "...cites A_p0X.png..."}, ...},
 "comic_B": {"": {"score": 0-5, "evidence": "...cites B_p0X.png..."}, ...},
 "overall_winner": "comic_A|comic_B|tie", "confidence": 0.0-1.0, "rationale": "..."}
```
If Codex returns non-JSON → retry **once**, stricter; still non-JSON → write a `{"parse_failed": true}` stub
and continue **Gemini-only**, noting the degradation in `comparison.md`.

### ③ GEMINI blind review (independent, same blind rules)
**3.1 per-page** — `mcp__gemini__analyzeFile`, `model auto-gemini-3`, **one call per image** (every page in A/
and B/), each page NOT seeing the other comic. Per-page JSON
`{label, page_filename, character_appearance ≤150, scene_signature ≤80, style_features ≤80,
artifact_severity_0_to_5 (int, 0=clean 5=catastrophic), notable_anomalies[]}`; persist to
`outputs/gemini_perpage.json`.
**3.2 synthesis** — `mcp__gemini-cli__ask-gemini` (text-only), fed both per-page observation lists + the blind
spec summaries (≤1000 chars each), scoring the same 0–5 A/B rubric, JSON only. Save verbatim to
`outputs/blind_review_gemini_raw.json`. Gemini is always `auto-gemini-3`. Fail fast if fewer than the staged
pages succeed per comic (the video floor was `) or
   an `outline_spec` node (`node_id` `outline:`) for the progressive comic, the caller passes that node_id
   as `TARGET_NODE_ID` and the A/B nodes point there
   (this is the cleanest "the whole comic" anchor; it must be a real node in `wiki/nodes/`).
2. **Else, the progressive comic's FIRST panel anchor** — a `panel:` that exists as a
   `comic.json panels[*].wiki_node_id` (e.g. the worked example has `panel:s01_aris_comic_v1`). This is the
   pragmatic fallback the slim schema supports today: it resolves via the comic.json panel-anchor union, so the
   edges validate. Record in the decision payload that the anchor is the first-panel proxy (the A/B is about the
   whole comic; the panel is just the schema-valid attachment point).
Whichever you choose, **the chosen id MUST already be a resolvable endpoint** — verify with
`python3 cli/validate_wiki.py ` BEFORE declaring done; if it doesn't resolve, the skill HALTs
rather than write a dangling edge.

## EXACT gate (A/B rubric) — dimensions, scoring, the wedge, vetoes
Ported from the aris_movie `blind-comparison-review` rubric, adapted from video to comic (the video skill's
`narration_sync` dimension is **dropped** and folded into `overlay_readability` = caption/bubble↔panel
reading-order alignment, which the `assembly_gate` already scores). Each reviewer scores **each comic 0–5 on
all six dimensions** — there is **no numeric pass threshold**; the verdict is the comparative `overall_winner`
+ `confidence`, and the consensus across the two families.

- **`story_comprehension`** — can the reader follow the through-line end to end (the comic's logline lands)
  from the pages alone? (video: scene_jumps coherent vs abrupt unexplained jumps.)
- **`visual_consistency`** — character identity (face/clothes/age) + palette/render stable across pages vs
  drifting. **Design-aware:** the two-world warm/dark split is *by-design, not drift* (ART_BIBLE §0.5); a
  reviewer must only flag *miscolored* worlds, never the split itself. (video: character_consistency +
  style_drift.)
- **`overlay_readability`** — bubbles/captions legible, correctly ordered, aligned to the right panel; no
  garble; bilingual text fits. (replaces the video `narration_sync` dimension.)
- **`editability_traceability`** — **THE STRUCTURAL WEDGE.** Could a human editor patch a bad page *without a
  full re-gen*, and is each failure *localizable*? 5 = a single panel + its local prompt is the minimum patch
  unit and every panel traces to its `panel_attempt`/`review`/`decision`/`failure_mode` wiki nodes; 0 = the
  whole comic is one indivisible bake with no trace, so any defect forces a total re-gen. **This is where the
  pipeline's contribution shows even when the baseline's one-shot output is visually strong** — it is an
  architectural FACT (wiki-backed traceability), not a pixel measurement, so it can WIN the A/B even while the
  baseline ties or beats on `polish`. (video: editability_proxy.)
- **`reproducibility`** — can the artifact be re-derived? (re-run the `comic.json` + content-SVG blueprints +
  the audited spiral → the same comic) vs a one-shot whose exact output is unrecoverable. (video: deterministic
  re-render of cached frames.)
- **`polish`** — raw aesthetic finish / artifact floor (the dimension the naive one-shot can plausibly win).
  (video: artifact_floor / artifact_severity.)

**Veto / hard-abort rules (the load-bearing rigor — abort beats taint):**
1. **`MAPPING_FILE` referenced in ANY reviewer prompt → HARD-ABORT** `"Blinding compromised — fix prompt
   template and re-run"`. Better to abort than ship a tainted A/B.
2. **Any home-team token** (`progressive`/`baseline`/`ARIS`/`SD2`/`naive`/`one-shot`/`spiral`/`our system`/
   `system under test`, or a rhetorical "show that A is better") in a reviewer prompt → the prompt is
   **REJECTED before submission** by the banned-token scan.
3. **Reviewer raw files predate `MAPPING_FILE`** → `blinding_integrity = "compromised"`, flag prominently in
   `comparison.md`, **never silently elide**.
4. **Codex non-JSON** → retry once stricter; else `{"parse_failed": true}` stub + continue Gemini-only, noted
   in the md.
5. **Fewer than the staged pages return from a reviewer** → re-run the missing page once, then **fail fast**.

**Invariants (REPORTED, not assumed):** seal written before any reviewer AND `seal_ts  **Schema note (read this — it is the orphan-panel-class discipline).** The aris_movie source describes a
> single rich `comparison` node with `baseline_for` / `compared_against` edges. The SLIM comic
> `node_schema.json` (v3.0) has **no `comparison` node_type and no `baseline_for`/`compared_against` edge
> types**; the canonical edge form is **`{src, dst, type}`** (NEVER `from`/`to`/`edge_type`) with `type ∈
> {attempt_of, reviews, decides, failure_of, rollback_of, supersedes}`. So this post-final A/B is recorded
> with the schema's existing verdict vocabulary: **two `review` nodes** (one per reviewer) **+ one `decision`
> node** (the consensus). The rich aris_movie `comparison` payload (per-dim scores, blinding audit) is carried
> as the decision node's payload — the schema is `additionalProperties: true`, so extra fields validate.

**Reads** — two finished comics (the progressive `comic.json` / rendered pages + the baseline rendered pages)
and the SHARED blind spec (the `intent_spec`'s `logline`/`narrative_beats` + `ART_BIBLE.md`). It does **not**
mutate any upstream authoring node.

**Writes** (status `complete` for the reviews, `final` for the decision — the runtime canon). The exact
`PAYLOAD_REQUIRED` from `cli/validate_wiki.py` is **`review` → `["target_node_id","reviewer","gate_kind"]`** and
**`decision` → `["target_node_id","verdict","gate_kind"]`**; `node_id` must match the schema pattern
`^(…|review|decision|…):[a-z0-9_-]+$` (lowercase + `_`/`-` only — `cmp_…` is fine, `` is digits):
- **`review`** × 2 (`node_id` `review:cmp__`) — payload **required** (all three): `target_node_id`
  (the resolved anchor from ⑥ — the caller's `intent_spec`/`outline_spec` id, or the first-panel proxy
  `panel:`; NEVER an id that doesn't resolve), `reviewer` (`codex` | `gemini`), `gate_kind`
  (`"blind_comparison"`). Recommended optional fields (validate under `additionalProperties`): `raw_path`
  (the verbatim `blind_review_*_raw.json`, **project-RELATIVE** — see the path rule below), `winner`
  (`progressive`|`baseline`|`tie`, **after unseal**), `confidence`, `per_dim_scores` (the unblinded 0–5
  per-dimension scores for both artifacts). The **raw** reviews live in files; the node only summarizes (no
  separate "review-node per dimension").
- **`decision`** × 1 (`node_id` `decision:cmp_progressive_vs_baseline_`) — payload **required** (all
  three): `target_node_id` (the SAME resolved anchor), `verdict` (the consensus
  `"progressive"|"baseline"|"tie"|"disagree"`), `gate_kind` (`"blind_comparison"`). Recommended optional:
  `progressive_path`, `baseline_path`, `comparison_md_path` (**all project-RELATIVE**, see below), `codex_winner`,
  `gemini_winner`, `anchor_kind` (`"author"` | `"first_panel_proxy"` — record which ⑥ option was used),
  `blinding_audit{sealed_at, first_review_started, last_review_completed, integrity, leakage_reason}`. This is
  the single authoritative A/B record (the aris_movie `comparison` node's role).

> **Path fields MUST be project-relative (release-gate rule).** `cli/validate_wiki.py` runs `abs_path_leaks()`
> over EVERY `wiki/nodes/*.json` payload and **hard-fails (`sys.exit 1`) on any string containing `/Users/` or
> `/home/`**. The only in-skill source of these paths is the seal, which stored them via `os.path.abspath(...)`
> — so before writing the nodes you MUST relativize: `progressive_path`, `baseline_path`, `comparison_md_path`,
> and any `raw_path` = `os.path.relpath(p, PROJECT_DIR)` (or just the `outputs/…` tail). Add a one-line guard
> mirroring the validator before writing each node — fail fast rather than emit an invalid node:
> ```python
> import json
> for s in (json.dumps(review_payload), json.dumps(decision_payload)):
>     assert "/Users/" not in s and "/home/" not in s, "HALT: payload has an absolute path; relativize before writing"
> ```
> (The `outputs/.ab_mapping.json` seal itself keeps `os.path.abspath` and is fine — the validator never scans it,
> only `wiki/nodes/`.)

**Edges** (canonical `{src, dst, type}` only; `type` ∈ `validate_wiki.py` `EDGE_TYPES`). The `dst` is the
**resolved `target_node_id`** from ⑥ (the caller's `intent_spec`/`outline_spec` id, or the first-panel proxy
`panel:` — an id that already resolves; **never the literal string ``**):
`review:cmp_codex_… --reviews--> ` and `review:cmp_gemini_… --reviews--> ` (each
with the optional `reviewer` + `verdict` edge fields); `decision:cmp_… --decides--> ` (with the
optional `verdict` field). `reviews`/`decides` are both legal `EDGE_TYPES` and exactly the verbs the example wi

…

## 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:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **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-blind-comparison-review
- 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%.
