# Generate short, assemble long

A video diffusion model trained on a ~5 second window will happily accept
`length=1449` and spend 97 minutes returning a clip that is technically 60
seconds and effectively a photograph. The image stays clean. It is the
*motion* that dies.

Measured on Wan 2.2 TI2V-5B, same prompt, same seed, only `length` changed.
"Breath depth" is peak body displacement inside a 3-second window:

| length | breath depth |
|---|---|
| 181 frames (7.5 s) | **1.29** |
| 481 frames (20 s) | ~0.8 — 41% of mean motion retained |
| 1449 frames (60 s) | **0.31** |

For scale, a prompt deliberately asking for a *motionless* subject scored
**0.58**. The 60-second generation was stiller than asking for stillness. The
model distributes the described movement across whatever duration you request,
so per-frame motion shrinks as length grows.

**Generate at or below the trained window. Assemble the rest.**

## Palindrome, not loop

Play the clip forward, then backward, and repeat. For any oscillatory subject —
breathing, water, foliage — the reversal is undetectable, and the turnaround is
a real frame sequence rather than a cut, so there is nothing to blend.

```sh
# trim the model's startup settle (first ~8 frames are noise resolving)
ffmpeg -i in.mp4 -vf "select=gte(n\,8),setpts=PTS-STARTPTS" -r 24 fwd.mp4
# reverse, dropping BOTH duplicated endpoints or the turnaround stutters
ffmpeg -i fwd.mp4 -vf "reverse,select=between(n\,1\,171),setpts=PTS-STARTPTS" -r 24 rev.mp4
# alternate in ONE concat list, then trim to target
printf "file 'fwd.mp4'\nfile 'rev.mp4'\n%.0s" {1..7} > list.txt
ffmpeg -f concat -safe 0 -i list.txt -frames:v 14400 out.mp4
```

Motion survives exactly: **0.224** mean in the loop against **0.221** in the
source. Joins measure 1.4x median motion where a real breath peaks at 13x —
invisible.

Two traps:

- **`-stream_loop` is not frame-accurate.** Looping a pre-built palindrome file
  left a hitch every cycle (2.7x median). Alternating the segments inside a
  single concat list dropped it to 1.4x.
- **A palindrome only works on symmetric motion.** A directional gesture — a
  paw flexing, a head turning — visibly un-happens on the reverse. Check what
  your motion actually is before mirroring it.

## Chaining dies at eight hops

Feeding each segment's last frame forward as the next segment's `start_image`
produces genuinely non-repeating footage. It also produces autoregressive error
feedback: the model slightly amplifies a spatial frequency, reads its own
output, and amplifies it again.

Edge energy and ringing, relative to the first segment:

| segment | 3 | 6 | 9 | 12 | 15 | 18 | 20 |
|---|---|---|---|---|---|---|---|
| edge | 107% | 124% | 150% | 179% | 198% | 218% | 220% |
| halo | 111% | 142% | 181% | 217% | 233% | 254% | 252% |

By segment 18 the texture is dense concentric ripples with colour fringing. It
**saturates** near 220% — a resonance settling at its equilibrium amplitude,
which happens to be ruinous. Useful to about **8-10 hops**, then stop.

Zoom (~0.5%/segment) and saturation (~2%/segment, compounding) also drift, but
those are correctable in post. The resonance is not.

## Two fixes that do not work

Recorded because both look obviously correct and cost a day between them.

**Spectral matching plus blur.** Match the seed frame's radial frequency
profile to the reference, then blur until edge energy returns to baseline. At
σ 0.6 all three metrics read ~100%. **The ripples were still plainly visible.**
Damping a coherent ripple's amplitude satisfies a scalar statistic without
removing the ripple. The numbers said fixed; the pixels said otherwise.

**Lossy damping.** An earlier chain that stayed clean had pulled its seed frames
out of H.264 video; the failing one used lossless PNG. Plausible theory:
compression was accidentally damping the feedback. Reintroduced deliberately as
a JPEG round-trip at q85 — 112 / 136 / 161 / 174% at segments 3 / 6 / 9 / 12,
identical to undamped within noise. The earlier chain was not stable because of
compression. It was stable because it was only eight segments long, and it was
measured through H.264 frames that smooth exactly this artifact.

## Measuring periodic motion

- **Use FFT, not autocorrelation.** Autocorrelation locked onto a harmonic and
  reported 12.3 cycles/min for a signal the FFT correctly read as 25.3.
  Method: per-pixel temporal std → top 5% varying pixels → mean per frame →
  quadratic detrend → Hanning → `rfft` → peak in the plausible band.
- **Never measure rate on an assembled loop.** The loop's own cycle dominates
  the spectrum. A finished palindrome returned 16.7/min for footage whose
  forward segments each measured 25-27. Measure one un-looped segment.
- **Scalar metrics are not a quality check.** Every failure above passed at
  least one metric. Look at the pixels, at full resolution — a downscaled
  contact sheet hid a two-headed animal that was obvious at 1:1.

## Prompt asymmetry

Positive prompts add objects to the scene; negatives remove concepts. They are
not interchangeable, and the failures are literal:

| written in positive | produced |
|---|---|
| `whiskers twitching` | a twitching, restless animal |
| `motionless, does not move` | a frozen still image |
| `locked-off tripod shot` | an actual tripod, in frame |

Say what *should* move in the positive. Put suppression — including equipment
nouns, and `frozen, still image, lifeless` — only in the negative.

## The general shape

Every attempt to generate long directly produced a worse result than
assembling short clips, and cost far more. The single most expensive render in
the exercise, at 97 minutes, was also the least usable output. Eight
independently generated 7.5-second clips, palindromed and crossfaded, gave ten
minutes with no degradation for about 90 minutes of total compute.

[[skills/index]]
