# Beutl Agent Timeline From Shotlist

> Beutl Agent Timeline From Shotlist is an Agent Skill for documents published in the b-editor/beutl repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.

Build a Beutl timeline from a shot list through the Agent Editing Toolkit MCP tools.

- Skill name: `beutl-agent-timeline-from-shotlist`
- Category: [Documents](https://www.openparable.com/skill/documents/1)
- Source: https://github.com/b-editor/beutl/blob/HEAD/.claude/skills/beutl-agent-timeline-from-shotlist/SKILL.md
- Repository: b-editor/beutl (1,219 GitHub stars when collected)

## SKILL.md

# Beutl Agent Timeline From Shot List

Use this skill when an agent needs to turn a shot list, storyboard, or timed brief into a Beutl project through the Agent Editing Toolkit.

## Workflow

Author **storyboard-first**: plan a fine shot breakdown, build the **static** layout of every shot (static storyboard) with no keyframes and no effects, lock it, then add effects, then motion. Do not animate or add effects until the static storyboard reads correctly. The phases below map to that order.

### Phase -1 — Video type classification

Before classifying, check the intake. If the incoming request is terse (missing two or more of: subject specifics, target duration, mood or audience, explicit style/palette constraints, an asset inventory) or the user supplied reference images/video/URLs for the intended look, load `beutl-agent-brief-expansion` first and use its recorded `expandedBrief` as the brief for classification and Phase 0. Expanded-brief fields are derived defaults: user-stated constraints stay literal, and the derived `paletteDirection` feeds `derive_palette` inputs rather than replacing its outputs. Runs that started from brief expansion are **low-effort mode** and must run the visual-review convergence loop in Phase 4 (step 21a).

Classify the brief before Phase 0. Record `videoType` and a one-line reason in notes, then call `get_started(videoType)` and follow the returned type workflow. Supported values are `motion-graphics`, `footage-cut`, `slideshow`, `lyric-captions`, and `logo-intro`. If the brief requires source clips, photos, music, SFX, or fonts that are missing from the workspace, load `beutl-agent-asset-sourcing` before Phase 0, decide source-or-generate per asset, and record provenance in `assets/manifest.json` before placing media.

| videoType | Signals |
|---|---|
| `motion-graphics` | Authored graphics, kinetic type, promo/explainer/infographic motion, BPM or beat-grid language, layered background grammar, no source media inventory as the main driver. |
| `footage-cut` | User supplies video clips, says edit my clips/vlog/event/interview/B-roll, asks for trim/order/source audio/music bed, or needs narrative clip coverage. |
| `slideshow` | Photos/stills plus music, photo movie/album/memories language, per-photo duration grid, gentle Ken Burns motion, consistent transitions, caption read time. |
| `lyric-captions` | Lyrics, subtitles, captions, transcript, timestamp sync, per-line timing, readability and contrast as the core deliverable. |
| `logo-intro` | Logo animation, intro, stinger, bumper, brand mark, 3-10 seconds, single subject, anticipation/reveal/settle/hold arc. |

Pass the resolved `videoType` on every `preview_quality_risks`, `suggest_quality_fixes`, `evaluate_edit_quality`, and `final_preflight` call. Do not omit it after classification; omitted `videoType` intentionally means the legacy motion-graphics gate behavior. For `motion-graphics` and `slideshow`, pass the derived palette role colors to rendered Phase 4 quality calls as `paletteRoleColors` using `#RRGGBB` values, preferably as an array of `{ "role": string, "color": "#RRGGBB" }` objects. If an MCP client cannot send that complex array reliably, pass `paletteRoleColors` as a JSON string containing the same array. If `derive_palette` returned `#AARRGGBB`, drop the alpha channel before calling the quality tools.

| videoType | Existing phases as-is | Replaced or specialized phases | Skipped unless requested |
|---|---|---|---|
| `motion-graphics` | Everything as written today. | None. | None. |
| `footage-cut` | Phase 1 staged `apply_edit`, `read_document_summary`, `measure_object_bounds`, Phase 4 still/quality/preflight. | Phase 0 creative direction/palette/background grammar becomes media inventory plus cut list unless authored graphic overlays are requested; when required clips are missing, load `beutl-agent-asset-sourcing` and record `assets/manifest.json` provenance before placing media; `shotBreakdownPlan` becomes clip in/out/order/Start/Length; audio handling is explicit. | `derive_palette` and `get_background_grammar` unless graphic overlays are requested; motion-graphics density/beat-grid plan conformance. |
| `slideshow` | Phase 1 static storyboard, measured captions, staged `apply_edit`, Phase 4 still/quality/preflight. | Phase 0 becomes image ordering, per-photo duration grid, and transition vocabulary; Phase 3 uses minimal Ken Burns-style scale/translate per photo. | Background grammar and dense foreground layer planning unless captions/backing graphics need it; BPM beat-grid checks. |
| `lyric-captions` | Phase 1 layout, measured text/backing plates, Phase 4 still/quality/preflight. | `beatGridPlan` becomes a timestamp sync table; `shotBreakdownPlan` becomes one Element per line/caption with Start/Length from the sync table; type roles are hero line/echo/credit. | Motion-graphics background richness and density planning; BPM-driven tempo targets unless the brief separately requests them. |
| `logo-intro` | Phase 1 static end-frame build, Phase 2 effects, Phase 3 motion authoring, Phase 4 still/quality/preflight. | `shotBreakdownPlan` collapses to one 3-10 second shot with anticipation/reveal/settle/hold; storyboard subdivision reviews the internal motion arc instead of adjacent cuts. | Multi-shot cut-continuity planning, shot-count expectations, BPM tempo planning unless explicitly requested. |

### Phase 0 — Direction and shot breakdown

1. Author the creative direction yourself. `list_creative_directions` is optional divergence stimulus, not a menu to pick from.
   - Decide the concept, base hue, tonal seed, harmony scheme, type system, motion vocabulary, and shot structure from the brief (or from scratch when the brief is vague) before leaning on any tool. Do not anchor on returned seed names.
   - Record the derivation reason before any color or background authoring: why this subject, audience, mood, keywords, and delivery surface lead to this hue family, tone, material vocabulary, and motion vocabulary. Unjustified choices are disallowed; if you cannot explain a hue/tone/motion choice, derive again instead of using it.
   - Call `list_creative_directions` (pass a fresh `seed` each run to vary the stimulus) and read its `recentToAvoid` list. Your direction MUST differ structurally from those recent fingerprints: change the dominant motion verbs, layout, palette family, and type treatment, not just the words. Do not default to the same look every run (e.g. hero-glow-on-dark + dashed selection-marquee + magnetic letter-spacing) — that repetition is the monotony this step exists to prevent.
   - Call `derive_palette` with `baseHueDegrees`, `tonalSeed`, `harmonyScheme`, the recorded `derivationReason`, and `structuralSignature`. Resolve any hue-band or structural repeat warning by changing the direction, or record a concrete reason why the repeat is intentional for this brief before continuing.
   - Call `get_background_grammar`, then instantiate the grammar in notes: one base layer, one required depth layer, optional second depth layer, one motion slot, and the three depth bands `background`, `midground`, and `foreground`. Concrete values come from the brief and derived palette, not from the grammar ranges alone.
   - The required workflow is: derive direction with recorded reason -> `derive_palette` -> instantiate background grammar. Any deviation, including hand-picked colors, skipped grammar, missing depth band, static background, or ignored repeat warning, requires a recorded reason in notes before `apply_edit`.
   - If the user prompt specifies concrete constraints (motif, style, palette, message, audience, subject), keep them literal; use the stimulus only to make the result less generic.
   - Once the concept is locked, call `record_creative_direction` with its fingerprint (concept label, palette roles, motion verbs, structural signature) so future sessions steer away from it.
   - In notes, record the authored concept label, derivation reason, derived palette roles/hue band, background grammar slots, motion verbs, structural signature, any stimulus names you used, `derive_palette` warnings, and how you diverged from `recentToAvoid`.
   - Map the concept into your own named Beutl elements/objects. Do not reuse returned seed names as Element/Object names.
   - For unconstrained briefs, keep project, still, and video basenames neutral, such as `project.bep`, `preview.mp4`, and `still-*.png`, or use the requested output directory slug. Record the concept name in notes instead of naming files after it.
2. Inventory source media before schema authoring for media-driven types, then call `get_schema` before authoring if the required drawable, media, or audio type is not already known.
   - For `footage-cut`, list every available clip, duration note, usable range, and audio role. For `slideshow`, list supplied photos or the needed image collection. For `lyric-captions`, list the music bed or the reason none is needed.
   - If any required clips, photos, music, SFX, or fonts are missing, load `beutl-agent-asset-sourcing`, follow its license/provenance contract, and write every downloaded or generated asset to `assets/manifest.json` before `apply_edit`.
   - For organic heat, ink, glass, smoke, grain, caustic, or other procedural fields, call `list_effect_recipes` with a shader/organic intent and consider `SKSLScriptEffect` instead of stacking only blurred gradient shapes. Prefer SKSL over GLSL for low-context file sessions because it is CPU-safe in still renders.
   - GPU/stylize effects (GLSL, `PixelSortEffect`, `ColorShift` on split-character text) are render-guarded to skip degenerate targets rather than crash the renderer, so use them for richer looks when wanted — and they run on the bundled SwiftShader software fallback when no hardware GPU is present (slower, not skipped), so always confirm the result with `render_still` before relying on them. Do not over-restrict to a single safe effect; varying the effect vocabulary is part of avoiding monotone output.
   - For particle-like density (sparks, dust, confetti, floating tokens, glyph debris), use the real `ParticleEmitter` drawable (`get_schema type=ParticleEmitter`) instead of faking a swarm with many ellipse Elements — fake particle swarms also trip decorative-shape review. Its animatable surface covers `EmitterShape` (point/line/circle), `EmissionRate`, `Lifetime`, `Speed`/`Direction`/`Spread`, `Gravity`, `TurbulenceScale`, size/color/opacity over particle life, and `ParticleDrawable` — any Drawable (a glyph fragment, a `GeometryShape`) can be the emitted sprite.
   - For music-driven briefs, the audio-reactive drawables `AudioWaveformDrawable`, `AudioSpectrumDrawable`, and `AudioSpectrogramDrawable` render real waveform/spectrum motion from an audio source with pluggable shape styles (bar, radial, mirrored bars, line, filled area, dots, block). Use them for lyric videos, visualizers, and beat-driven accents instead of hand-animating fake meters.
3. If source-code reading is allowed, use `beutl-agent-source-grounding` before authoring layout, transform, bounds, text measurement, render scale, effect-unit, reconciliation, or live-session semantics.
   - This is mandatory when the task mentions centered placement, coordinates/origin, `TranslateTransform`, `TransformOrigin`, backing plates, object bounds, render/export range, or when a rendered/user-observed result contradicts the plan.
   - Read `.claude/skills/beutl-agent-source-grounding/SKILL.md`, then use narrow `rg`/read passes over the source and tests it identifies.
   - Record a `sourceGrounding` note with `assumption`, `evidence`, `rule`, and `uncertainty` before the first relevant `apply_edit`.
   - If the user explicitly forbids source reading, skip this step and record that limitation.
4. Before authoring, record a quality preflight plan in notes:
   - `directionContract`: state the objective, audience, emotional temperature, brand posture, delivery surface, one-sentence promise, and the recorded derivation reason for hue, tone, material, and motion vocabulary. The reason must tie the subject/brief to the palette seed and motion vocabulary; unjustified choices are disallowed.
   - `messageHierarchy`: name the primary message, secondary emphasis, and supporting/caption information for each shot.
   - `textCasePlan`: use Title Case or sentence case by default; do not use long all-caps text unless the user explicitly asked for it.
   - `typographyRolePlan`: assign type roles such as hero, secondary, caption, label, and texture text before choosing sizes.
   - `readTimePlan`: keep fast-beat copy to a word, phrase, or symbol; hold or split longer text.
   - `beatGridPlan`: when a music bed file exists, call `analyze_audio_rhythm` before authoring and record `estimatedBpm`, `confidence`, `beatTimesSeconds`, and `strongOnsetTimesSeconds`; anchor Element boundaries, accent keyframes, and transition hits to those measured beats. If no music bed exists, convert explicit BPM or fast-tempo language into a nominal beat grid instead. For 120-140 BPM, default to 130 BPM when unspecified: 1 beat is about 462 ms, 2 beats about 923 ms, and 4 beats about 1.85 s. Plan visible foreground changes every 1-2 beats, normal foreground holds around 2-4 beats, no foreground event gaps longer than 4 beats, and only named final resolves/background textures may hold longer.
   - `highTempoDensityPlan`: for 120-140 BPM or roughly 1.5s shots, keep hero text to 1-3 words and supporting labels to 2-4 word tokens. Add density through nodes, particles, strokes, texture, accent motion, and secondary shapes rather than long copy. Do not count background-only drift as foreground tempo.
   - `shapeBudget`: reserve `RectShape` for full-frame/background plates or deliberately plain geometry; use rounded rectangles, ellipses, paths, media, strokes, or procedural texture for foreground structure. Do not leave a large persistent foreground `RectShape` behind multiple text beats unless it is an intentional named text backing plate with matching timing and measured padding. Do not use abstract glint/glow/aperture/lens ellipses as foreground decoration; replace them with parseable systems such as strokes, particles, letter fragments, editor/timeline marks, masks, media, or procedural texture.
   - `elementStructurePlan`: one ordinary timeline `Element` owns exactly one `EngineObject`. Multiple objects inside one `Element` are allowed only when that `Element` contains an `IFlowOperator` such as `DrawableGroup`, `DrawableDecorator`, `SoundGroup`, or `Scene3D`; otherwise split content into separate Elements. Choose `DrawableGroup` for multi-child grouping (camera rigs, portal intake); choose `DrawableDecorator` when the point is applying one shared transform/effect/opacity/blend to its children as a single composited unit (group fade, shared blur/shadow) — the difference is subtle, so source-ground it when the composited result matters.
   - `roleTagPlan`: name important objects/elements with role tags such as `[role:background]`, `[role:text-backing]`, `[role:decorative]`, or `[role:camera-rig]` so MCP quality tools can distinguish real text plates from decorative accents and camera rigs from content groups.
   - `shapeIntentPlan`: every large or animated foreground shape must have a clear role, purpose, and motion intent in the Element/Object name, such as `[role:decorative] beat sweep`, `[role:text-backing] title plate`, or `[role:background] surface`. Do not create anonymous blobs, panels, abstract light ellipses, or shapes whose job cannot be named in viewer-visible terms.
   - `paletteRoles`: use the `derive_palette` output roles (`bg-base`, `bg-accent`, `foreground`, `text-primary`, `accent`) and record the contrast checks. Do not hand-pick alternate colors unless the reason and contrast are recorded. Avoid dark teal plus cyan/magenta unless requested. For ambient/aperture/glow backgrounds, instantiate `get_background_grammar` with at least three depth bands and at least three gradient falloff stops or a real Blur/SKSL/procedural texture so color boundaries do not read as hard bands.
   - `backgroundGrammarPlan`: record the selected base layer, depth layer A, optional depth layer B, motion slot, and which authored Elements will satisfy background/midground/foreground depth bands.
   - `textPlatePlan`: if text needs a backing plate, plan matching Start/Length, centered transforms, and padding for the named `[role:text-backing]` text/plate pair. Decorative light slashes, glass bands, and texture plates should be tagged `[role:decorative]`, shot-limited, lower-Z background/surface elements, or non-rectangular/stroke/procedural treatments so quality review does not misclassify them as text backing plates.
   - `effectIntentPlan`: name the job of each effect chain: material texture, hierarchy separation, transition energy, color grade, or text legibility.
   - `shotBreakdownPlan`: derive the shot/beat count from duration × tempo before authoring (for 120-140 BPM, default 130 BPM: a 30s piece is about 65 beats). Plan a visible foreground event roughly every 1-2 beats, so a typical 30s high-tempo piece is **tens of fine shots, not 6-8 coarse ones**. Enumerate every shot with an index, `Start`, `Length`, primary focal point, primary message, role, **planned foreground element count, and planned hold seconds**. Subdivide long holds into distinct beats instead of letting one shot span many beats; only named final resolves and background textures may span multiple beats. This enumerated breakdown is the source of `Element` boundaries in Phase 1.
   - `quantitativePlanSheet`: turn `shotBreakdownPlan` into a numeric target table on the same four axes the quality tools measure later (shot count, edits per second, hold seconds per shot, foreground elements per shot), so the plan can be checked against actuals before export. The foreground-elements-per-shot target must be passed to `evaluate_edit_quality` / `final_preflight` as `plannedForegroundElementsPerShot` so `metrics.layerDensity` can compare authored layer density against the plan. **Set every target with a deliberate 2-3× margin above the gate minimum, because execution always shrinks the plan — planning to the bare floor guarantees a below-gate result. Do not plan to the minimum; if a target only just clears the floor, raise it.** Record:
     - **Shot count**: `beats = duration_sec × BPM / 60`; target ≥ 2-3× a coarse 6-8 baseline AND at least one distinct shot per 1-2 beats — take the larger. A 30s high-tempo piece plans roughly 30+ fine shots, never 6-8.
     - **Edits per second**: target timeline events/sec ≥ 2-3× `metrics.tempo.RequiredTimelineEventsPerSecond`; record the expected total timeline-event count that produces it.
     - **Foreground elements per shot**: target ≥ 2-3 foreground layers per shot (2-3× the single-element structural floor). Single-element shots are forbidden except a deliberately minimal named final resolve.
     - **Hold seconds per shot**: holds are a ceiling, so keep planned holds at 1/2-1/3 of the overlong-hold threshold (the 2-3× margin applied to an upper bound); normal foreground beats stay near 2-4 beats and only named resolves/background textures hold longer.
   - `compositionPlan`: define one primary focal point per enumerated shot and how grouping, alignment, scale, color, and repetition support it.
   - `motionContinuityPlan`: define reveal, development, and resolution phases plus how boundaries are bridged.
   - `cameraPlan`: Beutl's 2D pipeline has no scene camera, so camera (viewpoint) work must be planned explicitly as animated rig transforms — a piece where every shot is a static full-frame composition swapped by cuts reads as a slide deck, not motion graphics. For each shot or shot run, record the camera treatment: `locked` (deliberate static frame), `push-in`/`pull-back` (rig `ScaleTransform` around the focal point via `TransformOrigin`), `pan`/`tilt` drift (rig `TranslateTransform.X/Y`), `whip-pan` (fast rig translate + blur as a cut bridge), `roll` (rig `RotationTransform`), or `parallax` (per-depth-band rigs translating at different amplitudes, background slowest). A camera move is authored on a named `[role:camera-rig]` `DrawableGroup` that parents the shot's content; there is no automatic inverse, so moving the camera left/up means translating the rig right/down, and zooming in means scaling the rig up. Plan at least one non-locked camera treatment per shot run for `motion-graphics` briefs; an all-`locked` plan requires an explicit brief reason recorded in notes.
   - `cutContinuityPlan`: for every adjacent shot pair, record the planned visible bridge across the cut: an element crossing the cut, a camera move continuing across it (matched push-in or whip-pan from `cameraPlan`), a sweep, shared background continuity, overlapping transform/opacity ramp, or a deliberately documented hard-cut reason.
   - `transformIntentPlan`: for every rotated moving object, state whether motion is in screen space or local/rotated space. For screen-space motion, plan static orientation transforms before the animated `TranslateTransform`; for local-axis motion, state the intended axis and verification sample.
   - `verificationSamples`: choose at least three still times plus the motion/quality review sample set.

#### Contrast Exemplars - derive, don't copy

These examples describe relationships to derive with `derive_palette`; they are not literal palettes to reuse.

- Good: `text-primary` is much lighter than a dark `bg-base` and still clears the 4.5:1 floor against the brighter `bg-accent`; the saturated `accent` is small and clears 3.0:1 against `bg-base`.
- Good: on a light tonal seed, `text-primary` is a dark low-saturation role, `foreground` is the readable material color, and the bright accent is reserved for small motion cues.
- Bad: cyan text on a pale blue or bright yellow background relies on hue difference but fails luma contrast.
- Bad: three fully saturated roles at similar lightness compete; mute support colors and let one accent carry saturation.
- Bad: dark teal base plus cyan and magenta neon is only allowed when the brief explicitly asks for that look and the reason is recorded.

### Phase 1 — Static storyboard

Build the static layout of **every enumerated shot** with NO keyframes and NO effects. The deliverable is a readable static storyboard: correct composition, hierarchy, typography, color, and layering at each shot's representative frame.

5. Create or attach a session:
   - Stdio/headless: `create_project` or `open_project` with a `.bep` project path. Paths without an extension are normalized to `.bep`; `.beutl` is reserved for exported project packages.
   - Live editor: `attach_active_editor`. In the in-app host, `open_project`/`create_project` open the project in the running Beutl editor itself (the editor holds a single open project) and return a LiveEditor session — opening a project different from the one already open is rejected; attach or have the user close it first.
6. If live attach fails and the task allows headless output, switch to the stdio/headless `create_project` route rather than creating a custom generator.
7. When an output directory is requested, create/update `notes.md` there before the first edit and after every `apply_edit`, `save_project`, `render_storyboard`, `preview_quality_risks`, `suggest_quality_fixes`, `render_still`, `evaluate_motion_variation`, `evaluate_edit_quality`, `compare_revisions`, `final_preflight`, and `export_video` result. Record success/failure, change count or verdict/path, and the next action. While drafting a large patch before the next tool call, append a short heartbeat note every few minutes with the current stage and blocker risk.
8. Call `read_document` and keep the returned `schemaVersion`.
9. Build the **static layout** as a declarative document (Phase 1 — no motion, no effects):
   - Use PascalCase property names exactly as returned by `get_schema`.
   - New timeline `Elements` require `$type: "[Beutl.ProjectSystem]:Element"`.
   - Use stable `Id` handles when modifying existing elements.
   - Omit `Id` only for genuinely new elements/objects so the toolkit can mint one.
   - Do not add a second `Object` to an ordinary existing `Element`. To place another visible item, create another `Element` with its own single `EngineObject`. Add multiple `Objects` to one `Element` only for an intentional `IFlowOperator` flow chain such as `DrawableGroup`, `DrawableDecorator`, `SoundGroup`, or `Scene3D`; keep the parent `Element.Id` and omit `Id` only for genuinely new child objects in that flow chain.
   - Keep element `Start`, `Length`, and layer/Z values consistent with the enumerated shot breakdown from `shotBreakdownPlan`.
   - When `cameraPlan` assigns a shot a camera move, structure that shot for it now with a named `[role:camera-rig]` rig Element, choosing one of two patterns so Phase 3 animates one rig transform instead of retrofitting per-element motion:
     - **Portal (timeline) rig — preferred for multi-element shots**: keep the shot's content as ordinary one-object Elements on contiguous ZIndex rows, and put the rig Element directly below them (lower ZIndex) with `Objects` = a `PortalObject` whose `Count` spans the content rows, then a `DrawableGroup` with empty `Children`. `Count` is an inclusive ZIndex span, not an element count: the portal pulls every active Element with ZIndex in `rig+1`..`rig+Count` into the group, so content keeps per-element `Start`/`Length` and stays visible as timeline layers to quality metrics. Keep the pulled rows ZIndex-contiguous and time-aligned with the rig — pulled Elements render ungrouped whenever the rig Element is not active. Fetch `get_examples` for `insert-camera-rig-portal`.
     - **Nested rig — for a small cluster that shares the shot's timing**: parent the shot's drawables inside the `DrawableGroup`'s `Children` (nested children have no `Start`/`Length` of their own); the `PortalObject`'s `Count` stays `0`. Fetch `get_examples` for `insert-camera-rig-push-in`.
     In both patterns the `Objects` array must contain the `PortalObject` entry immediately before the flow operator — a bare `IFlowOperator` is rejected by `apply_edit`. The same portal+flow grouping works for the other `IFlowOperator`s (`DrawableDecorator`, `SoundGroup` for audio, `Scene3D`). Author the rig's transform statically at the shot's end-state framing in this phase, and keep locked full-frame background plates outside the rig (below the portal's ZIndex range) — that separation is also what makes parallax possible.
   - Author only static property values in this phase — no `KeyFrameAnimation`/`KeyFrame` (that is Phase 3) and no `FilterEffect` (that is Phase 2).
   - For default-aligned `TextBlock` and shape objects, treat `TranslateTransform(0, 0)` as centered in the scene; `TranslateTransform(x, y)` is an offset from the scene center. Do not use half-frame coordinates such as `(960, 540)` to center content in a 1920x1080 scene unless `AlignmentX=Left`/`AlignmentY=Top` was deliberately selected and source-grounded.
   - Author `GeometryShape` path coordinates with the artwork's top-left at `(0, 0)` (all coordinates non-negative): the drawn center lands at the alignment-resolved center plus the path bounds origin, so a path centered on `(0, 0)` renders up-left by half its size and scene-absolute coordinates shift by their full offset. When a path cannot be normalized, add a static `TranslateTransform(-boundsX, -boundsY)`; `measure_object_bounds` reports `geometryBoundsOrigin` and `preview_quality_risks` flags uncompensated offsets as `geometryPathOffset`.
   - Use `measure_object_bounds` after creating or modifying layout-sensitive text, shape, and backing-plate pairs to confirm render-node size, scene-space center, transformed bounds, and padding before relying on still renders.
   - If you only need the required container shape, fetch the targeted `insert-new-element-skeleton` example; do not inspect a full-scene starter just to learn `$type` placement.
10. Apply the static layout in small `apply_edit` stages that map to your enumerated shot breakdown — background/surface, then primary structure/shapes, then typography, then text backing plates — using static property values only (no motion, no effects yet). Inspect `valid`, `changes`, `validation`, and `createdIds` after each stage before continuing. Pass `quiet: true` for large staged patches.
11. If `apply_edit` returns `validation_rejected`, `unknown_type`, stale handles, invalid animation discriminator tokens, or fallback-object guidance, fix the patch from `get_schema`/`get_examples`/`read_document` and retry only that stage. Do not invent shorthand values for colors, pens, animations, brushes, transforms, or effects. Do not silently fall back to cut-only timing after a keyframe failure unless the user explicitly accepts that reduced motion model.
12. For file sessions, call `save_project` after every successful major `apply_edit` before continuing to the next stage; omit `session` unless you are deliberately disambiguating an older session id. For LiveEditor sessions, `save_project` should report that saving is not required/supported; record that message instead of treating it as a blocker.
13. After each major stage, verify with `read_document_summary`. Compare every expected element name/role from your synthesized scene plan against the actual elements and revise before rendering unless the omission is recorded with a concrete reason. If any object has `isFallback: true`, stop rendering and fix the patch from schema because fallback objects are placeholders, not usable visuals. Audit every object whose summary shows nested transform animation plus a static rotation/skew/scale: confirm the `TransformGroup.Children` order still matches `transformIntentPlan`, and patch the order with `$before`, `$after`, or `$index` before rendering if it does not. Also audit object counts: any ordinary Element with multiple objects must be split unless it contains a named `IFlowOperator`.
14. Verify the static storyboard before adding any effects or motion:
   - Call `render_storyboard` to render one still per enumerated shot plus a contact-sheet PNG of the whole storyboard. Review it as a static storyboard: every planned shot present, one clear focal point per shot, readable typography, the intended layering and color, and correctly aligned text/backing-plate pairs. For a scene with many Elements the synchronous call can exceed the MCP client request timeout; pass `background: true` to get `{ status: "running", jobId }` immediately, then poll `read_render_job(jobId)` until `state` is `completed` (its `result` holds the storyboard payload) — do not issue `apply_edit` while a background render is running; call `cancel_render_job(jobId)` to abort a background render you no longer need.
   - For a continuous single-shot piece whose Element boundaries do not create useful auto shots, pass explicit `timeSeconds` to `render_storyboard` instead of hand-assembling contact sheets from `render_still`. Choose representative times for opening, reveal/development beats, settle, and final hold; use `subdivisionLevel:1` or `2` to inspect the motion arc between those anchors.
   - Run `preview_quality_risks(videoType:<resolved>)` (it does not evaluate motion). Of its categories, only `elementStructure` can fail the gate; `shapeIntent`, `decorativeShapeClarity`, and `gradientFalloff` are advisory, but fix their major findings before still rendering when they reflect a genuine accident (not a recorded creative choice). For multiple related issues, call `suggest_quality_fixes(videoType:<resolved>)` and apply the smallest repair. For high-tempo promos, set a `styleProfile` such as `high-tempo-promo`, `kinetic-type`, or `high-tempo-promo 130bpm`.
   - Run `evaluate_edit_quality(videoType:<resolved>, staticLayout:true)` — the storyboard-phase gate. It skips motion checks, so the motionless storyboard is judged on composition, typography, readability, and structure only. Resolve `critical`/`major` issues before leaving this phase.
   - Use `measure_object_bounds` for any text/backing-plate pair that looks misaligned in the contact sheet.
   - Do NOT run `evaluate_motion_variation`, `evaluate_edit_quality` without `staticLayout`, or `final_preflight` in this phase — those are motion-phase gates and will false-block a static storyboard (identical frames read as zero motion).
   Iterate Phase 1 until the storyboard reads correctly; only then proceed.

### Phase 2 — Effects

15. Add effect chains onto the locked storyboard, one named job at a time. Do not start motion until effects read correctly on stills.
   - Every effect chain needs a named job: material texture, hierarchy separation, transition energy, color grade, or text legibility. Remove decorative stacks that do not serve one.
   - For organic heat, ink, glass, smoke, grain, caustic, or atmospheric fields, prefer `SKSLScriptEffect` (from `list_effect_recipes`) over stacking only blurred gradient shapes; SKSL is CPU-safe in still renders. Prefer SKSL over GLSL for low-context file sessions. The `fine-film-grain-field` recipe ships a monochrome film-grain shader; `organic-shader-field` ships a colored field shader — pick by intent, and `validate_shader` any custom SKSL before `apply_edit`.
   - GPU/stylize effects (GLSL, `PixelSortEffect`, `ColorShift` on split-character text) are render-guarded to skip degenerate targets rather than crash, and run on the SwiftShader software fallback when no hardware GPU is present (slower, not skipped); confirm every effect with `render_still`/`render_storyboard`. Vary the effect vocabulary instead of over-restricting to one safe effect.
   - For masked reveals, knockouts, alpha mattes, and wipes, use the real masking mechanisms rather than covering rectangles: every Drawable has an animatable `BlendMode` whose Porter-Duff modes (`SrcIn`, `DstIn`, `SrcOut`, `DstOut`, `Modulate`) composite it against the content below it in the same flow — scope the matte by putting mask and content inside one `DrawableGroup`/`DrawableDecorator` Element so it does not knock out the whole frame — and the `Clipping` FilterEffect (animatable `Left`/`Top`/`Right`/`Bottom`) is the rectangular wipe/crop primitive. Blend-mode matte behavior depends on flow order, so source-ground the exact compositing rule (`beutl-agent-source-grounding`) and verify with `render_still` before building a sequence on it.
   - For a true emissive glow/bloom (light that adds over the original, not a `DropShadow` fake), duplicate the drawable with `duplicate_object` passing `wrapInGroup=true`, then `apply_edit` the `additive-bloom` recipe (blur + `BlendMode` `Plus` + reduced `Opacity`) onto the returned `objectId` so the copy glows over the untouched original. `wrapInGroup=true` puts the original and copy under a `DrawableGroup` (an `IFlowOperator`) so `evaluate_edit_quality`'s `elementStructure` check stays clean; move the copy to a separate Element at a higher `ZIndex` instead when it needs independent timing or z-order. Lower `Opacity` or switch `BlendMode` to `Screen` for bright footage that blows out.
   - Re-verify with `render_storyboard`/`render_still` and `evaluate_edit_quality(videoType:<resolved>, staticLayout:true)` that effects serve their named jobs and did not break readability. Motion-phase gates still do not apply yet.

### Phase 3 — Motion

16. Add keyframes and animation on top of the locked storyboard and effects. Build the reveal, development, and resolution phases from `motionContinuityPlan`, and animate multiple property families (transform, opacity, brush/gradient, effect parameters, text spacing) — not just X plus opacity. Vary easing families per the 12 principles: use cubic/quintic ease-out for entrances, ease-in-out for moves, and reserve linear motion for deliberate mechanical travel. The easing catalog is much wider than cubic/sine — `BackEase*`/`ElasticEase*` give overshoot and anticipation, `BounceEase*` gives physical settles, and `SplineEasing` takes custom cubic-bezier control points; use them deliberately on accents (a bounce on every element turns cartoonish). Stagger related element starts by about 0.1-0.3 seconds and vary durations/directions so follow-through and overlapping action are visible. Apply motion in small `apply_edit` stages per beat and inspect `validation` after each; pass `quiet: true` for large patches.
   - Author the camera moves from `cameraPlan` first, before per-element accents: keyframe the `[role:camera-rig]` `DrawableGroup`'s `TransformGroup` children — `TranslateTransform.X/Y` for pans and whip-pans, `ScaleTransform.Scale` for push-in/pull-back (set `TransformOrigin` so the zoom centers on the shot's focal point), `RotationTransform.Rotation` for roll. Fetch `get_examples` for `insert-camera-rig-push-in` (nested rig) or `insert-camera-rig-portal` (timeline rig) and reuse its structure. Camera moves are slow and eased by default (a push-in of roughly 3-8% scale over the shot already defeats the static-slide feel); whip-pan bridges are the fast exception. For parallax, keyframe each depth-band rig's translate at a different amplitude (background slowest). Remember the inverse: camera-left = rig-right, camera-in = scale-up. For a true 3D dolly or orbit, use a `Scene3D` object and keyframe its `Camera.Position`/`Camera.Target` instead of faking perspective with 2D scale.
   - For perspective moves on 2D content (card flips, page turns, tilted reveals), use `Rotation3DTransform` (`RotationX`/`RotationY`/`RotationZ`, `CenterX/Y/Z`, and `Depth` — the perspective distance, default 500) inside the `TransformGroup` instead of faking depth with 2D scale alone.
   - For kinetic type, set `TextBlock.SplitByCharacters=true` (animatable) so glyphs composite individually — this is the enabling property behind "split-character" effects such as per-glyph `ColorShift` fringing or `PartsSplitEffect` shatter — and animate `TextBlock.Spacing` for tracking-in/out reveals. Stagger per-character timing rather than moving the whole block as one unit.
   - For line-drawing reveals ("the stroke draws itself on"), animate `Pen.TrimStart`/`Pen.TrimEnd` (0-100) on a `GeometryShape`/shape/text stroke; for marching-dash accents set `Pen.DashArray` (a static float list) and animate `Pen.DashOffset`.
   - Audio is authorable, not analysis-only: keyframe `Sound.Gain` (percent: 100 = unity, values above 100 amplify) for fade-ins/outs and ducking under narration, set `Sound.Effect` to an `AudioEffectGroup` with `DelayEffect`/`EqualizerEffect`/`CompressorEffect`/`LimiterEffect` children for echo/EQ/dynamics, and use `SoundGroup` (an `IFlowOperator` — the `PortalObject` pairing rule applies) to submix multiple audio Elements.
   - Decide the animation clock mode deliberately. With `UseGlobalClock=false`, `KeyFrame.KeyTime` is local to the owning timeline Element and should normally stay within `00:00:00`..`Element.Length`. With `UseGlobalClock=true`, `KeyFrame.KeyTime` is a scene timeline time and should intersect the visible Element range.
   - For explicit keyframes, fetch `get_examples` for `animate-float-property-keyframes` when animating an existing object, or `insert-new-animated-text-keyframes` when creating a new animated text object. Copy the concrete `KeyFrameAnimation<T>` and `KeyFrame<T>` discriminators from the example instead of inventing animation type names.
   - Before authoring multiple keyframed properties, make a small local keyframe helper snippet in your draft from the MCP example and reuse that exact JSON shape for every animated `Single`, `Boolean`, `Color`, `Size`, or transform property. Do not hand-type or manually Unicode-escape the generic discriminator strings; invalid tokens around `KeyFrameAnimation` or `KeyFrame` mean the helper is wrong and must be rebuilt from `get_examples` before continuing.
   - If `apply_edit.validation` contains a `Warning` for relative keyframes outside the Element local range, treat it as a timing bug unless the user explicitly asked for that state. Fix by either converting the keyframes to local times or setting `UseGlobalClock=true` when scene timeline times were intended.
   - For rotated moving shapes, do not animate only `TranslateTransform.X` and assume it will travel along the rotated visual axis. If the intended path is screen-space horizontal/vertical drift with a tilted object, put static orientation transforms such as `RotationTransform`, `SkewTransform`, or non-animated `ScaleTransform` before the animated `TranslateTransform` in the `TransformGroup`. If the intended screen-space path is diagonal, animate both X and Y as a vector. If the intended local-axis path depends on transform order, record that explicitly and verify the order with a rendered still/motion sample before export.

### Phase 4 — Motion verification and export

17. Re-render the storyboard with `subdivisionLevel: 1` after Phase 3 motion authoring; raise to `subdivisionLevel: 2` for suspicious gaps. READ the contact sheet, including every `kind: "inbetween"` frame, and check `cutEyeTrace` before judging continuity. For multi-shot types (`motion-graphics`, `footage-cut`, `slideshow`, `lyric-captions`), every adjacent shot pair must show the planned continuity: an element crossing the cut, a camera move continuing across it (matched push-in, whip-pan), a sweep, shared background continuity, an overlapping transform/opacity ramp, a deliberate hard-cut reason, or a consistent slideshow transition. Also verify the `cameraPlan` executed: shots planned as push-in/pan/parallax must show visible framing change between their in-between frames — identical framing on a non-locked shot is a rework finding. If the in-between frames look identical to the anchors except for an unplanned hard swap, treat it as a rework finding and add bridge animation or transition consistency. If `cutEyeTrace.exceedsEyeTraceBudget` is true and no planned bridge or hard-cut reason exists for that pair, treat it as a rework finding: add a bridging element, sweep, or focal-point realignment. For `logo-intro`, review the single-shot motion arc instead of cuts: anticipation, reveal, easing, settle, and the final hold. Record `cutContinuityActuals` or `motionArcActuals` in notes with pass/fail, evidence frame names, `cutEyeTrace` displacement when present, and the repair when needed.
18. Verify with `render_still` at representative shot boundaries. Treat any returned `warnings` as a blocker for export until you have either revised the scene or recorded why the warning is acceptable. In live-editor sessions, renders honor the editor's layer state — `TimelineLayer` lock/solo/audio-mute/video-mute and `Element.IsEnabled` — so if an expected layer is missing from a still, check those flags before diagnosing your own edit as the cause. For each still, record `visibilityAnalysis.visiblePixelRatio`, `foregroundPixelRatio`, `occupiedBoundsRatio`, and `maxQuadrantForegroundRatio`; compare `activeElements` against the planned visible elements; note the primary focal point, whether text/title elements are readable for their duration, whether effect chains still serve their named jobs, and whether foreground/background/accent density is present. Development and resolution stills should show at least three visible layer types, such as background/surface, primary motion, accent/detail, and typography; if text is present, it must have clear contrast against the background.
   - If a large decorative `RectShape` remains active behind several unrelated text shots, treat it as a likely text-background-fit problem before quality review. Limit it to the shot where it belongs, move it clearly into the background/surface role, or replace it with stroke/ellipse/path/procedural texture.
19. Run `evaluate_motion_variation` across 4-6 samples. If it reports `low-motion-variation` or `poor-frame-coverage`, or if the still review shows planned elements are never visible/readable, revise the edit.
20. Run `evaluate_edit_quality(videoType:<resolved>)` with the same sample set (the full motion gate; leave `staticLayout` off here). For motion graphics, pass `plannedForegroundElementsPerShot` from `quantitativePlanSheet`; for motion-graphics and slideshow flows, also pass `paletteRoleColors` from the `derive_palette` roles so `paletteBalance` can report the authored 60-30-10 area mix. Use an array of `{ "role": string, "color": "#RRGGBB" }` objects, or a JSON string containing that same array when the MCP client cannot send complex arrays reliably. When `beatGridPlan` came from `analyze_audio_rhythm`, also pass `beatTimesSeconds` so `audioSync` advisories can flag near-miss cuts. Pass `allowMinimalDensity=true` only when the brief explicitly asks for minimal/negative-space density and the deviation is recorded. Only gate-failing issues (`typographyReadTime`, rendered `typographyContrast`, `elementStructure`, `motionContinuity`, and supplied-plan `layerDensity`) block export; `shapeIntent`, `motionIntent`, `decorativeShapeClarity`, `gradientFalloff`, `tempoRhythm`, `paletteHarmony`, and `backgroundRichness` major issues are advisory and do not block. Revise and re-run until `passesQualityGate` is true; fix genuine-accident advisory findings when time permits, or record the explicit user reason for allowing an issue.
   - For motion-graphics deliverables, `animatedPropertyCount: 0` is a blocker even when `evaluate_edit_quality.passesQualityGate` is true. Add explicit transform, opacity, spacing, brush, or effect animation before export.
   - For `textBackgroundFit` issues involving decorative glass/light/texture rectangles, prefer a real design fix over suppressing the issue: constrain the rectangle's Start/Length to the intended beat, align it as a named backing plate with `measure_object_bounds`, lower it into the background, or replace it with a non-plate visual treatment.
   - For high-tempo/BPM briefs, inspect `metrics.tempo.RequiredTimelineEventsPerSecond`, `TimelineEventsPerSecond`, `RequiredTotalEventsPerSecond`, `LongForegroundGapCount`, and `LongestForegroundEventGapSeconds`. A scene is too slow if background motion hides sparse foreground changes or long foreground gaps.
   - After each revision `apply_edit` made in response to Phase 4 quality or visual-review findings, call `compare_revisions` before the next broad fix. Record a delta ledger in notes with metric deltas, issues resolved, issues introduced, `regression`, and the paired still paths. If `regression=true`, treat the introduced/worsened axis as a rework finding before export.
21. **Plan-conformance check (before finishing).** Before `final_preflight`/export, compare the authored result against your `quantitativePlanSheet` on the same four axes — not just the pass/fail gate. Gather actuals from `read_document_summary` for shot/Element count, `evaluate_edit_quality` `metrics.tempo` (`TimelineEventsPerSecond`, `SlowHoldCount`, `LongestForegroundHoldSeconds`), `metrics.layerDensity` (`Bands`, `AverageForegroundLayerCount`, `MinimumForegroundLayerCount`, `BandsBelowHalfPlannedForegroundLayerCount`), and `evaluate_motion_variation`. Confirm all four: actual shot/Element count ≥ planned shot count; actual `TimelineEventsPerSecond` ≥ planned edits/sec target (and ≥ `RequiredTimelineEventsPerSecond`); every hold ≤ its planned ceiling (no `SlowHoldCount` beyond named resolves); every measured time band's `ForegroundLayerCount` meets the planned density floor, with a Major `layerDensity` issue if authored density falls below half the supplied plan. Any axis short of the plan is a rework blocker — add the missing shots/events/layers and re-verify, or record a true minimal-density brief and pass `allowMinimalDensity`. Record a conformance summary in notes: per axis, planned vs actual vs pass/fail.
21a. **Convergence loop for low-effort runs.** When the run started from `beutl-agent-brief-expansion` (or the coordinator explicitly requests convergence), run `beutl-agent-visual-review` in its convergence loop mode after the deterministic gates pass and before `final_preflight`: iterate revision passes until every rubric axis scores ≥ 3 or the pass budget (default 3) is exhausted, phrasing every directive in the expanded brief's own concept vocabulary. An unconverged result hands off to the human with the delta ledger instead of exporting silently.
22. Prefer `final_preflight(videoType:<resolved>)` before export when the tool is available. For motion graphics, pass `requireAnimatedProperties=true` and `plannedForegroundElementsPerShot` from `quantitativePlanSheet`; for motion-graphics and slideshow flows, pass the same `paletteRoleColors` shape used in `evaluate_edit_quality`; pass `beatTimesSeconds` from `analyze_audio_rhythm` when a music bed was analyzed. Export only when `readyForExport` is true. If `final_preflight` is unavailable, use the separate `render_still` + `evaluate_motion_variation` + `evaluate_edit_quality(videoType:<resolved>)` sequence above.
23. Export a short preview with `export_video` when an encoder is available; if export is unavailable, record the reason in notes. Control output size with `crf` (0-51, higher = smaller; raise it to ~28-30 for full-frame grain or other hard-to-compress content) or `bitrate` (bits/s, ABR) — the two are mutually exclusive. For a long export, pass `background: true` and poll `read_render_job(jobId)` the same way as `render_storyboard`. In the final report, surface any CC-BY-SA share-alike obligations, user-supplied URL assets recorded as `license: "unverified"`, and all required attribution text from `assets/manifest.json`.
24. Save with `save_project` for file sessions after final revisions. For LiveEditor sessions, call `read_operation_status` or `save_project` once near the end if you need to report that the live edit is already applied but not file-saved by the toolkit.

## Motion Graphics Quality Bar

- Author storyboard-first: build and verify the static layout of every shot (Phase 1, via `render_storyboard` + `evaluate_edit_quality(staticLayout:true)`) before adding effects (Phase 2) or motion (Phase 3). A storyboard that does not read clearly will not improve by animating it.
- Use at least three timing phases: reveal, development, and resolution. Avoid a single continuous drift.
- After Phase 3 motion authoring, verify adjacent cuts with `render_storyboard(subdivisionLevel:1)` and raise to `2` for suspicious gaps on multi-shot types. In-between frames must show bridge motion, transition consistency, or recorded hard-cut intent, and `cutEyeTrace` must not exceed the budget without a planned bridge or hard-cut reason. For `logo-intro` or any continuous single-shot piece, pass explicit `timeSeconds` anchors and inspect the subdivided frames as a motion arc instead of a cut-continuity pass.
- Build fast tempo through contrast between quick accents and held readability beats. Do not make every layer move at the same speed.
- Avoid motion monotony: do not leave most keyframe transitions linear, and do not start every animated element at the same time with the same duration and direction.
- For 120-140 BPM briefs, work from a beat grid instead of the vague word "fast". At 130 BPM, 1 beat is about 462 ms, 2 beats about 923 ms, and 4 beats about 1.85 s. Plan enough foreground element boundaries and keyframes for the piece to read as fast in `tempoRhythm` metrics.
- Keep normal foreground beats near 2-4 beats. Longer holds are acceptable only for named background texture, ambient support, or a deliberate final resolve; add visible foreground events when readability requires a longer text hold. Background-only drift does not satisfy a fast-tempo brief.
- Animate multiple property families across the piece, such as transform, opacity, brush/gradient, effect parameters, and text spacing. Do not rely only on X movement plus opacity.
- Move the camera, not just the elements. A piece whose viewpoint never moves — every shot a static composition with only element-level animation, swapped by cuts — reads as a slide deck. Use `[role:camera-rig]` `DrawableGroup` transforms for push-ins, pans, parallax, and whip-pan cut bridges per `cameraPlan`; an all-locked-off piece needs an explicit recorded brief reason.
- Keep ordinary timeline Elements to one EngineObject. Multiple objects in one Element are reserved for `IFlowOperator` chains such as `DrawableGroup`, `DrawableDecorator`, `SoundGroup`, or `Scene3D`; otherwise split each visual object into its own Element.
- Every shot needs one primary focal point. Supporting text, marks, panels, and effects should sit lower in scale, contrast, timing, or density.
- Keep hero-scale typography to one primary message per beat; make captions, labels, and texture text visibly quieter before running `evaluate_edit_quality`.
- Readability is timed: short-lived text must be short, split across beats, or held longer. At roughly 1.5s per shot, use 1-3 words for hero text and 2-4 words or compact symbols for supporting labels.
- Maintain visual density: use layered background, foreground motion, accents, and typography/labels. A lone title over one moving shape is too sparse unless the brief asks for minimalism; verify with `metrics.layerDensity`, not only a manual object count.
- For fast promos, add perceived information through short typography, repeated non-rectangular nodes, particles, strokes, texture, and accent motion rather than adding long text.
- Use role tags consistently: `[role:background]` for full-frame surfaces, `[role:text-backing]` only for real measured text plates, and `[role:decorative]` for glass bands, slashes, glints, or rhythm marks that should not be interpreted as backing plates.
- Large or animated foreground shapes must expose role, purpose, and motion intent in names. If a shape's job cannot be stated as beat sweep, scan texture, pulse reveal, transition wipe, text backing, or another concrete intent, remove it before export.
- Do not use abstract foreground glint/glow/aperture/lens/glass ellipses as a quality shortcut. If viewers cannot parse what the shape represents without reading its layer name, replace it with strokes, particles, letter fragments, editor/timeline marks, masks, media, or procedural texture.
- For ambient/aperture/glow backgrounds, avoid hard two-stop falloff. Use at least three gradient stops, widen alpha/color transitions, add a real Blur/SKSL texture, or replace the shape with procedural surface texture.
- Use procedural texture when the concept is organic or atmospheric. A short `SKSLScriptEffect` on a broad shape is often better than many low-contrast blurred ellipses for heat, ink, glass, smoke, caustics, grain, or shimmer.
- Every effect chain needs a named job: material texture, hierarchy separation, transition energy, color grade, or text legibility. Remove decorative stacks that do not serve one.
- Give each major visual part a clear name in the patch so `read_document_summary` exposes the intended structure.
- Treat your synthesized scene plan as a completion checklist. A final scene that omits planned accent/density elements without a recorded reason is incomplete.
- After still renders, use `evaluate_motion_variation`; treat low adjacent-frame variation or persistent one-quadrant/sparse frame coverage as a failed self-check for motion graphics.
- For motion graphics, a passing rendered-difference check is not enough when the document has no explicit animated properties. If `evaluate_edit_quality` reports `animatedPropertyCount: 0`, revise the edit to add deliberate animation on transform, opacity, typography spacing, brush, or effect parameters before export.
- Numerical motion variation is necessary but not sufficient: planned elements must also be visibly present across representative stills, and text/title elements must be readable before export.
- A still that is mostly a smooth background after the reveal phase is not dense enough even if `evaluate_motion_variation` passes.
- Held title/logo sections need real continuous motion to pass `evaluate_motion_variation` (it requires ≥2% changed pixels at ≥48-per-channel delta between adjacent samples). Drift the held text and add a slow scale "breath"; a soft low-alpha ambient wash stays under the 48 delta and does NOT count as motion. `tempoRhythm` defaults to a 130 BPM target, so a calm 3-act brand/logo piece trips its minor advisories harmlessly — `passesQualityGate`/`readyForExport` stay true.
- Long all-caps text, overloaded visual hierarchy, unreadable short-lived copy, rendered low text contrast (`typographyContrast`), foreground RectShape dominance, abstract decorative light ellipses, hard ambient gradient falloff, flat single-layer backgrounds, unclear or arbitrary animated shapes, ordinary Elements with multiple EngineObjects, sparse layer density/depth coverage, sparse high-tempo event density, long foreground event gaps, overlong high-tempo foreground holds, misaligned text backing plates, dark teal/cyan/magenta palettes, low-scoring hue-wheel palettes, dense effect stacks without a named job, repeated card shadows, low temporal variation, and unmotivated hard cuts are quality failures unless explicitly requested by the user.
- `evaluate_edit_quality.passesQualityGate` must be true before final export for normal deliverables.

## Originality Rules

- For creative briefs, build an original timeline with small staged `apply_edit` calls; do not use `list_compositions`, `plan_composition`, or empty-scene examples as the default output path. To bootstrap from a non-template original starting point, `plan_original_scaffold` returns a one-call seed-derived scaffold (background/headline/subtitle/decorative foreground) whose placeholder copy and structure you then rewrite for the brief — it is an original starting point, not a reusable template.
- Treat `list_creative_directions` output as raw inspiration only. Do not copy returned seed names as the final concept title, Element/Object names, layer order, or file basename.
- Use composition templates only when the user explicitly asks for a template, starter, quick draft, or named template style.
- When a template is explicitly requested, pick a specific returned template name from `list_compositions`; do not rely on an implicit first template selection.
- Treat examples as schema snippets or fallbacks. Adapt their structure to the brief instead of copying a full starter scene unchanged.
- Avoid overused no-context motifs such as orbit rings, radar sweeps, map/atlas labels, signal nodes, dashboard bars, and dark teal cyan/magenta neon unless the user asks for them.
- Cross-session variety is a hard requirement: the same brief should NOT keep producing the same video. Before locking a direction, compare it against `recentToAvoid`, pass the structural signature into `derive_palette`, and deliberately change the structural language (motion verbs, layout grid, palette family, type treatment, transition style) from recent runs; then `record_creative_direction` so the next run can diverge too.
- Pass `quiet: true` to `apply_edit` for large staged patches; the full echoed change set can exceed the response size limit, so keep individual patches small and use the compact summary while authoring.

## Shot List Mapping

- Drive `Element` boundaries from the enumerated `shotBreakdownPlan`. One shot normally maps to one or more `Element` entries with explicit `Start`, `Length`, and `ZIndex`; each ordinary `Element` contains exactly one drawable/audio `EngineObject`.
- Use multiple `Objects` inside one `Element` only for explicit `IFlowOperator` flow chains such as `DrawableGroup`, `DrawableDecorator`, `SoundGroup`, or `Scene3D`; otherwise split compound visuals into separate Elements.
- A shot with a planned camera move maps to one `[role:camera-rig]` rig Element — either a portal rig (`PortalObject.Count = N`) pulling the shot's N contiguous content layers directly above it, or a nested `DrawableGroup` parenting the content as `Children` — with locked background plates as separate Elements below the rig, outside the portal's ZIndex range.
- Background plates should be lower `ZIndex`; titles, logos, and overlays should be higher.
- Prefer explicit durations over relying on media original duration unless the brief explicitly asks to preserve source timing.
- For repeated visual treatments, duplicate structure deliberately; do not rely on implied defaults when the brief gives concrete values.

## Merge-Patch Rules

- Arrays of objects with `Id` are id-keyed. A bare id-less array merges/appends into the existing members; it does NOT replace them.
- Use `{ "Id": "...", "$delete": true }` for removals.
- To wholesale-replace an id-keyed array in one patch (e.g. swap a `FilterEffectGroup.Children` chain instead of appending to it), make the FIRST element the sentinel `{ "$replace": true }`; the following elements rebuild the array in order (omit `Id` to mint fresh, or reuse an `Id` to keep that child), and `[{ "$replace": true }]` alone clears it. Replacement elements cannot also carry `$delete`/`$index`/`$after`/`$before`. Keep the group's own `Id` so only its children change.
- Use `$index`, `$after`, or `$before` for ordering; do not combine ordering directives.
- Unknown `Id` means stale handle; call `read_document` again instead of guessing.
- Existing parent with new flow child example: `{ "Elements": [{ "Id": "<existing-flow-element-id>", "Objects": [{ "$type": "<discriminator-from-get_schema>", "Name": "new-flow-child" }] }] }`. Use this only when the existing Element is an intentional `IFlowOperator` flow chain. Ordinary Elements should not receive a second Object.
- New Element example: `{ "Elements": [{ "$type": "[Beutl.ProjectSystem]:Element", "Name": "new-element", "Start": "00:00:00", "Length": "00:00:02", "Objects": [{ "$type": "<drawable-discriminator-from-get_schema>", "Name": "new-object" }] }] }`. New Elements and Objects omit `Id`.
- New flow-operator Element (e.g. a `[role:camera-rig]` `DrawableGroup`): the `Objects` array must pair a `PortalObject` immediately before the flow operator — `"Objects": [{ "$type": "[Beutl.ProjectSystem]:PortalObject", "Count": <N> }, { "$type": "<DrawableGroup-discriminator>", "Name": "[role:camera-rig] ...", "Children": [...] }]`. A bare flow operator without the `PortalObject` is rejected. `Count` is an inclusive ZIndex span, not an element count: every active Element with ZIndex in `rig+1`..`rig+Count` is pulled into the operator (leave `Children` empty then; empty rows contribute nothing). `Count: 0` pulls no timeline rows, so with the portal as the Element's first object the operator consumes only its nested `Children`; set `Clear: true` to explicitly discard earlier same-Element flow. Applies to all `IFlowOperator`s: `DrawableGroup`, `DrawableDecorator`, `SoundGroup`, `Scene3D`. See `get_examples` `insert-camera-rig-push-in` (nested) and `insert-camera-rig-portal` (timeline).

## Progress Watchdog

- Keep `notes.md` granular enough for another observer to reconstruct the route: every apply, save, render, evaluate, export, validation failure, and route change gets an entry.
- During long patch authoring between tool calls, update `notes.md` before the three-minute mark with a heartbeat such as `drafting stage N patch; next tool: apply_edit`; if you cannot do that, stop and report a blocker.
- If no tool success, saved project artifact, render/export artifact, or notes update happens for about three minutes while editing, stop and report the blocker/status instead of silently continuing.
- If the user or coordinator asks for status, call `read_operation_status` when available and respond immediately with the current session/source, last successful stage, and blocker before continuing.

## Safety Rules

- Keep values in documented ranges. If `apply_edit` reports coercion or rejection, adjust the request and retry the same small stage.
- Confirm destructive output overwrites only when the user explicitly asked for overwrite.
- Do not write outside `BEUTL_WORKSPACE`.

---

Source: https://www.openparable.com/skill/documents/beutl-agent-timeline-from-shotlist (OpenParable). Cite the canonical URL above.
