/** * Continuation-handoff advisory — a PURE, deterministic analyzer over a chained * scene plan, plus the failure atlas. No I/O, no Date, no Math.random. * * Harvested from the MIT `Emily2040/seedance-2.0` repo (`references/failure-atlas.md` * + the chain-depth rule in model-mechanics #5, pinned commit 63b32dc): * - errors compound across output-seeded chained generations — the ~4th–5th * link visibly drifts, so warn and prompt a re-anchor / intentional cut; * - a symptom -> likely cause -> repair-variable atlas for diagnosing a failed * continuation. * * Advisory by construction: `analyzeChainContinuity` returns warnings; nothing * here changes the chain engine, render behavior, or spend. */ export interface FailureAtlasEntry { symptom: string; cause: string; repair: string; } /** The 12 failure-atlas rows, verbatim from references/failure-atlas.md. */ export const CONTINUATION_FAILURE_ATLAS: readonly FailureAtlasEntry[] = [ { symptom: 'Continuation begins from planned ending', cause: 'Parent observed state was not reviewed.', repair: 'Replace opening with observed end state.' }, { symptom: 'Action restarts', cause: 'Completed beat was not marked already happened.', repair: 'Add completed beat exclusion.' }, { symptom: 'Future event appears early', cause: 'Reserved beat leaked into prompt.', repair: 'Remove future beat from prompt and endpoint.' }, { symptom: 'Identity drifts through extensions', cause: 'Continuity source displaced canonical identity reference.', repair: 'Re-anchor identity from canonical image.' }, { symptom: 'Screen direction flips', cause: 'Axis was not locked or reset intentionally.', repair: 'State screen direction or declare axis reset.' }, { symptom: 'Open motion stops', cause: 'Motion vector was not inherited.', repair: 'Carry subject/camera speed and direction.' }, { symptom: 'Camera phase restarts', cause: 'Camera endpoint from parent was missing.', repair: 'Start from observed camera phase.' }, { symptom: 'Prop contradicts prior clip', cause: 'Prop owner/position/condition was not tracked.', repair: 'Add prop state handoff.' }, { symptom: 'Dialogue repeats', cause: 'Completed dialogue was not logged.', repair: 'Mark line completed and continue audio phase.' }, { symptom: 'Extension quality degrades', cause: 'Extension depth and drift were ignored.', repair: 'Re-anchor or create intentional next shot.' }, { symptom: 'Reference roles contaminate', cause: 'Transfer/ignore clauses were absent.', repair: 'Split reference roles and exclusions.' }, { symptom: 'Event density is too high', cause: 'Several beats were compiled into one prompt.', repair: 'Reassign future beats to later clips.' }, ]; /** * Filter the atlas by a keyword over symptom/cause/repair (case-insensitive). * No query → every row (a fresh array copy). */ export function diagnoseContinuation(query?: string): FailureAtlasEntry[] { if (!query || !query.trim()) return [...CONTINUATION_FAILURE_ATLAS]; const needle = query.toLowerCase().trim(); return CONTINUATION_FAILURE_ATLAS.filter((entry) => `${entry.symptom} ${entry.cause} ${entry.repair}`.toLowerCase().includes(needle), ); } export interface ChainSceneInput { sceneIndex: number; /** The scene this one seeds from (its rendered output), or null when it re-anchors. */ chainedFrom: number | null; } export type ContinuationAdvisoryCode = 'chain-depth-reanchor'; export interface ContinuationAdvisory { code: ContinuationAdvisoryCode; severity: 'warning'; message: string; } export const DEFAULT_MAX_CHAIN_DEPTH = 4; /** * Chain-depth advisory. Each scene's depth is its position within an unbroken * output-seeded run: `chainedFrom === null` (or an unseen parent) re-anchors to * depth 1; otherwise depth is the parent's depth + 1. Scenes are processed in * order so a parent is already memoized — no recursion, no cycles. Emits ONE * advisory naming the scenes at or beyond `maxChainDepth`. */ export function analyzeChainContinuity( scenes: ChainSceneInput[], opts: { maxChainDepth?: number } = {}, ): ContinuationAdvisory[] { if (scenes.length === 0) return []; const threshold = opts.maxChainDepth ?? DEFAULT_MAX_CHAIN_DEPTH; const depthByScene = new Map(); for (const scene of scenes) { const parentDepth = scene.chainedFrom !== null && depthByScene.has(scene.chainedFrom) ? depthByScene.get(scene.chainedFrom)! : 0; depthByScene.set(scene.sceneIndex, parentDepth + 1); } const deep = scenes.filter((scene) => (depthByScene.get(scene.sceneIndex) ?? 0) >= threshold); if (deep.length === 0) return []; const maxDepth = Math.max(...deep.map((scene) => depthByScene.get(scene.sceneIndex)!)); const first = deep[0]!.sceneIndex; const last = deep[deep.length - 1]!.sceneIndex; const range = first === last ? `scene ${first} is` : `scenes ${first}–${last} are`; return [ { code: 'chain-depth-reanchor', severity: 'warning', message: `${range} ${maxDepth} links deep in an unbroken output-seeded chain (>= ${threshold}); errors compound across chained generations — insert an intentional cut (unchain) or re-render the tail from the original references.`, }, ]; }