Skip to content

What the conflict checker outputs — real example (Tadej alarm shot)

This is the real, unedited output of the pre-generation validation for one production shot, validated live on 2026-08-04. Full raw JSON: pregen-validation-output-example.json.

The shot: "Phone screen ignites with cycling anthem alarm blaring" — Tadej's smartphone alarm, extreme close-up, top-down. The shot has a sketch (never analyzed), one character (Tadej), one location (Tadej's Bedroom), one prop (Tadej's Smartphone Alarm).


1. The verdict (one per shot)

Field Value for this shot Meaning
status blocked Overall verdict: pass / pass_with_warnings / review_required / blocked / validation_failed
selectedSketchMoment middle Which moment of the shot the sketch depicts (start/middle/end) — inferred by the vision model
validatedAt 2026-08-04 11:46 When it ran
inputHash 717cb3f2f88d70cf Fingerprint of the inputs — same inputs later = cached verdict, no re-run. Full sha256 hex (64 chars) on current runs; this example was captured while it was still truncated

2. The conflicts (the interesting part — 7 found on this shot)

Every conflict carries the same structure. Here are three representative ones, exactly as stored:

Example A — deterministic blocker (no AI needed)

code:         SKETCH_ANALYSIS_MISSING
severity:     blocker            → red banner in design, gates generation
one-liner:    "Sketch analysis not found"
found:        "The selected sketch has not been analyzed. Run sketch analysis
               first — it labels characters and produces the notes the checks
               and the prompt rely on."
fields:       [sketch]           → highlights the Sketch card
dismissible:  false              → no "Not a conflict" button; must be resolved

Example B — camera blocker with a fix (vision model, Stage 3)

Note the severity: the catalog's default for this code is risky, and the model raised it to blocker for this instance. That is normal — see §4.

code:         SKETCH_CAMERA_MISMATCH
severity:     blocker            (catalog default: risky — model decided)
one-liner:    "Shot size in sketch doesn't match in Shot size property"
found:        "The sketch is framed wider than the described extreme close-up.
               The frame description says the phone screen should fill the
               frame with only the nightstand edge and a blanket corner also
               visible, but the sketch includes extra tabletop space plus a
               digital clock and lamp base."
proposed fix: "Tighten the crop so the phone is the dominant subject and
               remove the clock/lamp from frame."
fields:       [FRAME DESCRIPTION, CAMERA PROPERTIES, STAGE 2 SKETCH OBSERVATION]
dismissible:  false

"FRAME DESCRIPTION" here means the checker's derived frame text, not the creator's field — see the box in §3.2.

Example C — dismissible pose issue (the "Not a conflict" candidate)

code:         SKETCH_POSE_ACTION_MISMATCH
severity:     risky
one-liner:    "Pose in sketch doesn't match frame description"
found:        "The sketch mixes multiple action moments with a ghosted retreat
               hand and motion arrow, instead of depicting the single instant
               of impact described."
proposed fix: "Keep only the impact pose: one solid hand flattened on the
               phone, no ghosted positions or directional arrow."
fields:       [FRAME DESCRIPTION, STAGE 2 SKETCH OBSERVATION]
dismissible:  true               → user can mark "Not a conflict"

All 7 on this shot, at a glance

# Severity Code One-liner
1 blocker SKETCH_ANALYSIS_MISSING Sketch analysis not found
2 blocker SKETCH_CAMERA_MISMATCH Shot size in sketch doesn't match in Shot size property
3 blocker SKETCH_ANGLE_MISMATCH Camera angle in sketch doesn't match in Camera angle property
4 blocker SKETCH_POSITION_MISMATCH Character position in sketch doesn't match frame description
5 risky SKETCH_COMPOSITE_MOMENTS Sketch combines several moments in one drawing
6 risky SKETCH_POSE_ACTION_MISMATCH Pose in sketch doesn't match frame description
7 minor dialogue_truncated (model-invented code — see §4) Shot properties contradict each other

Reading it as a story: the sketch for this shot is drawn as an oblique medium view mixing several action moments, while the shot metadata asks for a single top-down extreme close-up of the phone at the instant the hand slams it. The checker caught the framing, the angle, the layout and the multi-moment drawing, and attached a concrete fix to 4 of the 7 (the deterministic missing-analysis blocker and the two multi-moment/text findings have no suggestion — suggestion is optional).

Row 7 also shows the Stage-1 problem: the code is dialogue_truncated, invented by the model, so the user just sees the generic "Shot properties contradict each other." The actual finding — a dialogue line cut off mid-sentence — is only in description.

3. Design UI fields — everything available to show on the review screen

For the head of design and the frontend engineer: this is the inventory of what can go on the "Review all inputs before generation" screen, and where each piece comes from.

Read the "Worth showing?" / "Notes" columns carefully — not everything here ships in the validate response. Three groups:

  1. In validation — comes back from the validate call, ready to render.
  2. On the shot / project / story — the frontend already fetches these separately; validation does not echo them back.
  3. Needs backend work — flagged inline where it applies (asset variantId, the annotated sketch URL, Stage-1 conflict codes).

3.1 Visual references (image cards)

Source: validation.resolvedInputs — the EXACT images the checker validated, same resolution generation uses.

Card What's available per card Notes
Sketch resolvedInputs.sketchUrl Always first. Conflicts about the drawing attach here
Character (one card each) one references[] entry with type: "character" — name, imageUrl, assetId One card per character assigned to the shot that has a reference image
Location one references[] entry with type: "environment" The environment reference
Prop (one card each) references[] entries with type: "prop" Includes container props (a bus/tent interior that acts as the setting)

Each references[] entry is exactly { type, name, imageUrl, assetId } — four keys, nothing else. Two caveats:

  • variantId is not in the payload. The collector resolves the variant to pick the right image, but only the assetId survives into resolvedInputs. If the UI needs to show which view ("Default / Back / Side"), that's a backend addition, not something to read off this object.
  • The sketch URL is the raw sketch, not the annotated one. resolvedInputs.sketchUrl is input.sketch.rawUrl. Stage 3 actually looks at the annotated version when one exists (selectedForGenerationUrl), and that URL is not exposed. Also a backend addition if design wants the C1/C2 labels visible.

Reference counts per shot in the measured scenes ranged from 1 to 6.

Per-card conflict decoration: each conflict carries fieldPaths + a code, so the UI knows which card gets the amber border and the inline conflict text (prop conflicts → prop card, location conflicts → location card, sketch conflicts → sketch card). Note that Stage-3 fieldPaths are section labels, not asset names — mapping a prop conflict to a specific prop card means matching on the prop name inside description/evidence, which is unreliable. Safest V0: decorate by card type, not by individual asset.

Not available (yet): reference shots (prior generated shots used for lighting/atmosphere) — not part of validation V0.

3.2 Textual references (field rows) — exhaustive list

Source: the shot document (what the user/system wrote) + validation.frameCandidates (what the checker derived). Suggested display label → actual field.

Only validation.* rows come back from the validate endpoint. Everything sourced from the shot document, the project or the story is a separate fetch the frontend already does — validation does not echo those fields back.

The one thing to get right: frameCandidates is the checker's own text

Stage 1 rewrites the shot's text fields into three static frame descriptions and stores them in frameCandidates. Stage 3 then compares the sketch against frameCandidates[selectedSketchMoment] — not against initialFrameInstructions / middleFrameInstructions / finalFrameInstructions (selectedFrameAlignmentStage.ts:61).

Conflict descriptions then say things like "the frame description says the phone screen should fill the frame" — quoting the derived text. If the UI only shows the creator's fields, the user reads that sentence, goes looking for it in their own text, and it isn't there. Our PM hit this on the first pass and reported it as a bug.

Concrete example from the measured scene (shot 1). Creator wrote, in initialFrameInstructions:

"Becca stands frame-left and Tim stands frame-right beside the Bubble Car at center-right, both waist-up with the driveway and street behind them."

frameCandidates.start, which is what the checker actually compared the sketch to:

"Medium eye-level static 2-shot, framed head-to-waist. Becca stands frame-left at roughly 30% of the frame, body angled slightly three-quarter right, shoulders square and chin level, eyes fixed off-frame right. Tim stands frame-right at roughly 65% of the frame, body also angled slightly three-quarter…"

Same intent, far more specific — and the extra specifics are what conflicts get raised against. Show the derived text on the review screen, labelled as derived (e.g. "What the checker compared your sketch to"), otherwise the conflict text is unverifiable to the user.

Display label Field What it contains Worth showing?
Description description One-line summary of the shot yes — primary
Performance actingInstructions The movement/acting across the shot (temporal) yes — primary
Initial frame initialFrameInstructions Static description of the start moment, as the creator wrote it yes
Middle frame middleFrameInstructions Static description of the middle moment yes, but it is empty in practice: nothing in the product writes this field (it's not in ALLOWED_METADATA_FIELDS, so SketchAIDA can't set it) and it was empty on 16/16 shots of the scene we measured
Final frame finalFrameInstructions Static description of the end moment yes
What the checker actually compared against validation.frameCandidates.{start,middle,end} See the box below — this is not the creator's text yes, and label it clearly as derived
Sketch shows validation.selectedSketchMoment start / middle / end — which moment the sketch depicts, inferred by Stage 2 yes — one chip, very useful context. The shot field sketchConnectedTo exists in the schema and the checker reads it if set, but nothing writes it today, so in practice this always comes from validation
Shot size shotSize Enum: WIDE, MEDIUM, CLOSE_UP… yes
Shot size details shotSizeDetails Free text explaining the framing yes
Camera angle cameraAngle Enum: EYE_LEVEL, LOW_ANGLE, TOP_DOWN… yes
Camera angle details cameraAngleDetails Free text yes
Camera movement cameraMovement Enum: STATIC, PAN, PUSH_IN… yes
Camera movement details cameraMovementDetails Free text optional
Dialogue dialogues[] (structured: speaker + line) with legacy dialogue fallback Who says what in this shot yes
Timing timing Shot duration in seconds optional
Sound sound Sound notes optional
Effect effect + effectDetails Visual effect + rationale optional
Turn marker turnMarker The meaningful change in this shot probably not (internal)
Visual tactics visualTactics Rationale object for visual choices probably not (internal)
User feedback feedback Regeneration feedback the user typed only in regenerate flows
Art style project artStyle Project-wide art style text optional (right column context)
Creative intent story creativeIntent.summary Tone/mood of the story optional (right column context)

Same per-row conflict decoration as the cards: conflicts carry fieldPaths (e.g. ["dialogue","actingInstructions"]) → highlight those rows.

3.3 Verdict area (from validation itself)

Element Source Notes
Overall verdict status pass / pass_with_warnings / review_required / blocked / validation_failed
Generate allowed? canGenerate drives the button
Conflict list conflicts[] the banner (see §4 for the anatomy of each)
"Sketch shows the END of this shot" selectedSketchMoment + sketchAlignmentConfidence nice contextual chip
"What the checker compared your sketch to" frameCandidates[selectedSketchMoment] the derived frame text conflicts are raised against — see the box in §3.2. Label it as derived, not as the creator's own words. Optional field: absent on results validated before 2026-08-04
Checked at / freshness validatedAt e.g. "checked 2 min ago"

4. The conflict object, key by key (for PM/design)

Every conflict is one JSON object. Here is each key, the values it can take, and where it appears in the UI:

code — what kind of problem it is

Not shown to the user directly: the code selects the one-liner text, and it's the stable ID we count in analytics ("which conflict type do users dismiss most?"). The full catalog, one row per code, with the exact one-liner the user sees.

The "Default severity" column is a fallback, not a guarantee. For every LLM-produced finding the model reports its own severity and that wins: severity: finding.severity || template.defaultSeverity (preGenValidation.service.ts:321). The catalog value only applies when the model omits it, or for the deterministic non-LLM conflicts (SKETCH_ANALYSIS_MISSING, SKETCH_MATCHES_NO_FRAME, SKETCH_COMPOSITE_MOMENTS, VALIDATION_INFRASTRUCTURE_FAILED), which always use the catalog value.

Measured on 31 real shots (2 scenes): SKETCH_POSE_ACTION_MISMATCH (catalogued risky) came back blocker 6× · risky 10× · minor 3×. SKETCH_PROP_STATE_MISMATCH (catalogued risky) came back blocker 9× · risky 4×. ENVIRONMENT_VARIANT_MISMATCH (catalogued blocker) came back risky 3× and blocker 0×.

Do not build UI that maps a code to a severity. Read severity off each conflict object.

The sketch contradicts the shot's settings (found by comparing the drawing against the metadata)

All model-decided. "Observed" = what actually came back across 31 real shots.

Code One-liner shown to the user Default severity Observed
SKETCH_CAMERA_MISMATCH Shot size in sketch doesn't match in Shot size property risky blocker 5 · risky 3
SKETCH_ANGLE_MISMATCH Camera angle in sketch doesn't match in Camera angle property risky blocker 1 · minor 1
SKETCH_ENTITY_COUNT_MISMATCH Characters in sketch don't match Characters in shot risky blocker 1
SKETCH_POSITION_MISMATCH Character position in sketch doesn't match frame description risky blocker 4 · risky 6
SKETCH_POSE_ACTION_MISMATCH Pose in sketch doesn't match frame description risky blocker 6 · risky 10 · minor 3
SKETCH_PROP_STATE_MISMATCH Prop in sketch doesn't match Props property risky blocker 9 · risky 4
SKETCH_ENVIRONMENT_MISMATCH Location in sketch doesn't match in Location variant risky blocker 3 · risky 2 · minor 1

The sketch itself has a problem (regardless of the metadata)

These four are deterministic — raised by code, not by the model, so the severity below is the real severity, always.

Code One-liner shown to the user Severity Raised when
SKETCH_ANALYSIS_MISSING Sketch analysis not found blocker the selected sketch has no stored analysis (fires on every un-analyzed shot)
SKETCH_MATCHES_NO_FRAME Sketch doesn't match any moment of this shot blocker best moment score < 0.4
SKETCH_COMPOSITE_MOMENTS Sketch combines several moments in one drawing risky Stage 2 sets appearsCompositeOrMultiMoment
SKETCH_MOMENT_AMBIGUOUS Sketch could show more than one moment of the shot risky defined in the catalog but nothing raises it today — no code path emits this. Treat as reserved

A reference image is missing or wrong (character/location/prop images)

All model-decided.

Code One-liner shown to the user Default severity Observed
CHARACTER_REFERENCE_MISSING Character has no reference image blocker never fired in 31 shots
ENVIRONMENT_VARIANT_MISMATCH Location in sketch doesn't match in Location variant blocker risky 3 (blocker 0)
PROP_REFERENCE_MISSING Prop has no reference image risky risky 5 · blocker 1
REFERENCE_SKETCH_INCOMPATIBLE A reference image can't work with this sketch composition risky risky 3 · blocker 1

The text fields contradict each other (no images involved)

Read this before using these codes for anything. Stage 1's code field has no enum in the response schema (stageSchemas.ts:57 — code: { type: 'string' }), so the model writes whatever string it wants. Measured over 31 shots: 14 text conflicts, 14 distinct invented codes, and not one of them matched the catalog. Every one fell through resolveIssueTemplate() to the generic one-liner "Shot properties contradict each other."

Code One-liner shown to the user Default severity Observed
TEMPORAL_PROGRESSION_CONTRADICTION Start, middle and end don't form one coherent shot blocker never fired
TEXT_FIELD_CONTRADICTION Shot properties contradict each other risky never fired
CAMERA_TAXONOMY_DETAILS_CONFLICT Camera property contradicts its own details risky never fired
FRAME_NOT_STATIC Frame description contains motion, not a single moment minor never fired
DIALOGUE_ATTRIBUTION_AMBIGUOUS Dialogue speaker is unclear for this shot minor never fired
whatever the model invents — LOCATION_CONTRADICTION, dialogue_speaker_unclear, eye_contact_conflict, car_position_contradiction, extra_boarding_implication_in_camera_movement_details, … Shot properties contradict each other (generic fallback) as reported by the model 14 of 14 text conflicts

Consequences for the frontend and for analytics:

  • Every text conflict currently shows the same generic one-liner, whatever it's actually about. The specific problem is only in description. If the UI shows one-liners collapsed by default, text conflicts read as identical rows.
  • Text-conflict codes are not countable. They're free prose, so per-code dismissal analytics won't work on them until Stage 1 gets a code enum. Backend fix, not a frontend one — tracked, not done.
  • Stage 3's codes are enum-locked (stageSchemas.ts:230-243), so everything in the tables above this one is reliable.

Informational — never shown, analytics only

Code Meaning Severity Observed
DETAIL_DELEGATED_TO_REFERENCE A detail isn't in the sketch because the reference image carries it (that's fine) info Stage 3 can emit it; never observed in 31 shots
SKETCH_CONNECTION_INFERRED The system inferred which moment the sketch shows info defined but nothing raises it — reserved

