comic-continuity-audit — the Motif-State-Table Audit (Phase 1, step S11)

SkillDev tools

The 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.

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_ledger and 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: IDENTICAL min=0 scores 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_ledger node (its columns / 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-3 via mcp__gemini__analyzeFileONE call PER panel image (analyzeFile takes ONE filePath, 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 dominant big-frame panel), one analyzeFile each.
  • 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__codex sidecar 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_constraint nodes skipped) · GEMINI_MODEL = auto-gemini-3 (never downgrade, reviewer-routing).
  • OUTPUT = a review node (always) + story/CONTINUITY_TRACE.md + observed_state.json per panel + 0-N continuity_constraint candidate nodes + (on mismatch) a decision + a failure_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_ledger node (node_type: "motif_ledger", id motif:<slug>) — payload columns, 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_attempt node under audit (node_type: "panel_attempt", id attempt:<slug>) — payload source_panel_id, image_path (the single baked panel PNG, or a page of panels), attempt_index.
  • the MOST RECENT accepted panel_attempt on 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_constraint nodes — filter on the payload boolean payload.active == true (the designed institutional-memory "is this constraint live" flag, NOT the node status; skip nodes whose status is superseded/rejected). Per validate_wiki.py PAYLOAD_REQUIRED["continuity_constraint"] the payload carries exactly family, 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 review node (node_type: "review"). Node-id and reviewer are NAMESPACED to avoid a byte-collision with the bake-time panel_gate CC review: id review:continuity_<panel>_<attempt>_cc (NOT review:<panel>_<reviewer> — that is byte-identical to the existing review:panel_s01_a01_cc and would OVERWRITE it), reviewer continuity_cc (a logic judgment, not the bake-time per-pixel cc), gate_kind: "continuity". The four continuity-gate dims go under payload.review_scores — that map is the ONLY copy comic-cross-layer-gate --gate continuity reads (its §① loads each review's payload.review_scores): review_scores = {ledger_row_complete, invariants_hold, mirror_locks_paired, design_aware} (each 0–5, the EXACT dims it fuses) + a timed_out bool + notes at payload top-level. The 3 schema-required keys are target_node_id (the motif_ledger = motif:<slug>, the node --gate continuity is passed + adjudicates), reviewer, gate_kind; status: "complete". Append a reviews edge review:continuity_<panel>_<attempt>_* → motif:<slug> (the motif_ledger is the exact node the continuity gate collects reviews INTO; the audited attempt is named in the review node_id + carried in notes — never target attempt:* (the continuity gate does not adjudicate the attempt), never reviewed_by, the opposite direction). (The bake-time panel_gate reviews use flat top-level dim keys for the engine's reader; this AUTHOR-layer continuity review uses review_scores because that is the map the author gate reads.)
  • on a contradiction a decision node (node_type: "decision", id decision:continuity_<panel>_<slug>, payload schema-required target_node_id, verdict, gate_kindgate_kind: "continuity", status: "final") whose verdict is retry_panel (same-panel mug/tokyo/metric miss) or assembly_drift (a cross-frame motif break — routed to the page assembly_gate's drift_panels, NOT a per-panel rollback); plus a failure_mode node. (Vocabulary note, by design: retry_panel/assembly_drift are bake-time REPAIR REQUESTS consumed by the engine/comic-director — a deliberately SEPARATE vocabulary from the cross-layer --gate continuity acquittal verdicts {approve, revise}; two different decision nodes with two different jobs, and the node schema does not constrain verdict strings, so both validate.) That failure_mode node (node_type: "failure_mode", id fail:continuity_<panel>_<slug>, payload schema-required layer: "continuity", affected_shot_ids, active: true, status active) carries a repair_pattern that is the POSITIVE INVARIANT for the next bake — e.g. "S06 DDL reads exactly T-18:40 so 18:40 > S07's 18:30 holds the countdown", never the ban. (Dataflow, exact: the engine sources the next bake's invariant from the panel-gate reviewer's in-memory failure_mode_positive_invariantverdict.invariantpending[pid] → the next generate_panel; the persisted failure_mode.payload.repair_pattern is the audit SINK written FROM that same invariant, NOT read back by the engine — so author repair_pattern as the identical positive invariant the reviewer emits.) Append a decides edge (decision → attempt, verdict on the edge)
    • a failure_of edge (failure_mode → attempt). No rollback_of edge — 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 in validate_wiki.py EDGE_TYPES: reviews / decides / failure_of (+ the existing attempt_of minted upstream).
  • 0-N continuity_constraint candidate nodes (institutional memory; id cont:<slug>, payload family, 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 by additionalProperties: 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:

  1. Every panel needs a content_svg. The baked panel must trace to a blueprint node whose payload carries a non-empty content_svg (the deterministic SVG blueprint that was the generation condition). No content_svg → the panel was free-baked, the table-row comparison has no anchor → REFUSE, route back to comic-blueprint-author. (You audit what was committed, not a vibe.)
  2. A baked panel carrying a content_svg needs expected_literals. For any panel whose effective text_mode is baked AND that carries a content_svg (cfg_usable's exact trigger), the blueprint's expected_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/empty expected_literals on a baked figure panel → REFUSE (the fail-closed gate cannot verify exact_parse/claim_delta/DDL without them). text_mode: "html" (pure-scene) panels carry no literals — they are gated on stray_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 the ML RESEARCH motif 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, tagged env, and ignored — a panel showing a </> mug is NOT a motif-mug appearance and must NOT be flagged. The B01↔S20 bookend cold is "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 consecutive 19:42 (S03/S04, same-instant rhyme) are allowed because non-increasing; S06=18:40 exists SOLELY so 18:40 > S07's 18:30 holds 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_parse vs claim_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.2 appearing where an exact_parse should 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 constellationsame 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 in sequence_index order; 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 draft T-15:27 showed LESS remaining than S07's T-18:30 (15:27 < 18:30, i.e. the deadline jumped closer then snapped back further away) — a rewind; fixed to T-18:40 so the remaining-time sequence 18: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_parse value in the claim_delta column 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 absent or n/a (the duo in S01/S03/S06/S18/S20/S22; bounce = n/a when 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 deliberately sub-threshold Tok|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