Install
$ agentstack add skill-wanshuiyin-aris-movie-director-comic-blind-comparison-review ✓ 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 Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ 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-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 600s 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.
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).
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:
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:
{"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 outlinespec node (nodeid outline:) for the progressive comic, the caller passes that node_id as TARGETNODEID and the A/B nodes point there (this is the cleanest "the whole comic" anchor; it must be a real node in wiki/nodes/`).
- 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 (ARTBIBLE §0.5); a reviewer must only flag miscolored worlds, never the split itself. (video: characterconsistency + 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 thecomic.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: artifactfloor / artifactseverity.)
Veto / hard-abort rules (the load-bearing rigor — abort beats taint):
MAPPING_FILEreferenced in ANY reviewer prompt → HARD-ABORT `"Blinding compromised — fix prompt
template and re-run"`. Better to abort than ship a tainted A/B.
- 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.
- Reviewer raw files predate
MAPPING_FILE→blinding_integrity = "compromised", flag prominently in
comparison.md, never silently elide.
- Codex non-JSON → retry once stricter; else
{"parse_failed": true}stub + continue Gemini-only, noted
in the md.
- 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 baselinefor / comparedagainst edges. The SLIM comic > nodeschema.json (v3.0) has **no comparison node_type and no baselinefor/comparedagainst edge > types**; the canonical edge form is **{src, dst, type}** (NEVER from/to/edgetype) with type ∈ > {attemptof, reviews, decides, failureof, 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_idreview: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_iddecision: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:cmpcodex… --reviews--> and review:cmpgemini… --reviews--> (each with the optional reviewer + verdict edge fields); decision:cmp… --decides--> (with the optional verdict field). reviews/decides are both legal EDGETYPES` 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
- 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.