motion-studio

tools/check.py and tools/test_motion.mjs

What the tests prove.

And what they don’t. Each check below shows its latest real output for every film, copied from the reports by the sync script. A check only proves what it measures.

Determinism

Pass or fail

Proves

Two renders of 0–2 s give identical frame hashes (ffmpeg -f framemd5).

Doesn’t prove

Anything after 2 s; the same result on another machine, GPU or Chromium version; that the live preview matches the render.

FilmLatest outputStatus
Safari OS v2 · 16x9120/120 frame hashes identical across two renders of 0-2 sOK
Safari OS v2 · 9x16120/120 frame hashes identical across two renders of 0-2 sOK
Safari OS v2 · 1x1120/120 frame hashes identical across two renders of 0-2 sOK
Florios morph120/120 frame hashes identical across two renders of 0-2 sOK
Safari OS v1120/120 frame hashes identical across two renders of 0-2 sOK
The showreel120/120 frame hashes identical across two renders of 0-2 sOK

Loop seam

Pass or fail

Proves

On a looping film, the jump from the last frame to the first is no bigger than the film’s 95th-percentile frame-to-frame change.

Doesn’t prove

That motion continues across the seam: speed can still jump. The loop-state probe covers that for morph films.

FilmLatest outputStatus
Florios morphlast→first change 0.05 vs median per-frame change 0.07, 95th pct 3.78OK
The showreellast→first change 0.64 vs median per-frame change 0.89, 95th pct 12.23OK

Contact sheet

Report only

Proves

Nothing by itself. It is one frame per beat for a person, or a model, to look at.

Doesn’t prove

Motion quality. Stills can’t show easing, judder or timing.

FilmLatest outputStatus
Safari OS v2 · 16x9out/safarios-v2-contact.png (64 frames, one per beat)Report
Safari OS v2 · 9x16out/safarios-v2-9x16-contact.png (64 frames, one per beat)Report
Safari OS v2 · 1x1out/safarios-v2-1x1-contact.png (64 frames, one per beat)Report
Florios morphout/florios-morph-contact.png (24 frames, one per beat)Report
Safari OS v1out/safarios-contact.png (40 frames, one per beat)Report
The showreelout/reel-contact.png (24 frames, one per beat)Report

Loudness

Pass or fail

Proves

Integrated loudness within ±1 LU of −14 LUFS, and true peak at or below −1 dBTP, measured on the final mp4.

Doesn’t prove

That the mix sounds good, is balanced, or suits the film.

FilmLatest outputStatus
Safari OS v2 · 16x9-14.1 LUFS integrated, true peak -1.2 dBTPOK
Safari OS v2 · 9x16-14.1 LUFS integrated, true peak -1.2 dBTPOK
Safari OS v2 · 1x1-14.1 LUFS integrated, true peak -1.2 dBTPOK
Florios morph-14.1 LUFS integrated, true peak -1.6 dBTPOK
Safari OS v1-14.0 LUFS integrated, true peak -4.0 dBTPOK
The showreel-13.9 LUFS integrated, true peak -4.0 dBTPOK

Cue sync

Pass or fail

Proves

For every cue in timeline.json, the first visible pixel change in that cue’s element lands within one frame of its time.

Doesn’t prove

That the right sound plays there, or is audible; anything not listed as a cue. The film reports its own element boxes, so a wrong box gives a wrong answer.

FilmLatest outputStatus
Safari OS v2 · 16x951/51 cues: first visible change of the cued element within 1 frame of its sound (median +0.0 ms, range +0..+17 ms)OK
Safari OS v2 · 9x1651/51 cues: first visible change of the cued element within 1 frame of its sound (median +0.0 ms, range +0..+17 ms)OK
Safari OS v2 · 1x151/51 cues: first visible change of the cued element within 1 frame of its sound (median +0.0 ms, range -17..+17 ms)OK
Florios morph15/15 cues: first visible change of the cued element within 1 frame of its sound (median +0.0 ms, range +0..+8 ms)OK
Safari OS v148/48 cues: first visible change of the cued element within 1 frame of its sound (median +0.0 ms, range +0..+17 ms)OK
The showreel28/28 cues: first visible change of the cued element within 1 frame of its sound (median +6.2 ms, range -8..+17 ms)OK

States and loop state

Pass or fail

Proves

On a morph film, each state’s container is within ±2 px of its spec table (size and corner radius), and every animated track’s value and velocity match at the loop point.

Doesn’t prove

Anything inside the container.

FilmLatest outputStatus
Florios morphstates: 8/8 within ±2 px of the tableOK
Florios morphloop state: max |value(t=12) − value(0)| 0.00e+00, max |velocity(t=12) − velocity(0)| 0.00e+00 (all tracks)OK

