comic-continuity-audit — the Motif-State-Table Audit (Phase 1, step S11)
SkillDev toolsThe authoring-side DIEGETIC continuity AUDIT — it produces the world-state evidence that comic-cross-layer-gate `--gate continuity` adjudicates against the storyboard's MOTIF STATE TABLE (motif_ledger). It checks each baked panel STRICTLY against the pre-committed table row, then emits a continuity review-node the gate fuses (it does NOT own a bake-time KEEP). Gemini auto-gemini-3 is the per-panel FACT extractor (one analyzeFile per panel image, fact-only); Claude is the deterministic JUDGE against the table row + the global invariants (DDL countdown monotonic never-rewind, bounce S02 = the film's ONLY MAX, the two metric columns never co-mingle, the non-adjacent MIRROR LOCKS). CAST-AWARE: a panel simply NOT containing a character is design, NEVER a drift penalty (absence≠drift). On a same-panel miss it requests `retry_panel`; a cross-frame motif break is routed into the page assembly_gate's drift set (seed-anchored comic panels are independent — no cross-panel rollback). Use when the user says "查连续性", "continuity audit", "连戏检查", "world-state drift", "motif table check", "对一下分镜表", or a panel has been baked and you need to verify it realized its motif-table row.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the comic-continuity-audit — the Motif-State-Table Audit (Phase 1, step S11) skill
What this skill tells your AI
The instructions your AI receives, as published by wanshuiyin/aris-movie-director in skills/comic-continuity-audit/SKILL.md and read by ahel’s review.
The diegetic-continuity AUDIT step of the comic-author suite: verify that a
baked panel obeys the WORLD-LOGIC the comic committed to — time / prop-state / motif-arc / metric-value /
causality across cuts — distinct from identity/style (comic-director's panel_gate already covers "does
the character LOOK the same"; this covers "does the WORLD stay consistent"). The audit is not vibes: it
reads the pre-committed MOTIF STATE TABLE authored by comic-storyboard-creator
(the motif_ledger node) and checks each baked panel STRICTLY against its own row + the film's global
invariants. This skill is the AUDIT producer, not the gate: it emits a continuity review node (world-state
evidence) that comic-cross-layer-gate --gate continuity collects and
adjudicates — the loop can DRIVE but it cannot ACQUIT (acceptance-gate).
On a same-panel miss it requests a retry_panel; a cross-frame motif break is handed to the page
assembly_gate's drift set (seed-anchored comic panels are independent — there is no cross-panel
rollback in this image pipeline, spiral_engine.js:54). It ports the
real procedure of aris_movie's continuity-audit (Gemini-as-fact-extractor → Claude-as-deterministic-judge →
violations → routed repair) onto stills, but anchors it to our pre-committed per-dimension-per-panel ledger
instead of asking a holistic judge to infer intent.
Cardinal lesson baked in as a predicate, not prose: the audit IS the table. The audit never invents a rule — it reads the
motif_ledgerand flags any panel whose observed value contradicts that panel's row. And it is cast-aware: a panel that simply does not contain a character (the duo are absent in S01/S03/S06/S18/S20/S22 by design) is design, NEVER a drift penalty (absence≠drift). This is the concrete fix for the B03 cross-frame FALSE-drift: IDENTICALmin=0scores across re-bakes were the fingerprint of a broken, cast-blind RUBRIC — not a broken artifact. When scores don't move across repairs, audit the JUDGE (is it reading the table? is it penalizing intended absence?), do not keep regenerating the panel.
motif_ledger (the TABLE) ─▶ ① LOAD attempt + prior-accepted + this panel's table row + active continuity_constraints
▼
② EXTRACT Gemini auto-gemini-3, ONE analyzeFile per panel image, FACT-ONLY (no scoring, no comparison)
▼
③ JUDGE Claude deterministic: observed ⊖ table-row AND global invariants
│ (DDL monotonic · bounce S02=ONLY MAX · metric columns never co-mingle · MIRROR LOCKS)
▼
④ CAST-AWARE FILTER intended-absence ≠ drift; design variation ≠ drift (read the row's design intent)
▼
⑤ EMIT one continuity review node (always) + 0-N continuity_constraint candidates + observed_state patch
▼
contradiction? ─ yes ─▶ same-panel miss: request retry_panel (failure_mode.repair_pattern = positive invariant)
│ cross-frame break: add panel id to the page assembly_gate drift set (NO cross-panel rollback)
│ no
▼
⑥ the continuity review node is COLLECTED by comic-cross-layer-gate --gate continuity (the gate ACQUITS, not this skill)
Constants
- THE GATE = THE TABLE. The audit reads the
motif_ledgernode (itscolumns/rows/invariants/mirror_locks) and checks each panel against its own row. It does not invent rules; an un-tabled claim is out of scope (escalate to the storyboard layer, do not adjudicate it). - MOTIF_TABLE_COLUMNS (verbatim, the dimensions the audit reads) =
Panel | Mug(ML RESEARCH motif 杯)| DDL | Tok|yo | exact_parse | claim_delta | bounce. - FACT-EXTRACTOR = Gemini
auto-gemini-3viamcp__gemini__analyzeFile— ONE call PER panel image (analyzeFiletakes ONEfilePath, never a batched array). Prompt is FACT-ONLY: "Do NOT score quality. Do NOT compare to anything. Just extract what you SEE in THIS ONE panel." For a multi-panel page, sample the salient panels (first + last + the dominantbig-framepanel), oneanalyzeFileeach. - JUDGE = Claude (this agent), deterministic predicate evaluator over the observed JSON vs the table row
- the global invariants. The judge is logic, not per-pixel taste.
- CROSS-MODEL INDEPENDENCE — the executor that baked the panel (the Codex-family
mcp__codex__codexsidecar bake) is NOT a judge here; it never self-acquits (reviewer-independence). Gemini extracts, Claude judges; the generator family is excluded from this audit. - GEMINI_FALLBACK — a frame Gemini fails: retry once; if it still fails, mark that dimension
indeterminate, never default to satisfied/violated. A false negative wastes a re-bake; surface ambiguity. - MAX_CONSTRAINTS_TO_CHECK = 20 (cap; archived/superseded
continuity_constraintnodes skipped) · GEMINI_MODEL = auto-gemini-3 (never downgrade,reviewer-routing). - OUTPUT = a
reviewnode (always) +story/CONTINUITY_TRACE.md+observed_state.jsonper panel + 0-Ncontinuity_constraintcandidate nodes + (on mismatch) adecision+ afailure_mode+ edges.
Input contract — what it reads, what it writes (the wiki node it touches)
This skill is pure audit — it produces the world-state evidence (a continuity review node) that
comic-cross-layer-gate --gate continuity later COLLECTS (via the
reviews edge) and fuses against the motif_ledger. The continuity verdict belongs to that gate (it scores
the gate's four dims ledger_row_complete / invariants_hold / mirror_locks_paired / design_aware); this skill
only writes the review + (on a contradiction) requests the engine's bake-time repair (retry_panel, or a
cross-frame add to the page assembly_gate's drift set). It does NOT feed a bake-time panel_gate KEEP —
verified: spiral_engine.js panelVerdict computes narr from
cc.narrative_beat_fidelity / cc.composition_story and never reads a plot_continuity field, so this
skill never claims MIN-fusion influence over the bake-time KEEP.
Reads (per schemas/node_schema.json, node/comic/3.0):
- the
motif_ledgernode (node_type: "motif_ledger", idmotif:<slug>) — payloadcolumns,rows(the per-panel ground truth),invariants(DDL monotonic; bounce-uniqueness; metric-column separation),mirror_locks(the non-adjacent paired constraints). This is THE spec the audit checks against (and the gate then adjudicates). - the
panel_attemptnode under audit (node_type: "panel_attempt", idattempt:<slug>) — payloadsource_panel_id,image_path(the single baked panel PNG, or a page of panels),attempt_index. - the MOST RECENT accepted
panel_attempton the same lineage =PRIOR_ACCEPTED(the implicit reference for monotonicity); if none → bootstrap-only pass (vacuous-satisfied, still emit a review node). - all live
continuity_constraintnodes — filter on the payload booleanpayload.active == true(the designed institutional-memory "is this constraint live" flag, NOT the nodestatus; skip nodes whosestatusissuperseded/rejected). Pervalidate_wiki.pyPAYLOAD_REQUIRED["continuity_constraint"]the payload carries exactlyfamily, constraint_text, applies_to_panel_ids, source_node_id, active— read all five.
Writes (every node-id matches the schema pattern ^(…|review|decision|fail):[a-z0-9_-]+$; every payload
carries exactly the validate_wiki.py PAYLOAD_REQUIRED fields for its type):
- always one
reviewnode (node_type: "review"). Node-id and reviewer are NAMESPACED to avoid a byte-collision with the bake-timepanel_gateCC review: idreview:continuity_<panel>_<attempt>_cc(NOTreview:<panel>_<reviewer>— that is byte-identical to the existingreview:panel_s01_a01_ccand would OVERWRITE it), reviewercontinuity_cc(a logic judgment, not the bake-time per-pixelcc),gate_kind: "continuity". The four continuity-gate dims go underpayload.review_scores— that map is the ONLY copycomic-cross-layer-gate--gate continuityreads (its §① loads each review'spayload.review_scores):review_scores = {ledger_row_complete, invariants_hold, mirror_locks_paired, design_aware}(each 0–5, the EXACT dims it fuses) + atimed_outbool +notesat payload top-level. The 3 schema-required keys aretarget_node_id(themotif_ledger=motif:<slug>, the node--gate continuityis passed + adjudicates),reviewer,gate_kind;status: "complete". Append areviewsedgereview:continuity_<panel>_<attempt>_* → motif:<slug>(themotif_ledgeris the exact node the continuity gate collects reviews INTO; the audited attempt is named in the review node_id + carried innotes— never targetattempt:*(the continuity gate does not adjudicate the attempt), neverreviewed_by, the opposite direction). (The bake-timepanel_gatereviews use flat top-level dim keys for the engine's reader; this AUTHOR-layer continuity review usesreview_scoresbecause that is the map the author gate reads.) - on a contradiction a
decisionnode (node_type: "decision", iddecision:continuity_<panel>_<slug>, payload schema-requiredtarget_node_id, verdict, gate_kind—gate_kind: "continuity",status: "final") whoseverdictisretry_panel(same-panel mug/tokyo/metric miss) orassembly_drift(a cross-frame motif break — routed to the page assembly_gate'sdrift_panels, NOT a per-panel rollback); plus afailure_modenode. (Vocabulary note, by design:retry_panel/assembly_driftare bake-time REPAIR REQUESTS consumed by the engine/comic-director— a deliberately SEPARATE vocabulary from the cross-layer--gate continuityacquittal verdicts{approve, revise}; two different decision nodes with two different jobs, and the node schema does not constrain verdict strings, so both validate.) Thatfailure_modenode (node_type: "failure_mode", idfail:continuity_<panel>_<slug>, payload schema-requiredlayer: "continuity",affected_shot_ids,active: true, statusactive) carries arepair_patternthat is the POSITIVE INVARIANT for the next bake — e.g. "S06 DDL reads exactlyT-18:40so18:40 > S07's 18:30holds the countdown", never the ban. (Dataflow, exact: the engine sources the next bake's invariant from the panel-gate reviewer's in-memoryfailure_mode_positive_invariant→verdict.invariant→pending[pid]→ the nextgenerate_panel; the persistedfailure_mode.payload.repair_patternis the audit SINK written FROM that same invariant, NOT read back by the engine — so authorrepair_patternas the identical positive invariant the reviewer emits.) Append adecidesedge (decision → attempt, verdict on the edge)- a
failure_ofedge (failure_mode → attempt). Norollback_ofedge — that edge is for the page-level assembly rollback the engine actually performs, not a (non-existent) per-panel chained rewind. Edge vocab used here, all invalidate_wiki.pyEDGE_TYPES:reviews / decides / failure_of(+ the existingattempt_ofminted upstream).
- a
- 0-N
continuity_constraintcandidate nodes (institutional memory; idcont:<slug>, payloadfamily, constraint_text, applies_to_panel_ids, source_node_id, active) — an implicit check that fires twice on the same lineage gets promoted to an explicit constraint node so the next run checks it cheaply. - patches the attempt payload with
observed_state(allowed byadditionalProperties: true) — this is the progressive chain: the NEXT audit has a verified reference instead of re-inferring it.
Two fail-closed engine contracts (refuse, do not paper over)
The audit runs only on artifacts the pipeline guarantees; if a precondition is missing it fails closed and points at the offending node rather than silently auditing a malformed unit:
- Every panel needs a
content_svg. The baked panel must trace to ablueprintnode whose payload carries a non-emptycontent_svg(the deterministic SVG blueprint that was the generation condition). Nocontent_svg→ the panel was free-baked, the table-row comparison has no anchor → REFUSE, route back tocomic-blueprint-author. (You audit what was committed, not a vibe.) - A baked panel carrying a
content_svgneedsexpected_literals. For any panel whose effectivetext_modeisbakedAND that carries acontent_svg(cfg_usable's exact trigger), the blueprint'sexpected_literals(the ascii-tokenizable numbers/labels: e.g.["REJECT","37","T-16:05"]) MUST be present; they are the literal values the metric/DDL columns are checked against. Missing/emptyexpected_literalson a baked figure panel → REFUSE (the fail-closed gate cannot verifyexact_parse/claim_delta/DDLwithout them).text_mode: "html"(pure-scene) panels carry no literals — they are gated onstray_text_absence, and their continuity is judged on the scene-state columns (mug / bounce / cast presence) only.
THE AUDIT PREDICATES — what this skill checks and reports to the continuity gate
This is the pre-gate routing (the audit's own predicates) — NOT the gate's APPROVE condition. The gate's
APPROVE predicate is owned by comic-cross-layer-gate --gate continuity
(ledger_row_complete ≥ 4 AND invariants_hold ≥ 4 AND mirror_locks_paired ≥ 4 AND design_aware ≥ 4, with a
structural HARD VETO on any invariant violation). This skill computes the per-column / per-invariant findings
below, folds them into those four dims, and writes them to the review node the gate fuses.
Audit columns (the six table columns; each is a machine-checkable predicate the audit verifies from the
table alone — they roll up into the gate's invariants_hold + ledger_row_complete dims):
mug— tracks ONLY theML RESEARCHmotif mug. States:hot(two stable steam wisps = the film's temperature origin) /fading(one thin wisp) /cold(no steam + milk skin). DESIGN-AWARE DISAMBIGUATION: the</>mug is an environment prop, taggedenv, and ignored — a panel showing a</>mug is NOT a motif-mug appearance and must NOT be flagged. The B01↔S20 bookendcoldis "the hardest continuity constraint" (S20 must read stone-cold + milk-skin, same mug as S01's hot origin).ddl— global invariant "DDL 全片单调递减,永不回拨" (monotonic non-increasing across the ENTIRE film, never rewinds). Exact 16-value spine:T-24:00:00 → 19:42 → 19:42 → 18:55 → 18:40 → 18:30 → 17:46 → 17:02 → 16:21 → 16:05 → 05:12 → 02:58 → 02:41 → 01:57 → 00:42 → 00:27(submitted). Two consecutive19:42(S03/S04, same-instant rhyme) are allowed because non-increasing;S06=18:40exists SOLELY so18:40 > S07's 18:30holds the never-rewind rule.tokyo— the broken-JSON chip{"city":"Tok|yo"}lifecycle:absent → first-micro (S09, sub-threshold, NOT gated) → hero/gated center (S10) → evidence thumbnail (S11) → tiny as-prior (S12a) → REPAIRED {"city":"Tokyo"} ✓ parsed (S17) → single unlabeled dark star (S22).exact_parsevsclaim_delta— the two metric columns that "永不混写" / never co-mingle:exact_parse ∈ {0.60, 0.71, 0.66, 0.78, 0.89},claim_delta ∈ {+6.2, +1.4}. A value written into the wrong column is a violation (a+6.2appearing where anexact_parseshould be, or vice-versa).bounce— uniqueness invariant "S02 是全片唯一 MAX" (S02 is the film's ONLY maximum bounce). Every later high is annotated "低于 S02"; post-fall peaks are BANNED (S12a = "attempted celebration, ARRESTED before becoming a jump"); the arc closes at S21 "smallest bounce (弧线终读数)".
Cross-panel MIRROR LOCKS (non-adjacent paired constraints, beyond the per-row table — read from
motif_ledger.mirror_locks):
- S11 REJECT ↔ S16 ACCEPT — same diagonal stamp geometry/angle (both from
stamp_family_v1.svg's shared verdict-card geometry) + identical stamping-arm pose. The audit checks the geometry matches, not the word. - S02 unique-MAX ↔ S21 smallest — the bounce-thermometer endpoints (a second MAX at S21 is a veto).
- S16b labeled star-map ↔ S22 wordless constellation — same node coordinates from
wiki_starmap_nodes_v1.json(禁目测 / no eyeballing — the JSON is the only truth source for both; S22 is programmatically derived, never re-laid-out by eye). - S20/S21 set-continuity — same room/desk/camera as S01 (S01's baked image is the additional condition ref).
HARD VETO FINDINGS (any one → the audit reports the gate's invariants_hold dim as violated (≤1) →
the gate structurally HARD-VETOes ADVANCE; this skill requests retry_panel for a same-panel miss, or routes a
cross-frame break to the assembly_gate drift set):
- DDL rewind — DDL is a COUNTDOWN of remaining time, declared monotonic NON-INCREASING (
永不回拨). Walk the accepted chain insequence_indexorder; the violation is a LATER panel showing MORE remaining time than an earlier panel (the countdown went UP / reset), OR any DDL value not matching the committed 16-value spine in order. An earlier panel having more remaining time than a later one is the CORRECT case, never a violation. (Canonical bug: S06's draftT-15:27showed LESS remaining than S07'sT-18:30(15:27 < 18:30, i.e. the deadline jumped closer then snapped back further away) — a rewind; fixed toT-18:40so the remaining-time sequence18:55(S05) → 18:40(S06) → 18:30(S07)is non-increasing.) - a second bounce == MAX — any post-S02 panel reading MAX bounce (the uniqueness invariant).
- a cross-written metric column — an
exact_parsevalue in theclaim_deltacolumn or vice-versa. - a broken MIRROR LOCK — REJECT/ACCEPT geometry mismatch, or S16b↔S22 coordinates that don't derive from the shared JSON, or S20/S21 set drift off S01.
SCORING — how findings roll into the FOUR continuity-gate dims this audit writes (ledger_row_complete,
invariants_hold, mirror_locks_paired, design_aware, each 0–5; the gate ADVANCES iff all four ≥ 4 with a
HARD VETO on any invariant violation). Per dim: clean / bootstrap → 5 · minor finding → 4 · major → 2 ·
critical → 1 · ≥2 critical on that dim → 0. SEVERITY map: any ddl / bounce-uniqueness /
metric-cross-write violation = critical on invariants_hold; a broken mirror-lock = critical on
mirror_locks_paired; a missing/blank table variable = a hit on ledger_row_complete; a mug / tokyo /
motif-state miss = major on invariants_hold; an intended-absence false-flag (caught in Phase 3) = a hit on
design_aware; an indeterminate (Gemini-confidence < 0.6) = minor, surfaced not vetoed. The gate
(not this skill) min-fuses these dims across the collected continuity reviewers and decides advance/revise —
this audit only authors the scores. (Note: this is the AUTHORING-side continuity gate's dim set; it is
distinct from the bake-time panel_gate's narrative_intent/plot_continuity formula in spiral_engine.js,
which this skill does not feed.)
CAST-AWARE GUARD (do NOT penalize, per acceptance-gate design-awareness):
- intended absence ≠ drift — a panel whose row marks a character/motif
absentorn/a(the duo in S01/S03/S06/S18/S20/S22;bounce = n/awhen the duo aren't on-panel) is correct by design; flagging it is a FALSE drift. Read the row's intent before scoring. - intended variation ≠ drift — the warm↔dark-cyber world flip, the
env</>mug, a deliberatelysub-thresholdTok|yo chip (S09,≤1.5%frame height, below the blind-review transcription threshold) are all by-design and out of the drift contract. - IDENTICAL scores across re-bakes ⟹ audit the JUDGE, not the panel. If the continuity dims
(
invariants_hold/design_aware/ …) don't move after a repair, the rubric is cast-blind or not reading the table — stop regenerating, fix the audit/gate (the B03 false-drift lesson).
Procedure (followable, one panel at a time — never batch the audit)
① Phase 0 — Load state (the table is the spec)
Parse the panel_attempt id → slug (: → __); read its image_path, source_panel_id, attempt_index.
Load the motif_ledger node and pull this panel's row + the invariants + the mirror_locks that name
this panel. Enumerate live continuity_constraint nodes (filter on payload.active == true, skip nodes
whose status ∈ {superseded,rejected}, cap 20, prioritise those whose applies_to_panel_ids contains this
panel). Resolve PRIOR_ACCEPTED = most-recent accepted attempt
on the lineage. Fail-closed check now (mirror run_comic.py cfg_usable EXACTLY — never weaker than the
engine floor): confirm the panel's blueprint carries a non-empty content_svg, and — if
text_mode == "baked" AND a content_svg is present (cfg_usable's EXACT trigger — NO 'figure panel' sub-test,
which would be narrower/weaker than the engine floor) — that expected_literals is a non-empty list where
every entry is an ascii-tokenizable string (isinstance(e, str) and re.findall(r"[a-z0-9+._-]+", e.lower()));
if either is missing or any literal fails that → HALT and route back (do not audit a malformed unit). Snapshot inputs to continuity_context.json. If no
motif_ledger row exists for this panel → escalate to the storyboard layer (the audit has nothing to check
against; never improvise a row).
② Phase 1 — Extract observed state (Gemini, fact-only, one call per image)
Call mcp__gemini__analyzeFile with auto-gemini-3, ONE filePath = the panel PNG (for a page, one
call per sampled panel: first + last + the dominant big-frame). Prompt is strictly fact-only — "Do NOT score.
Do NOT compare. Extract only what you SEE in THIS ONE panel." Demand a JSON with the columns this audit reads:
mug_state ∈ {hot,fading,cold,env,absent,unclear} (and whether the visible mug is the ML RESEARCH mug or the
</> env mug); ddl_value (the literal countdown string seen, or none); tokyo_state (absent / micro /
hero / thumbnail / repaired / star, + the literal JSON string seen); exact_parse_values[] and
claim_delta_values[] (the numbers seen, kept in separate arrays — do not merge); bounce_level
(the executor's pose: max / high / tempered / deflate / silent / n-a, + "is the duo on-panel?");
characters_visible[]; props_visible[]; plus a free anomalies[] (the negative-space audit). Aggregation
is deterministic Python, no LLM (majority/last-frame anchor for a page). On a per-frame Gemini failure:
retry once → still failing → that dimension is indeterminate (conf 0.0), never defaulted.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 61
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
comic-continuity-audit- Source
- github.com/wanshuiyin/aris-movie-director