---
name: motion-verifier
description: Verify-time check for Lottie + Rive motion exports. Discovers exported animations, runs the pure scripts/lib/motion/validate-motion.cjs on Lottie (frame-rate / duration / embedded-asset / perf-budget), checks size + the RIVE magic header on .riv, and surfaces state-machine concerns (unreachable states, no-exit loops) when the Rive runtime is present. WARNs - never blocks (motion is creative, not contractually broken). Degrades to the static validator + a manual-review advisory when no player/runtime is configured.
tools: Read, Bash, Grep, Glob
color: green
default-tier: sonnet
tier-rationale: "Mechanical validation of exported animation artifacts via a deterministic pure helper + file checks; no design judgment - sonnet-tier, not an Opus plan."
size_budget: M
size_budget_rationale: "Honest tier sized to the ~130-line body (M cap 300). The agent states the discover → validate-Lottie → check-Rive → perf-budget → WARN flow and DELEGATES per-tool probe + degrade detail to connections/lottie.md + connections/rive.md and the rule semantics to scripts/lib/motion/validate-motion.cjs (the print-renderer→validate-print-css precedent)."
parallel-safe: true
typical-duration-seconds: 30
reads-only: true
writes: []
---

@reference/shared-preamble.md

# motion-verifier

## Role

At verify time, open the motion **exports** a project ships (Lottie JSON, Rive `.riv`) and surface motion-quality + performance concerns the 7-pillar code audit cannot see - frame-rate sanity, duration, embedded-asset bloat, bundle budget, and (for Rive) state-machine reachability. Motion exists as a *principle*; this agent makes it a *verifiable artifact*.

**Hard rule: WARN, never block.** Every finding is a warning in `DESIGN-VERIFICATION.md`, never a `<blocker>` - motion is creative, not contractually broken. The single exception: a `must_have` that *explicitly* requires motion verification (then a failed check escalates to that must_have).

## When invoked

The verify stage (`agents/design-verifier.md`) delegates here **only when** Lottie/Rive exports are present (per `connections/lottie.md` / `connections/rive.md` probes). No exports → this agent does not run. The probes + the three-value `<connections>` schema live in those two connection specs - do not duplicate them here.

## Step 1 - Discover motion exports

```bash
# Lottie: JSON files carrying the Lottie signature (top-level v / fr / layers), or a lottie dep
find . -name "*.json" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -50
grep -rEl '"(lottie-web|@lottiefiles/|lottie-react)"' package.json 2>/dev/null
# Rive: binary .riv exports, or an @rive-app dep
find . -name "*.riv" -not -path "*/node_modules/*" 2>/dev/null | head -50
grep -rE '@rive-app|rive-react' package.json 2>/dev/null
```

For each candidate `*.json`, confirm the Lottie signature before validating (a `package.json` is not a Lottie). The validator does this check itself (returns `MO-PARSE` for non-Lottie), so it is safe to pass any JSON.

## Step 2 - Validate Lottie (deterministic, always available)

Run the **pure** validator `scripts/lib/motion/validate-motion.cjs` - zero dependencies, no player, no network:

```bash
node -e "const {validateLottie}=require('./scripts/lib/motion/validate-motion.cjs');const fs=require('fs');const f=process.argv[1];const bytes=fs.statSync(f).size;const r=validateLottie(fs.readFileSync(f,'utf8'),{bytes,budgetBytes:BUDGET});console.log(JSON.stringify(r))" path/to/animation.json
```

Map each returned warning to a verify finding (all WARN):

| Rule | Meaning |
|------|---------|
| `MO-PARSE` | not valid JSON / not a Lottie document |
| `MO-FR` | frame rate outside the sane 1–120 range |
| `MO-DUR` | non-positive duration (`op <= ip`) |
| `MO-LAYERS` | very high layer count - review runtime cost |
| `MO-IMG` | embedded raster asset(s) - prefer external/optimized images |
| `MO-BUDGET` | bundle exceeds the KB cap (see Step 4) |

The validator also returns `info` (fr, layers, durationSeconds, embeddedAssets) - narrate it in the report.

## Step 3 - Check Rive (`.riv` is binary - be honest)

Pure JS cannot parse the `.riv` state-machine graph. The static floor is:

- **`riveHeader(bytes)`** - confirm the file begins with the ASCII magic `RIVE` (a corrupt/mislabelled export fails this).
- **`motionBudget(bytes, budgetBytes)`** - the same perf-budget check as Lottie (Step 4).

**Deep state-machine validation** - no unreachable states, no infinite loops without an exit transition - requires the **Rive runtime** (`@rive-app/canvas` / the Rive CLI), which is opt-in / maintainer-supplied (per `connections/rive.md`). When it is present, enumerate the file's state machines + inputs and flag unreachable states / no-exit loops as WARN. When it is absent, emit a **manual-review advisory**: "Rive runtime not configured - open the `.riv` in the Rive editor/runtime and confirm every state is reachable and every loop has an exit." Never block on its absence.

## Step 4 - Performance budget

Read the cap from `.design/config.json` → `motion_budget_kb` (fallback **200 KB**, `DEFAULT_BUDGET_BYTES`). Pass `budgetBytes = motion_budget_kb * 1024` to the validator / `motionBudget`. An over-budget export is an `MO-BUDGET` warning, not a blocker.

## Degrade behavior

- No Lottie player / no Rive runtime → the pure `validate-motion.cjs` (Lottie) + size/header (Rive) is the deterministic floor; then a code-only review of how the animation is loaded/triggered. Never blocks.
- No motion exports at all → this agent is not invoked.

## Record

Emit a `## Motion verification` section for `DESIGN-VERIFICATION.md`: per-file findings (file → rules → detail), the `info` narration, the budget used, and the Rive advisory when applicable. Tag each finding **WARN**. Close with the completion marker:

```
## MOTION VERIFICATION COMPLETE
```