Text visible

Pass or fail

Proves

At each scene’s settle time, the frame is pixel-identical with the text masks off, and with the container clip off (within 8 levels): no text at rest is clipped.

Doesn’t prove

Text mid-motion; text overlapping other elements; contrast.

FilmLatest outputStatus
Safari OS v2 · 16x99/9 scenes identical at their settle time with text masks off (exact)OK
Safari OS v2 · 16x99/9 scenes identical at their settle time with container clip off too (tolerance 8 levels)OK
Safari OS v2 · 9x169/9 scenes identical at their settle time with text masks off (exact)OK
Safari OS v2 · 9x169/9 scenes identical at their settle time with container clip off too (tolerance 8 levels)OK
Safari OS v2 · 1x19/9 scenes identical at their settle time with text masks off (exact)OK
Safari OS v2 · 1x19/9 scenes identical at their settle time with container clip off too (tolerance 8 levels)OK
Florios morph8/8 states identical at state + 1.0 s with text masks off (exact)OK
Florios morph8/8 states identical at state + 1.0 s with container clip off too (tolerance 8 levels)OK

Text size

Pass or fail

Proves

The smallest readable string on screen is at least the format’s minimum size (minTextPx).

Doesn’t prove

Strings the film doesn’t register as readable; contrast, or legibility at real phone size.

FilmLatest outputStatus
Safari OS v2 · 16x9smallest readable text per scene ≥ 56 pxOK
Safari OS v2 · 9x16smallest readable text per scene ≥ 40 pxOK
Safari OS v2 · 1x1smallest readable text per scene ≥ 40 pxOK

Audio onsets

Report only

Proves

Report only: how close the mix’s onsets sit to the 16th-note grid.

Doesn’t prove

Pass or fail; sync to the picture.

FilmLatest outputStatus
Safari OS v2 · 16x9240 detected: median |offset| from 16th grid 6.7 ms, 94% within 20 msReport
Safari OS v2 · 9x16240 detected: median |offset| from 16th grid 6.7 ms, 94% within 20 msReport
Safari OS v2 · 1x1240 detected: median |offset| from 16th grid 6.7 ms, 94% within 20 msReport
Florios morph52 detected: median |offset| from 16th grid 5.5 ms, 98% within 20 msReport
Safari OS v1128 detected: median |offset| from 16th grid 7.7 ms, 99% within 20 msReport
The showreel73 detected: median |offset| from 16th grid 5.7 ms, 99% within 20 msReport

SFX placement

Report only

Proves

score.py put every sound effect exactly at its cue’s time (internal consistency).

Doesn’t prove

Measured audio–visual sync. Cue sync measures the picture side.

FilmLatest outputStatus
Safari OS v2 · 16x9sfx placement (internal consistency): 51 cues, max |sfx time − cue time| 0.00 msReport
Safari OS v2 · 9x16sfx placement (internal consistency): 51 cues, max |sfx time − cue time| 0.00 msReport
Safari OS v2 · 1x1sfx placement (internal consistency): 51 cues, max |sfx time − cue time| 0.00 msReport
Florios morphsfx placement (internal consistency): 15 cues, max |sfx time − cue time| 0.00 msReport
Safari OS v1sfx placement (internal consistency): 48 cues, max |sfx time − cue time| 0.00 msReport
The showreelsfx placement (internal consistency): 28 cues, max |sfx time − cue time| 0.00 msReport

Clipping and astats

Pass or fail

Proves

No decoded sample at or beyond full scale. It also reports DC offset and crest factor.

Doesn’t prove

Distortion added before the limiter.

FilmLatest outputStatus
Safari OS v2 · 16x9clipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
Safari OS v2 · 16x9astats: DC offset -0.000075 / +0.000005 (L / R), crest factor 5.82 (15.3 dB) / 5.62 (15.0 dB)Report
Safari OS v2 · 9x16clipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
Safari OS v2 · 9x16astats: DC offset -0.000075 / +0.000005 (L / R), crest factor 5.82 (15.3 dB) / 5.62 (15.0 dB)Report
Safari OS v2 · 1x1clipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
Safari OS v2 · 1x1astats: DC offset -0.000075 / +0.000005 (L / R), crest factor 5.82 (15.3 dB) / 5.62 (15.0 dB)Report
Florios morphclipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
Florios morphastats: DC offset -0.000228 / -0.000273 (L / R), crest factor 4.98 (13.9 dB) / 4.91 (13.8 dB)Report
Safari OS v1clipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
Safari OS v1astats: DC offset -0.000014 / +0.000133 (L / R), crest factor 3.16 (10.0 dB) / 3.31 (10.4 dB)Report
The showreelclipping: 0 samples at or beyond full scale (|x| >= 1.0); astats flat factor 0.00OK
The showreelastats: DC offset +0.000319 / +0.000509 (L / R), crest factor 3.05 (9.7 dB) / 3.18 (10.1 dB)Report

