Comic continuity audit
Skill wanshuiyin/ARIS-Movie-Director/skills/comic-continuity-audit
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.From its SKILL.md
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.
One thing to look at
- runs commandsInstructs the agent to run 1 command, including `mcp__gemini__analyzeFile`.
SKILL.md
37.5 KB, ~10.0k tokens by cl100k_base, 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.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.