System

Code One-liner shown to the user Severity
VALIDATION_INFRASTRUCTURE_FAILED Input check could not complete — try again blocker (deterministic)

severity — how bad it is

Value Meaning Surfaced in UI as
blocker The image will clearly be wrong; generation is gated Conflict row in the banner; Generate button disabled; no "Not a conflict" button (in the design: the red banner tier)
risky Likely wrong interpretation Conflict row in the yellow banner, dismissible
minor Small deviation, probably fine Conflict row in the yellow banner, dismissible
info FYI only (e.g. "detail delegated to reference") Not shown at all — logged for analytics only

shortMessage — the one-liner

Values One fixed sentence per catalogued code, e.g. SKETCH_CAMERA_MISMATCH → "Shot size in sketch doesn't match in Shot size property". Never model-written — the same code always reads identically. Uncatalogued codes (all Stage-1 text conflicts today) get the generic "Shot properties contradict each other"
Surfaced in UI as The banner row text ("5 conflicts found" list) and the amber text on the affected input card

description — what the system found

Values Free prose written by the model for THIS shot, e.g. "The sketch is framed wider than the described extreme close-up…"
Surfaced in UI as "What the system found" in the expanded view (today: hover tooltip on the row; design: the expandable panel)

suggestion — the proposed fix, in words

