← Files PicsartARCHIVED FILE

skills/motion/references/MOTION-TROUBLESHOOTING.md

9.86 KB · Oct 10, 2026 · 18:02 UTC

↓ Download file

See the change to this file →

# Motion troubleshooting — symptom → cause → fix

When the designer says a pair "feels off," this maps the symptom to a concrete fix. It plugs into
the edit loop: **locate** the layer via `picsart_media_query_layout` → **apply** the fix via
`picsart_media_patch_scene` (or the `apply_*` tools) → **show** the re-rendered result. Never
"fix" it by describing the change; author it and show it. Easing/preset values come from
`get_capabilities`.

## "Feels robotic"
- Linear easing on spatial movement → use ease-out (entrance) / ease-in-out (on-screen).
- Straight path → add a 10–20px arc at the midpoint.
- Uniform timing → stagger siblings 50–100ms.
- Everything synced → offset starts/stops 50–150ms.
- No secondary motion → add a shadow, icon reaction, or subtle ambient.

## "Too slow"
- Duration over the element's budget → check the duration ranges (`MOTION-PRINCIPLES.md` §3 / `MOTION-PATTERNS.md`).
- ease-in-out where ease-out would do → ease-out feels faster.
- Too much anticipation → reduce to ~10% or remove.
- Stagger cascade too long → keep total <500ms.

## "Too fast / jarring"
- Below the minimum → modals 300ms+, page/screen transitions 400ms+.
- No easing → add ease-out at minimum.
- No resolution → add a 50–100ms settle at the end.
- Large move with no wind-up → add 100–200ms anticipation.

## "Feels cheap / flat"
- Only the primary moves → add a secondary (and ambient if it fits).
- Opacity-only → combine with position or scale.
- Same easing everywhere → vary primary vs secondary.
- No follow-through → child elements trail 50–150ms.
- No overshoot → add 3–10% (only in a playful/energetic register).

## "Too distracting"
- Too many things moving → the ⅓ rule (max ⅓ of elements active at once).
- Amplitude too large → reduce to the minimum that reads.
- Competing focal points → one focal element per beat; dim the rest.
- Ambient too prominent → keep it under ~20% of the primary motion's energy, and slower.
- No breathing room → 100–200ms pause between beats.

## "No personality / inconsistent"
- Default easing everywhere → apply the reel's personality register (`MOTION-PRINCIPLES.md` §6).
- Durations vary for the same element type → use the personality's duration range consistently.
- Entrance direction changes shot to shot → pick one origin and keep it.
- Mixed archetypes → commit to one for 90%+ of the reel.

## "Dropped frames / heavy"
- Animating width/height/margin → animate transform (position/scale) instead.
- Too many animated elements → keep under ~20 per frame.
- Heavy shadows/filters → simplify or pre-render.
- Everything firing at once → stagger to spread the load.

## "Content shows through a header / overlay" (occlusion)
A layer meant to *cover* moving content (sticky header, modal backdrop, overlay) but content is visible through it:
- **Covering layer is semi-transparent** (a gradient / low-opacity fill) → it overlaps but doesn't occlude → add an **opaque `color` backing** behind it (over the content, under the header text) so the content is fully hidden; keep the gradient only as a soft edge.
- **Wrong z-order** → the cover must be *above* the content it hides (later in `layers[]`).
- **Header band too short** → its opaque zone must span the full header height, not just behind the first line.
- **Verify at the *deepest* overlap** → check a `contact_sheet` frame when content is fully scrolled *under* the cover, not at the start — the leak only appears once content moves behind it.
- **Run `layout_lint` first — it catches most of this cheaply:** it flags overlapping boxes over time and is z-order aware, so it *will* surface a wrong-z-order cover or a box that shouldn't overlap. Its one blind spot is opacity: a *semi-transparent* leak reads as an intentional overlap and isn't flagged. So lint first, then verify occlusion on a rendered frame for the transparency case — the frame check is the follow-up, not the substitute.

