/** Explicit observations for agents; this module does not perform visual analysis. */ export const MOTION_CHECKS = ['starting-pose', 'identity', 'subject-motion', 'camera-stability', 'framing', 'audio'] as const; export type MotionCheck = typeof MOTION_CHECKS[number]; export interface CinemaMotionReview { method: 'sampled-frames' | 'full-playback'; checks: Array<{ criterion: MotionCheck; verdict: 'pass' | 'fail' | 'unreviewed'; reason: string }>; } export function validateCinemaMotionReview(value: CinemaMotionReview): void { if (!value || !['sampled-frames', 'full-playback'].includes(value.method) || !Array.isArray(value.checks)) throw new Error('Motion review requires an inspection method and checks'); if (value.checks.some(check => !check || typeof check !== 'object')) throw new Error('Motion review contains an invalid check'); if (value.checks.length !== MOTION_CHECKS.length || new Set(value.checks.map(c => c.criterion)).size !== MOTION_CHECKS.length) throw new Error('Motion review requires each of the six criteria exactly once'); for (const check of value.checks) { if (!MOTION_CHECKS.includes(check.criterion) || !['pass', 'fail', 'unreviewed'].includes(check.verdict) || typeof check.reason !== 'string' || !check.reason.trim()) throw new Error('Invalid motion review criterion, verdict or reason'); if (value.method === 'sampled-frames' && check.criterion === 'audio' && check.verdict !== 'unreviewed') throw new Error('Sampled frames cannot establish audio compliance'); } } export function motionReviewNextSteps(value: CinemaMotionReview): string[] { validateCinemaMotionReview(value); const fixes: Record = { 'starting-pose': 'Match the source pose to the opening action before changing the motion prompt.', identity: 'Inspect the face and wardrobe against the approved references.', 'subject-motion': 'Isolate the failed action or hold; change one variable and compare evidence.', 'camera-stability': 'Inspect fixed background landmarks; test camera control separately from subject motion.', framing: 'Increase source-frame head and foot margins, then inspect the entire sequence.', audio: 'Listen to the complete clip and review speech, music and ambience.', }; return value.checks.filter(c => c.verdict !== 'pass').map(c => c.verdict === 'unreviewed' ? `Review ${c.criterion}: ${fixes[c.criterion]}` : fixes[c.criterion]); } export function assertMotionReviewDecision(value: CinemaMotionReview, decision: string): void { validateCinemaMotionReview(value); if (decision === 'pass' && (value.method !== 'full-playback' || value.checks.some(c => c.verdict !== 'pass'))) throw new Error('Motion pass requires full playback and all six checks passed; partial evidence cannot approve production'); } export const STARTING_POSE_GUIDANCE = 'Starting-frame continuity: match the visible pose to the opening action. For a hold, begin with planted feet and relaxed arms; avoid a mid-stride source. Review subject motion and camera stability separately.';