Comic continuity audit
Skill wanshuiyin/ARIS-Movie-Director/skills/comic-continuity-audit
Agentic, long-horizon visual generation: a fuzzy story → a cross-model-audited image-based movie. Brings ARIS's research-wiki + multi-agent debate to multimodal generation (intelligence lives in the agent; the diffusion model just renders). Image-based today, video next.
npx -y skills add wanshuiyin/ARIS-Movie-Director --skill comic-continuity-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
37.5 KB, as published. Nobody here has run it
comic-continuity-audit — the Motif-State-Table Audit (Phase 1, step S11)
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.
③ Phase 2 — Deterministic judge (observed ⊖ table row + invariants)
Claude compares the extracted JSON to this panel's table row and the global invariants, as predicates:
- per-column: does
mug_statematch the row's mug (and is anenvmug correctly ignored)? doesddl_valueequal the row's DDL? doestokyo_statematch the row's lifecycle stage? are the metric values in the right columns and equal to the row? - global invariants (across the whole accepted chain in
sequence_indexorder, usingPRIOR_ACCEPTED+ the spine): DDL remaining-time monotonic NON-INCREASING — flag ONLY a later panel showing more remaining time than an earlier one (a countdown rewind), or a value off the 16-spine; an earlier panel legitimately has more remaining time. Bounce S02 = ONLY MAX (no post-fall peak);exact_parse/claim_deltanever cross-written. - mirror locks for this panel: REJECT↔ACCEPT geometry; S16b↔S22 shared JSON coords; S02↔S21 endpoints;
S20/S21↔S01 set. Geometry/coords are checked structurally (禁目测 where a JSON truth source exists).
Each fired check →
{check, verdict ∈ {violated, satisfied, indeterminate}, evidence, severity}.
④ Phase 3 — Cast-aware filter (absence≠drift) BEFORE scoring
Re-read this panel's row intent and drop any "violation" that is actually intended: a character/motif the
row marks absent/n/a; the warm/dark world flip; an env </> mug; a sub-threshold Tok|yo chip; an html
panel's lack of literals. A flagged-but-intended item is a FALSE drift and is removed, not scored. If after
this filter the score is identical to a prior re-bake's score, STOP and audit the rubric (cast-blindness), do
not trigger another bake.
⑤ Phase 4 — Emit continuity review + constraints + observed_state patch
Write one review node (always; id review:continuity_<panel>_<attempt>_cc, reviewer: "continuity_cc", gate_kind: "continuity" — the namespacing avoids overwriting the bake-time
review:<panel>_<reviewer> panel-gate node; see the Writes contract). Write the four continuity-gate dims
under payload.review_scores = {ledger_row_complete, invariants_hold, mirror_locks_paired, design_aware}
(per the scoring table — that map is what --gate continuity reads) + timed_out + notes, and append a
reviews edge review:continuity_<panel>_<attempt>_* → motif:<slug> (the motif_ledger — the exact node
--gate continuity is passed and collects reviews INTO; NOT attempt:*, which the continuity gate does not
adjudicate). Append the human-readable trace to
story/CONTINUITY_TRACE.md and observed_state.json. Patch the attempt with observed_state (the verified
reference for the next audit — the progressive chain). For any implicit check that has now fired twice on this
lineage, emit a continuity_constraint candidate node (institutional memory) so it becomes an explicit cheap
check next run. Bootstrap pass (no PRIOR_ACCEPTED): verdict is vacuously satisfied, but STILL emit the
review node and seed candidate constraints from the observed state.
⑥ Phase 5 — Request repair on contradiction (executor never self-judges; NO cross-panel rollback)
On ANY hard-veto finding (DDL rewind / second MAX bounce / cross-written metric / broken mirror-lock) or a
critical/major severity, write a decision node + a failure_mode node — the engine reality is the
authority here (spiral_engine.js:54: "panels are independent (no
chain) → only KEEP or RETRY-SAME-PANEL, NEVER rollback-to-prior"), so the verdict is NOT a per-panel
rollback:
- same-panel miss (mug / tokyo / metric / a DDL value wrong on this panel) →
decision.verdict: "retry_panel". The engine re-bakes THIS panel with the positive invariant injected — sourced from the panel-gate reviewer's in-memoryfailure_mode_positive_invariant(→verdict.invariant→pending[pid]→ the nextgenerate_panel), NOT read back from the persisted node (payload.repair_patternis the audit SINK). - cross-frame break (a mirror-lock between non-adjacent panels, or a DDL/bounce relation that only shows
across panels) →
decision.verdict: "assembly_drift", and add the offending panel id(s) to the page assembly_gate'sdrift_panelsset (the engine'sassemblyVerdictreturnsrollbackwithdrift_panelsfor page-level cross-frame repair — the ONLY rollback this image pipeline has). Do not claim a chained downstream re-bake "because each panel conditions the next" — that is false for seed-anchored comic panels. failure_modenode (layer: "continuity",affected_shot_ids,active: true) — populatepayload.repair_patternas a POSITIVE INVARIANT (the audit record of the same invariant the reviewer emits for the re-bake — a sink, not what the engine reads back), e.g.continuity_ddl_rewind→ "S06 DDL reads exactlyT-18:40so the remaining-time sequence stays non-increasing (18:55 → 18:40 → 18:30)";continuity_mirror_lock_broken→ "S22 star coords derive verbatim fromwiki_starmap_nodes_v1.json(禁目测), matching S16b". State the desired target, never the ban.
Append a decides edge (verdict on the edge) + a failure_of edge — no rollback_of edge (that
edge type is for the engine's page-level assembly rollback, not a per-panel rewind). The re-bake / assembly
repair is owned by comic-director + the engine; this skill only requests it.
Canonical failure-mode names to reuse:
continuity_ddl_rewind · continuity_bounce_second_max · continuity_metric_cross_write ·
continuity_mirror_lock_broken · continuity_mug_state_wrong · continuity_tokyo_stage_wrong.
Worked example
The canonical filled ledger to audit against is
examples/comic_m3_audit/story/STORYBOARD_DRAFT.md
— the CORRECTED MOTIF STATE TABLE at lines 12-14 (the verbatim conventions block + the table header)
and the 24 filled rows (lines 16-39) ARE the motif_ledger this audit reads. Copy this pattern exactly:
- The header + conventions (line 12-14) is the gate spec, verbatim:
| Panel | Mug(ML RESEARCH motif 杯)| DDL | Tok|yo | exact_parse | claim_delta | bounce |with the conventions "mug 列只追踪ML RESEARCHmotif 杯;</>杯是环境道具(标 env)… DDL 全片单调递减,永不回拨 … S02 是全片唯一 MAX". The audit reads predicates off THIS, it does not author new rules. - A real monotonic-DDL fix the audit would catch (line 21): remaining-time
S05=18:55, S06=18:40, S07=18:30is correctly non-increasing.S06was changed from the draft'sT-15:27toT-18:40purely so S06 still has MORE remaining than S07 (18:40 > 18:30) —T-15:27would have shown LESS remaining than S07's18:30(the countdown rewound forward then snapped back). A re-baked S06 readingT-15:27→continuity_ddl_rewind, critical oninvariants_hold→retry_panel(same-panel re-bake with the positive-invariant repair). - Split metric columns that never co-mingle (lines 23-31):
exact_parsecarries{0.60,0.71,0.66,0.78,0.89}(S10 shows0.71→0.66whileclaim_deltastays—);claim_deltacarries{+6.2,+1.4}(S14 shows+6.2→+1.4whileexact_parsestays—). A bake that prints+6.2in the curve where0.66belongs →continuity_metric_cross_write, critical. - bounce uniqueness (lines 17, 27, 38):
S02 = MAX(全片唯一最高跳);S12a = attempted celebration, ARRESTED before becoming a jump;S21 = smallest bounce(弧线终读数). A re-baked S12a leaping to MAX →continuity_bounce_second_max, critical. - The CAST-AWARE rows (absence≠drift) —
S01 bounce = n/a(duo 未醒),S03 bounce = n/a(duo 不在本格),S20 mug = COLD … bounce = n/a(duo 未露脸). The Gemini extractor will report "no duo on panel" and "no motif mug steam"; the judge must read these as satisfied-by-design, NOT as drift. If an S01 re-bake keeps scoring0because the duo are missing, the RUBRIC is cast-blind (the B03 false-drift fingerprint) — fix the judge, do not regenerate S01. - Mirror locks — S11
REJECT↔ S16ACCEPT(lines 26, 32: "同一卡面几何/角度 … 精确镜像"), and S16b ↔ S22 (lines 33, 39, the wordless dark star) sharewiki_starmap_nodes_v1.json(禁目测). The audit checks the geometry/coords match the shared source, not the stamp word.
The same project ships the consolidated canonical asset contract (lines 369-381) — the audit uses it to know
WHICH instances are continuity-bearing (one ddl_widget_template_v1.svg for the whole 18-instance timeline;
one stamp_family_v1.svg for all verdict stamps; one wiki_starmap_nodes_v1.json truth source for S16b+S22).
Hard do / don't (earned lessons)
- DO read the
motif_ledgerand check each panel against ITS row — the audit IS the table; never improvise a rule the storyboard didn't commit (an un-tabled claim escalates to the storyboard layer, it is not adjudicated here). - DO keep Gemini fact-only (no scoring, no comparison) and one
analyzeFileper panel image — the fact-extractor must not see the expected values (reviewer independence); the JUDGE owns the comparison. - DO be cast-aware: read a row's
absent/n/a/envintent and treat it as satisfied-by-design. Absence≠drift. Intended variation≠drift. - DO treat identical scores across re-bakes as a broken JUDGE, not a broken artifact — audit the rubric for cast-blindness (the B03 false-drift lesson), do not keep regenerating.
- DO fail closed: no
content_svgon the panel, or noexpected_literalson a baked figure panel → REFUSE and route back; never audit a free-baked or un-anchored unit. - DON'T let the model that baked the panel (the Codex-family
mcp__codex__codexsidecar bake) judge its own continuity — the generator family never self-acquits; this audit is Gemini-extract + Claude-judge, and the cross-layer gate adjudicates. - DON'T default an
indeterminate(Gemini failed / confidence< 0.6) tosatisfiedorviolated— surface the ambiguity; a false negative wastes a re-bake. - DON'T flag the warm↔dark-cyber world flip, the
env</>mug, or a deliberately sub-threshold Tok|yo chip — they are by-design and out of the drift contract. - DON'T confuse this with identity/style — that is
comic-director'spanel_gate. This lane is WORLD-state (time / mug-heat / metric values / bounce arc / mirror geometry / causality) ONLY. - DON'T own ANY verdict — this skill writes a continuity
reviewnode (the four dimsledger_row_complete / invariants_hold / mirror_locks_paired / design_aware);comic-cross-layer-gate--gate continuitycollects it (via thereviewsedge), min-fuses, and ACQUITS. It does NOT feed the bake-timepanel_gate(spiral_engine.jsreads noplot_continuityfield); on a contradiction it only requests aretry_panel(same-panel) or anassembly_driftroute (cross-frame), never a per-panel rollback.
Protocols (governance contracts this skill honors)
reviewer-independence— Gemini blind-extracts facts from the image only (never shown the expected table row); the executor that baked the panel (the Codex-familymcp__codex__codexsidecar bake) is not a judge of its own continuity.acceptance-gate— the loop can DRIVE but can't ACQUIT: this skill only authors the continuity review (the four gate dims);comic-cross-layer-gate--gate continuity(a different model family) fuses + decides, and the gate is design-aware (absence≠drift) — intended absence/variation is never a drift penalty.reviewer-routing— Geminiauto-gemini-3for fact extraction; never downgrade the reviewer tier.artifact-integrity— the baker does not judge its own panel's numbers; themotif_ledgeris ground truth and the observed values are verified (blind Gemini extract + deterministic diff), never originated by the gate.review-tracing— every audit writes areviewnode + theCONTINUITY_TRACE.md+observed_state.jsonso each verdict (and every rollback) is auditable.