## "A layer is blank in the preview — but the scene validates (and export looks fine)"
The asset is blocked in the **browser preview**, even though `validate_scene` passes and server-side
`contact_sheet`/`export` render it (**validates ≠ works; server ≠ browser preview**):
- **`csp: blocked connect-src … figma.com/….svg`** → the asset `uri` is a `figma.com` URL. The preview engine's Content-Security-Policy allowlist excludes it. For an icon/logo, the better fix is to **drop the asset**: convert its paths into a live shape with `picsart_media_import_svg_path` (no URL, so nothing to block). Otherwise **lift it to a Picsart-hosted URL** (`picsart_media_upload`/`picsart_drive` → `pastatic`/`gcdn`) and author that.
- **`csp: blocked img-src … blob:`** → the asset `uri` is a `blob:` object URL (a transient browser handle, never a valid scene asset). **Upload the underlying file** (`picsart_media_upload`/`picsart_drive`) and author its `pastatic`/`gcdn` URL.
- **base64 `data:` URI** → same story: blocked/unsupported in the preview (esp. SVG). Use a Picsart-hosted URL, not `data:`.
- **`file://` or an expired short-lived link** → lift to a durable Picsart-hosted URL.
- **Font by name/key** → text renders empty / engine won't boot; author the resolved `.ttf`/`.otf` URL (see `SKILL.md`).
- **Fix ONLY the affected asset — never flatten to dodge CSP.** Re-host that one image (or convert an unsupported vector to a raster *for that element alone*); do **not** rasterize the card, component, or screen it lives in. Flattening to make the CSP error disappear destroys the layering — it's a wrong ad, not a fix. And never rasterize a shape or live text for CSP: those are authored, not uploaded.
- **Don't trust `contact_sheet`/`export` here** — they're server-side and won't reproduce the browser CSP block. Verify in the editor preview, or just use Picsart-hosted URLs everywhere so it works in both.

## "Engine failed to boot: content kind X has no component … not known to this build"
The scene uses a layer `content.kind` the **browser player's "standard surface" has no component
for** (seen live with `composition`), while the server render path considers it valid — so
`validate_scene` passes and server-side `contact_sheet`/`export` render it (**validation ≠ boot**,
the same class of gap as fonts-by-name). It is **not** simply a stale widget: the content-kind →
component registry the browser player consumes can be pinned *newer* than the server capabilities
build — so `composition` looks like a **render-path capability gap** (server renders it; the browser
standard surface doesn't), not a version lag.
- **Do now:** the export/still fallback is correct — show the exported result and keep working.
- **Author around it:** for editor-bound scenes prefer a kind the browser surface renders
  (`media`, `text`, `color`, `empty`, `shape`, `scene_ref`) — a nested `composition` usually
  restructures into an `empty` rig with parented children (same visual, and the rig still scales its
  children together for a zoom). This is the reliable path today.

## "The rebuilt frame matches the design but nothing animates per-element / it's basically an image"
The frame was **rasterized** — composed from one whole-frame screenshot (or a few big group slabs)
instead of per-node layers. A slab can only fade/scale as a whole, so no element moves independently:
that's a slideshow, not motion design.
- **Cause:** took `get_screenshot` of the frame/group as the layer — usually because the per-node tree
  wasn't fetched (or was skipped when metadata "looked empty"), so the screenshot was the easy path.
- **Fix:** throw the slab out. Fetch **this frame's full layer tree** (you only hold one or two
  frames — you can afford all of it) and rebuild: **each design node = its own scene layer**, authored
  as its native kind (text→`text`, rects/cards/borders/circles→`shape`, icons/logos→vector, only true
  photos/video→raster). **Definition of done:** the scene's layer count ≈ the design frame's node
  count. One media layer covering the frame = failed, redo. (Hard gate: `SKILL.md` Stage 2.)
- **Never rasterize to fix CSP** — re-host the one affected asset, don't flatten (see above).

## "It looked right in the stills but wrong when it played" (stills ≠ editor)
The still renderer (`contact_sheet`/`export --format image`) and the browser editor **do not draw
some things the same way**, so a move that looks correct frame-by-frame can be wrong in motion:
- **The editor is the ONLY proof of MOTION.** Stills are fine for the Stage-2 *position* render-match
  (a frozen layout vs the Figma frame), but **motion correctness — overlap during an animation,
  timing, z-order while things move — is judged in `picsart_scene_editor`, never from stills.**
  Approve a pair on the played editor, not a contact sheet.
- **Known divergence seen live:** a **photo scaling in from its left edge** (edge-anchored scale)
  looked correct in stills but **overlapped a sibling line** in the editor. If a move reads clean in
  stills, don't trust it — watch it play in the editor. (For the connector/line case, the fix is to **fade the arriving
  photo in rather than scale it**, and put the connector *below* the photo — [`TRANSITIONS.md`](./TRANSITIONS.md)
  §"Recipe — the connected-canvas rig".)
- Also render-path-only, already covered above: **CSP-blocked assets** and **`composition`/`track`
  content kinds** render server-side (stills/export) but blank or fail to boot in the editor — same
  lesson, the editor is authoritative for what the designer will actually see.

## Quick diagnostic (run before approving a pair)
No linear on spatial motion · duration matches the element type · a primary + at least a secondary
where it helps · consistent personality · directional easing correct · no move >⅓ screen without an
intermediate keyframe · ≤⅓ of elements active at once · follow-through present · every motion has a
purpose · still holds up on the 100th viewing.

SHA-256: d58dd63e3a4a7088ba783cb31899379c462ffbdfcbf13bc8437d93e4ebd01640