Verification

The fixtures

Each fixture is there for one specific behavior, and says which in a sentence. That sentence is what a failure prints.

FixtureWhat it pins
spring-settled2-kf spring that reaches rest in its span → Motion exports a native spring
spring-cutAUDIT M2 — a spring the timeline cuts mid-overshoot must not run on to rest
merged-union-stopsAUDIT M1 — tracks with different keyframe times share one CSS @keyframes
constant-transformAUDIT H1 — a pinned transform value must survive the animation's `both` fill
two-constant-transformsAUDIT H2 — two pinned transform values must not overwrite each other
delayed-opacityAUDIT H3 — a delayed track must hold kf0 from t=0, not its default value
multi-kf-easesper-segment easing across a 3-keyframe track (preset then bezier)
spring-inside-multia spring segment inside a keyframe array — the old bezier stand-in was wrong here
independent-delaystransform and opacity animate on separate clocks with separate delays
rotate-and-scaletransform composition order must match the engine
noncommuting-rotate-scalexTHE order fixture. rotate∘scaleX ≠ scaleX∘rotate, so this fails outright if any dialect composes transforms in a different order from the engine. Uniform scale commutes with rotation, which is why the original five properties could not catch an ordering mistake.gsap exempt — GSAP composes rotation BEFORE scale; the engine and Motion compose scale before rotation. Measured here: b = 0.707 vs 1.414, exactly the two orderings. The order lives inside GSAP's CSSPlugin and is not configurable, so this is a property of the target library rather than of our output — the only way around it would be to stop using GSAP's transform handling at all.
skew-pairskewX/skewY reach the matrix through b and c, which nothing else touchesgsap exempt — GSAP's fixed transform order again — it places skew after rotation and before scale, the engine and Motion place both skews last. Same cause as noncommuting-rotate-scalex.
scale-axes-independentscaleX and scaleY on separate timings — a merged transform must keep both
rotate-3drotateX/rotateY flatten into the 2D matrix without perspective; the gate compares what the browser resolved, so this pins the flattening toogsap exempt — GSAP applies rotationY before rotationX; the engine and Motion apply rotateX first. Measured: GSAP leaves b = 0 throughout (Ry·Rx has no vertical shear) where the engine reaches -0.612 (Rx·Ry does). Not a tolerance question — a different composition, fixed inside CSSPlugin.
filter-familythe whole filter group in one string. Motion cannot give its members independent timing (it interpolates `filter` as one value), so this is the fixture that proves the merged stops still land on the engine.
filter-springa spring on a filter member — sampled into the merged stops, not native
transform-and-filterboth groups animating at once must not interfere — they are separate CSS properties and separate @keyframes, and a bug in the group loop would show up as one of them silently overwriting the other
drop-shadow-strengthdropShadow is one number driving two lengths; parsing it back must agree
color-hue-crossOKLCH interpolation crossing hues — the case that proves the dialects are not silently interpolating in sRGB, where red→blue detours through grey
color-from-greyAn achromatic endpoint has no hue, so a fade from grey must travel out along the TARGET's hue rather than swinging onto it through a tint
color-spring-and-transformA color on a spring alongside a transform — color must carry the same curve vocabulary as everything else, and the two must stay in step
path-arc-with-scale-and-fadeAn arc into place while it scales up and fades in — the path composes BEFORE the transform, the distance is a percentage, and the element must land exactly home
path-loop-follow-delayedA loop that turns to face its direction of travel, starting late — `offset-rotate: auto` has to survive every dialect, and a delayed distance must hold the path's start. Paced at 2s: Motion is sampled live, and a faster pass through the loop turns a few ms of sampling jitter into more error than the export has (1.3s still missed 1 run in 2)
path-orbit-springA spring round an orbit — Motion's native spring on a percentage (rest thresholds pinned), CSS's sampled linear() and GSAP's sampled stops, all on a distance that the path folds into a smaller displacement. A soft spring, for the live-sampling reason the loop gives: a launch fast enough to be a typical spring measured the sampler, not the export (stiffness 50 still missed 1 run in 4)
path-wave-keyframesThree distances on a wave with different curves — keyframe arrays and merged stops carrying `%` values, where a bare number would be pixels along the path

25 fixtures, each checked in all three dialects unless its row says a dialect is exempt. The list is read from the fixtures themselves, so it’s always current.