Values Free prose, e.g. "Tighten the crop so the phone is the dominant subject and remove the clock/lamp from frame." Absent when no safe fix exists
Surfaced in UI as "Proposed fix" in the expanded view

proposedFix — the fix as a machine-applicable patch

Values { path, oldValue, newValue, reason, confidence }. path is always one of the five text fields Stage 1 can rewrite — description, actingInstructions, initialFrameInstructions, middleFrameInstructions, finalFrameInstructions. It is never an enum field like shotSize, and only Stage-1 text conflicts ever carry one (findFixForFields, preGenValidation.service.ts:346). newValue is a full rewritten paragraph, not a token — 10 of 118 conflicts had one in the measured scenes
Surfaced in UI as Nothing yet (V0). Stored so a future one-click "Fix with AIDA" button can apply it without re-running anything. Because newValue is a long paragraph, a one-click apply will need a diff view, not an inline chip

dismissible — can the user wave it away?

Value Surfaced in UI as
true (risky/minor) The "Not a conflict" button appears on the row
false (blockers) No button — the only way forward is "Go back and resolve"

fieldPaths — which inputs are involved

Values List of field/input names. Two different naming styles, depending on the stage: Stage 1 and the deterministic checks use real camelCase field names (["sketch"], ["dialogue", "actingInstructions"]); Stage 3 uses uppercase section labels from its own prompt, not field names — ["FRAME DESCRIPTION", "CAMERA PROPERTIES", "STAGE 2 SKETCH OBSERVATION"]. STAGE 2 SKETCH OBSERVATION in particular is an internal pipeline artefact and maps to no user-visible input at all
Surfaced in UI as Amber border + inline conflict text on the matching input card (Sketch card, Location card, the Dialogue row…) — and where "Go back and resolve" should land the user. Match case-insensitively, map the section labels yourself, and treat anything unrecognised as "general" rather than dropping the conflict