Small speaker

Report only

Proves

Report only: the loudness drop after two 150 Hz high-passes, a stand-in for a phone speaker.

Doesn’t prove

How it really sounds on a phone. It isn’t gated; each brief sets its own target.

FilmLatest outputStatus
Safari OS v2 · 16x9-15.7 LUFS after two 150 Hz high-pass stages vs -14.1 LUFS full range: 1.6 LU dropReport
Safari OS v2 · 9x16-15.7 LUFS after two 150 Hz high-pass stages vs -14.1 LUFS full range: 1.6 LU dropReport
Safari OS v2 · 1x1-15.7 LUFS after two 150 Hz high-pass stages vs -14.1 LUFS full range: 1.6 LU dropReport
Florios morph-15.8 LUFS after two 150 Hz high-pass stages vs -14.1 LUFS full range: 1.7 LU dropReport
Safari OS v1-18.7 LUFS after two 150 Hz high-pass stages vs -14.0 LUFS full range: 4.7 LU dropReport
The showreel-17.9 LUFS after two 150 Hz high-pass stages vs -13.9 LUFS full range: 4.0 LU dropReport

Spring tests

All pass

Proves

Every closed-form spring in lib/motion.js matches the spring ODE, integrated at dt = 1e-5, to within 1e-3: value, velocity, retargeting with springTrack, and loop seams.

Doesn’t prove

That a spring looks right in a film, or that a film uses the right preset. It tests the maths, not the taste.

Latest output of node tools/test_motion.mjs: “all spring tests passed”

step response (closed form vs semi-implicit Euler, dt = 1e-5, t in 0..2 s)

casefreqdampmax|closed−sim|overshootsettle 2%|1−x(2 s)||1−x(10 s)|
SNAPPY3.50.857.40e-50.63 %0.190 s0.00e+00.00e+0
DEFAULT2.20.84.78e-51.52 %0.272 s4.13e-100.00e+0
HEAVY1.412.74e-50.00 %0.663 s4.25e-70.00e+0
CAMERA1.20.952.41e-50.01 %0.698 s1.83e-60.00e+0
PLAYFUL2.50.56.41e-516.30 %0.514 s3.51e-90.00e+0
damp 0.32.20.36.28e-537.23 %0.812 s1.56e-40.00e+0
damp 0.82.20.84.78e-51.52 %0.272 s4.13e-100.00e+0
damp 12.214.30e-50.00 %0.422 s2.82e-110.00e+0
damp 1.52.21.53.40e-50.00 %0.771 s3.04e-50.00e+0
damp 32.232.03e-50.00 %1.662 s8.97e-35.16e-11

springTrack retargeting (targets change before earlier moves settle), t in 0..3 s

casemax|track − sim|
SNAPPY1.85e-4
DEFAULT1.19e-4
HEAVY7.07e-5
CAMERA6.29e-5
PLAYFUL1.63e-4
damp 0.31.78e-4
damp 0.81.19e-4
damp 11.08e-4
damp 1.58.64e-5
damp 34.98e-5

periodic springTrack (period 3 s): seam and steady state vs ODE

case|Δvalue||Δvelocity|steady state: max|track − sim|max|velocity err|
SNAPPY0.0e+01.5e-41.04e-41.11e-3
DEFAULT0.0e+05.7e-56.68e-54.34e-4
HEAVY0.0e+02.4e-53.96e-51.73e-4
CAMERA0.0e+01.8e-53.55e-51.23e-4
PLAYFUL0.0e+07.5e-58.97e-55.36e-4
damp 0.30.0e+06.9e-59.09e-54.66e-4
damp 0.80.0e+05.7e-56.68e-54.34e-4
damp 10.0e+05.7e-56.04e-54.47e-4
damp 1.50.0e+05.8e-54.87e-54.65e-4
damp 30.0e+05.8e-52.83e-54.87e-4

The determinism hook

Blocks the edit

Proves

No line Claude writes in render code calls Math.random, a timer, requestAnimationFrame, the wall clock, or a CSS transition. Exit code 2 sends the violation back to Claude. CI runs the same script over every film.

Doesn’t prove

Anything a line-by-line pattern can’t see: state carried between frames, or values read back from layout. The // preview-only exemption works on trust. The determinism check above is the backstop.

Source: .claude/hooks/check-determinism.mjs · CI: .github/workflows/films.yml