Repository navigation
v0.15.1
0.15.1 (2026-11-01)
This release fixes five results that broke what the headers promise. A timeline eased by a cubic bezier or circ never steps back, so a mark at its end is crossed once (#214). A spring's settling time leaves room for the sample's rounding (#216). A stagger whose offsets would pass the doubles is refused (#221), an easing table holds a sample past the floats at the largest float (#223), and a host easing's NaN is taken as its progress and counted as misuse, while its infinity keeps equal ends in place (#225). Values change: circ samples everywhere, the child times of a timeline eased by a cubic bezier, the ends of spring transitions, and what a host easing's NaN or infinity gives. Each fix below names who sees it.
Fixed
- A timeline's easing never decreases, as
mtwnIsEasingMonotonicpromises for circ in every mode and for a cubic bezier with bothyin[0, 1], CSS'sease,ease-in,ease-outandease-in-outamong them. Their samples could step back between two progresses a hair apart: circ by an ulp, a cubic bezier by an ulp next to its end (ease) up to 2.6e-5 (cubic-bezier(1, 0, 0, 1)near 1/2). A mark at the end of a timeline eased byeasecould then be crossed twice, and a build with assertions trapped (#214).- Circ's samples move by at most 2^-53 wherever circ is sampled:
mtwnSampleEasing, keyframes, transitions, timelines and the fit. Over a million progresses, 10% (in), 16% (out), 6% (in-out) and 9% (out-in) of them move. A host that plays circ and compares frames across versions sees these. - A timeline eased by a cubic bezier moves its children's times: within 2.3e-14 for CSS's keywords, and by up to 3.6e-6 near a vertical tangent, where the new value is the closer one. Event counts change only where they were wrong. A cubic bezier anywhere else (
mtwnSampleEasing, keyframes, transitions) keeps its bits, so a timeline's values can differ from them by a few 1e-14 for CSS's keywords, as the easing and clips guides now say. - Such a timeline costs more to sample. 1,000 clip players each at its own time, their timeline eased by
ease, take about 1.9 times as long to update as on 0.15.0; players in step share the sample and take 1.4% longer. A linear timeline takes 0.5% longer.
- Circ's samples move by at most 2^-53 wherever circ is sampled:
mtwnComputeSpringSettlingTimegives a time from whichmtwnSampleSpring's offset and velocity stay within their tolerances, as it promised. The sample, rounded, could be just outside at that time: a 2 Hz critically damped spring from 0.1 at rest, both tolerances 1e-3, sampled a velocity 3 ulps past it (#216).- Every finite settling time moves later or stays: for tolerances from 1e-9 to 1, later by 1.1e-14 to 3.1e-11 relatively, and for tolerances near the subnormals by up to 2.1%. Spring transitions, and an inertia's hand-off to a spring, end later by as much. A host that compares a spring transition's end or its frames past it across versions sees it.
- A start within both tolerances still settles at once, unless it is within the sample's rounding of a tolerance's edge, relatively about 1e-14 and up to about 4e-9 just above critical damping: then its time is just past 0 (a critical 2 Hz spring from 1e-3 at rest, to 1e-3: 2.77e-8 s).
- The header and the physics guide say when the time is infinite: an undamped spring, or one decaying at a rate below about 4e-306, that does not start within both.
mtwnComputeStaggerrefuses withmtwn_errorInvalid, writing nothing, a stagger whose offsets would pass the largest double, where it wrote infinity and returnedmtwn_success: a start of 1e308 with an amount of 1e308 over 3 items, or an easing overshooting far past its ends. Every stagger accepted before with finite offsets gives the same bits (#221).mtwnSampleEasingTableholds a sample past the floats' range at the largest finite float of its sign, as every other float the library writes is held, where it wrote infinity:cubic-bezier(0.5, 1e200, 0.5, 1), a back easing with an overshoot of 1e100 andlinear(0, -1e300), for example. A sample within the range keeps its bits (#223).- A host easing's NaN is taken as the progress the function was called with, wherever it is sampled (#225). The same NaN gave a target
FLT_MAX, a scale target a NaN velocity, a clip track the base where it came from the track's timing easing, andmtwnSampleEasingand the table NaN.- A context counts one misuse for each sample of an update that met one: a transition's value, and a clip track's value for each player. The count is the same with the sharing on or off, serially or on tasks. Calls without a context substitute without counting, so a fit or a rough easing whose host function gave NaN now succeeds where it was refused.
- A host easing's infinity between equal ends keeps them, velocity 0, in every kind, as a transition and as a track. They showed
FLT_MAXor NaN, and a scale track's cubic segment gave NaN between other keyframes too.mtwnMixRotationbetween equal ends far out (a progress of 1e20) gives the rotation, where it gave a zero quaternion. - A host whose easing returns NaN or infinity sees these values and the misuse count. An easing that never does gives the same bits.