confidence — how sure the checker is

Values 0.0–1.0. Mostly hardcoded, not a real model confidence: 0.9 for Stage-1 conflicts, 0.85 for all Stage-3 conflicts, 1 for the deterministic ones (preGenValidation.service.ts). Only SKETCH_MATCHES_NO_FRAME and SKETCH_COMPOSITE_MOMENTS derive it from a score
Surfaced in UI as Not shown in V0 — and it should stay that way until it means something

5. Also in the output (beyond conflicts)

  • frameCandidates — the start/middle/end frame descriptions Stage 1 derived from the shot's text. Not the creator's own fields — see the box in §3.2 before you render this.
  • sketchMomentScores — how well the sketch matched each moment, e.g. a real measured shot came back start 0.20 / middle 0.14 / end 0.05, meaning the sketch matched nothing well; anything under 0.4 also raises SKETCH_MATCHES_NO_FRAME.
  • sketchAlignmentConfidence — Stage 2's own confidence in the moment it picked (0.45 on that same shot).
  • resolvedInputs — the exact sketch + reference images the checker looked at (what the Visual references cards render).
  • timings — how long each stage took. Empty array on a cached verdict — the reliable way to tell a cache hit from a fresh run.
  • traces — the full stage-by-stage LLM output. Stripped by the controller before the response is sent (workbench.controller.ts:306), so the frontend never sees it. It's in the local analysis JSON only.

6. Known behavior to be transparent about

Measured over 31 shots across 2 real scenes (118 conflicts). None of this is speculation.

Detection is stable, attribution is not. A broken shot gets flagged every run. The specific dimension blamed can vary between runs — one run says "camera mismatch", another says "pose mismatch" for the same underlying problem. Doesn't affect whether the user is warned; does mean per-code counts drift.

Severity for the same code is not stable across shots. SKETCH_POSE_ACTION_MISMATCH came back blocker 6× / risky 10× / minor 3×. The Stage-3 prompt gives the model a single line of guidance on how to pick, so this spread is expected until the rules are tightened. Practical consequence: whether a shot is blocked or merely review_required can hinge on one model judgement call.

Stage-1 conflict codes are unusable as identifiers. No enum in the schema, 14 invented codes in 14 findings, all rendered with the same generic one-liner (§4).

Every measured shot was blocked. 31/31 — but SKETCH_ANALYSIS_MISSING fires on all 31 because sketch analysis had never been run on either scene, and it alone forces blocked. Discounting it: 2.81 conflicts per shot, 1.06 blockers per shot, 14 of 31 shots blocked and 3 fully clean. Read any "100% blocked" number with that in mind.

Dialogue barely contributes. 5 of 118 conflicts involved dialogue — 2 risky, 3 minor, 0 blockers.

Latency: ~100–110 s average on a fresh run, 194 s worst case measured. Cached verdicts are instant.

Unmeasured: the false-positive rate. Every conflict read in detail was defensible, but "defensible to the person who built it" is not "users agree." Per-code dismissal analytics is the instrument, and it only produces data once real users click.