Repository navigation
Releases
v1.1.0
1.1.0 (2026-11-04)
This release makes mtwnCreateClipPlayer check its bindings in time linear in their count, where it compared each binding with every one before it (#292). A player of 2^20 bindings took 194 s to make at -O2 and now takes 12 ms. At the old rate, the 2^24 slots a clip may have would have taken about 11 hours. Two refusals of a def that binds a slot twice change their status, as below. MTWN_ABI_VERSION and a shared library's SOVERSION are now 1.1. A context def stamped by 1.0.0's headers is refused with mtwn_errorVersion, so a program built against 1.0.0 is rebuilt; its source builds unchanged. No value, velocity or event record changes.
Changed
mtwnCreateClipPlayerchecks its refusals in this order, which its header now states: an invalid def, then the rate's limit, then the context's capacities of players and of bindings, and a slot bound twice last (#292). A def that binds a slot twice changes its status only where another refusal also applies:- with the context's players or bindings full,
mtwn_errorCapacity, not counted as misuse, where it wasmtwn_errorInvalid, counted; - with a rate past
MTWN_RATE_LIMIT,mtwn_errorLimit, where it wasmtwn_errorInvalid; both are counted as misuse.
- with the context's players or bindings full,
- The clips guide and
mtwnClipTrack's header say that a track read withmtwnClip_GetTrackpoints into the clip's storage. The values layer takes it as it takes a track the host made, reading only within the counts the track states, so the host keeps it as it was read (#299).
v1.0.0
1.0.0 (2026-11-03)
This is the first release of 1. Maul Tween's API is settled, and from here it ships only minor and patch releases of 1 (#273). Every function, type, enum value, macro and struct field in include/maul-tween/ keeps its name, signature and meaning across 1.y. The binary interface holds within a minor version. The API is the same as 0.17.0's, so a program built against 0.17.0 needs only a rebuild. MTWN_ABI_VERSION and a shared library's SOVERSION are now 1.0, and a context def stamped by 0.17.0's headers is refused with mtwn_errorVersion. No value, velocity or event record changes.
Changed
- The getting started guide's Compatibility section states the promise 1.y keeps (#273):
- what it covers, and the fields of the structs a maker fills, which it leaves out;
- the binary interface within a minor version, with
MTWN_ABI_VERSIONand the SOVERSION at major.minor, and the CMake package taking any later 1.y; - a changelog line for any change of results;
- no stored format to keep.
- The easing guide and
mtwnMakeElasticEasing's header give the largest difference from GSAP's and tween.js's elastic for every amplitude (#272). The new bound isq (1 + q) 2^-10 / (1 - q 2^-10), withqthe larger of the amplitude and 1. The old bound,a (1 + a) 2^-10, was too small below amplitude 1 and at large amplitudes. The curves do not change;test_rival_easingchecks 90 of the rivals' curves against the new bound.
v0.17.0
0.17.0 (2026-11-03)
This release changes two results to follow the references they name, ahead of 1.0.0. A finished player given a new rate runs at it from the time it holds, so a positive rate keeps it finished at its end where it jumped back (#264). A path's direction through a hold or a step is the way the path goes on, as SVG 2's path directionality takes it, where it was the way it came (#266). The clips guide's GSAP porting notes now cover grids with fewer items than columns and every way a clip's positions differ from GSAP's timeline, each checked against GSAP 3.15.0's outputs (#260, #262). Results change for a finished player given a positive rate (its time, the values it writes and its records), and for the direction and angle a host asks of a timed path through a hold or a step.
Changed
- Breaking:
mtwnClipPlayer_SetRateon a finished player runs it at the new rate from the time it holds, its start time set again from it,start = now - current / rate(#264). A positive rate keeps the player finished there. It kept the start time it finished with, as Web Animations' setting of the playback rate leaves it, so the next update put it back at(now - start) * rate: a 2 s clip finished at 12 s and given 0.5 at 12.5 s jumped back to 1.3 s and finished again at 14 s.- A finish after the change is no longer taken back, and the event records change with it: no iteration or end is crossed again.
- A negative rate plays the player back from there at once, as before. Every other state takes the rate as before, by Web Animations' seamless update of the playback rate.
- The specification's own seamless procedure would instead keep a finished player at its end for as long as it stood finished before a negative rate moves it.
docs/internals/timing.mdnames this, and a running player's pending rate, as the two places the players depart from the specification's text. tools/oracle/player_oracle.pywrites Level 1's player procedures again from the specification, with those two departures, andtest_playermatches its 300 scripts to the bit.
- Breaking:
mtwnComputePathDirectionandmtwnComputePathAnglegive a timed hold (a segment of no length between keyframes at different offsets) and a step the direction of the nearest later segment with a length, at its start, as SVG 2 moves past a segment of no length (#266). They gave the nearest earlier one, at its end. A path right to (1, 0), held there from 0.4 to 0.6 and then up, faced along x through the hold, and now faces up from 0.4.- Only at the path's end, where no segment with a length follows, do they still take the earlier one. A plain corner, a path without holds or steps, and every path at constant speed give the same bits.
- The header and the values guide say that a cusp's outgoing direction is the library's choice, since SVG 2 leaves it open, and that Chrome takes the incoming direction at every boundary, against SVG 2's text.
- The clips guide's porting note for staggers covers a grid with fewer items than columns (#260): GSAP lays it out as one row of its items and spreads
eachovercount - 1. A grid of one row whose origin GSAP puts off that row has no translation, measured across both axes.test_rival_staggerchecks 832 of GSAP's cases. - The clips guide names every way a clip's positions differ from GSAP's timeline, each with the translation a port needs (#262). These are a child's delay, a start before 0, a callback, the end after an endless child, percentages and a label not yet added. For a child before 0, it says what plays at the clip's time 0.
test_rival_positionchecks 400 of GSAP's timeline scripts.
v0.16.0
0.16.0 (2026-11-03)
This release changes two stagger results, found by comparing staggers with GSAP's distribute, which the clips guide names as their model. A negative spacing now runs from the farthest items, as GSAP's does, so no offset falls below the start and every offset is a delay a transition takes (#257). An offset whose host easing turns out not finite when it is written is refused, where it was returned (#254). The guide's porting note now gives every translation a port from GSAP needs, checked against GSAP 3.15.0's own outputs (#258). Offsets change for every stagger with a negative spacing; no other value, velocity or event record changes.
Changed
-
Breaking:
mtwnComputeStaggerwith a negative spacing puts the farthest items at the start and the nearest at the start plus the spread's size, on the easing turned around: an offset isstart - span * ease(1 - t)(#257). It put the nearest items at the start and the others below it, on the same easing. A row of six from the first with a spacing of -0.1 gave 0, -0.1, ..., -0.5, which as delays a transition refuses; it now gives 0.5, 0.4, ..., 0, as GSAP does.- A linear stagger's offsets are the old ones moved up by the whole spread, each item keeping its place in the order. An eased one's follow the easing turned around, so they are not a shift of the old ones.
- A negative amount over items all at one distance, a single item among them, gives every item
start - spacing, where it gavestart. - A stagger with a positive spacing or a spread of 0 gives the same bits.
-
Breaking:
mtwnComputeStaggerrefuses withmtwn_errorInvalida stagger whose host easing gives a value that is not finite when an offset is written, where it returnedmtwn_successwith that offset not finite (#254). The easing is sampled at every item before any offset is written, so only a host function that gives another value at the same progress when sampled again gets there. Its offsets are then unspecified; every other refusal still writes none. Every offset of a success is finite, as the header says. -
The clips guide's "Porting from GSAP" note names every way a stagger differs from GSAP 3.15.0's
distribute, each with its translation (#258):- GSAP's
eachbecomes an amount; - its ratio points, and
"end"and"center"in a grid, become points of the grid; - its
"edges"becomes the center with the spacing negated; - GSAP rounds each offset to 1e-7.
tools/oracle/rivals.mjswrites 448 of GSAP's cases, andtest/test_rival_stagger.cchecks that each case, translated as the note says, agrees within GSAP's rounding. - GSAP's
v0.15.4
0.15.4 (2026-11-02)
This release keeps linear() easings monotonic where mtwnIsEasingMonotonic says they are, which a timeline's crossing count relies on (#243). A sample inside a segment could land an ulp past the segment's later point and step down at it. A sample moves only there, by at most about an ulp, so only hosts sampling linear() just below a point see it.
Fixed
- A
linear()sample no longer passes its segment's later point (#243). A segment's sample isa + share * (b - a), and the rounded difference could carry it an ulp pastbwhere the share is 1 or rounds to it:linear(0, 0.03 1%, 0.3 3%, 1)gave 0.30000000000000004 just below 3% and 0.3 at 3%. A timeline eased by such a curve could count a mark twice, depending on where the frames fell. Inside a segment the sample is now held at the point's output where it would pass it.mtwnSampleEasing, and the tracks, transitions and timelines eased bylinear(), its fits and rough easings included, change only at those progresses, just below a point, by at most about an ulp of the segment's larger output.- A clip's records change only where a timeline eased by such a
linear()had a frame land there: a mark it crossed twice is now crossed once.
v0.15.3
0.15.3 (2026-11-02)
This release fixes two ways the event records broke their promise that every crossing between two updates is counted, however far time moved. An endless node whose iteration passes the largest double is held there, as the timing model holds it, where its crossing count was NaN (#237). A sweep's count is taken between its two ends, so it no longer depends on how much the clip crossed before the sweep (#241). No value or velocity changes; event records change only for clips that cross more than 2^53 points before a sweep, or whose endless nodes pass the doubles. Each fix below names who sees it.
Changed
- The players guide says a count of 2^64 or more is
UINT64_MAX, and that an endless node whose iteration passes the largest double crosses nothing between two times past it. Theiterationfield ofmtwnTimingResultsays where it is infinite and what holds there.
Fixed
- An endless node whose iteration passes the largest double no longer makes its crossing count NaN (#237). A track of 1e-308 s passes it after about 1.8 s, and one at an offset of -1e300 with a delay of -DBL_MAX at once. Release builds dropped such crossings without a word, and builds with assertions trapped in the update.
- Between two times past the doubles the node is held at the end of its infinite iteration, as its values are, and nothing is crossed: no record changes there in Release.
- In the frame in which an endless timeline passes the doubles, the marks below it now record
UINT64_MAXcrossings each way they run, where none were recorded.
- A sweep's records no longer depend on how much its clip crossed before the sweep began (#241). A count was the difference between what each end reached from the clip's start, and past 2^53, the doubles' last exact integer, that difference lost its low bits. A timeline of 1000 iterations of 1 s holding an endless track of 1e-18 s, swept by two doubles near 500 s, recorded 65,536 iterations where about 113,687 are crossed, and a record's first and last iterations landed where the rounded counts flipped, its last before its first.
- A host sees corrected counts and first and last iterations only where a clip's nodes cross more than 2^53 points before a sweep, which takes iterations far below a microsecond in long or nested timelines. Where every count stays below 2^53 the records are the same bits.
- A count through timelines nested to
MTWN_MAX_CLIP_DEPTHstays linear in the depth.
v0.15.2
0.15.2 (2026-11-02)
This release fixes two results that broke what the headers promise. An Oklab or OkLCh color far past its ends keeps each channel's sign (#227), and mtwnIsEasingMonotonic no longer approves a linear() easing whose halves step down where in-out or out-in joins them, so a timeline refuses it (#230). Values change only for colors driven past about 5.6e102 in Oklab or OkLCh; nothing within range moves. Each fix below names who sees it.
Changed
- The easing and values guides say when linear() is monotonic in in-out and out-in, and that a color channel past the floats' range is held at the largest finite float of its sign in every space.
Fixed
- An Oklab or OkLCh color whose coordinates pass about 5.6e102 has each channel at the largest finite float of its own sign, as the headers promise, in
mtwnMixColor, color transitions, color tracks andmtwnConvertColorFromSpace(#227). A channel whose sum met infinities of both signs was held at+FLT_MAX: black to white at a progress of -1e110 gave+FLT_MAXin each channel, brighter than white, where sRGB gives-FLT_MAX. Oklab is the default space of transitions and tracks.- A host that drives a color that far, with a back overshoot past about 1e100 or a linear() output past the doubles' range for example, sees the corrected sign.
- An OkLCh color so far out that its hue passes the doubles (a progress past about 1e307) takes a hue of 0, as a missing hue does, where its channels were held at
+FLT_MAX. - Nothing within range moves: 800,000 mixes and conversions in every space and hue method give the same bits as 0.15.1.
mtwnIsEasingMonotonicreturns false for linear() in in-out ending above 1, or in out-in starting below 0, where it returned true (#230). Those modes join the curve's halves at 1/2, and such an easing steps down there:linear(0, 2)in in-out gives 1 just below 1/2 and 0 at it.mtwnClip_AddTimelinetook a timeline eased so, and its marks could be counted twice, or trap in a build with assertions.mtwnClip_AddTimelinenow refuses such a timeline withmtwn_errorInvalid. A host that eased a timeline so sees the refusal.- Every other easing keeps its verdict, and no value, velocity or event record of an accepted clip changes.
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.
v0.15.0
0.15.0 (2026-10-31)
This release follows the Maul family's conventions of .github v0.0.40. The enumerations are int32_t and the rough easing's flags uint32_t, so every struct holding one changes its layout, and the dropped events counter is mtwnContext_GetEventsDropped (#207). mtwnContext_GetMisuse no longer counts two refusals that depend on the library's state (#203). An unknown color space, hue method, angle method or rotation path is refused whatever the target's or track's kind (#210), and five corrections make the headers and guides say what the code does. No value, velocity or event record changes.
Changed
-
Breaking: the 25 enumeration typedefs are
int32_t, no longeruint8_t, andmtwnRoughDef.flagsis the newmtwnRoughFlags, auint32_t(#207). Their values do not change. The 25 aremtwnValueKind,mtwnNodeKind,mtwnPositionKind,mtwnEventKind,mtwnPhase,mtwnFill,mtwnDirection,mtwnAlternateEasing,mtwnStaggerOrigin,mtwnStaggerAxis,mtwnStaggerSpacing,mtwnSegmentKind,mtwnTransitionKind,mtwnVelocitySource,mtwnColorSpace,mtwnHueMethod,mtwnAngleMethod,mtwnRotationPath,mtwnComposite,mtwnPlayState,mtwnEasingKind,mtwnStepPosition,mtwnCssKeyword,mtwnEaseModeandmtwnRoughTaper.- Every struct holding one changes its layout on some data model: every def,
mtwnEvent,mtwnBinding,mtwnTimingandmtwnClipNodeInfo. A program rebuilds against the new headers. - A negative kind, mode or choice, which the signed types allow, is refused with
mtwn_errorInvalidas an unknown one is, in a call, a def or a made easing, color mix or track; a path's keyframe kind is read within its tables. - The default context def's clip storage is 240 KiB, no longer 224 KiB, still 16 default clips: a default clip takes 14,808 bytes, where it took 14,280. With a task system, a group's share per binding grows from 60 to 68 bytes, and the default def's groups from about 900 KB to about 940 KB.
- Every struct holding one changes its layout on some data model: every def,
-
Breaking:
mtwnContext_GetDroppedEventCountis renamedmtwnContext_GetEventsDropped, as the family names a counter of work lost (#207). -
Breaking:
mtwnContext_GetMisuseno longer counts two refusals the library's state decides (#203):- a stale id of a player the context handed out, kept or not, given to
mtwnDestroyPlayer,mtwnClipPlayer_GetStateor any clip player control. It is still refused withmtwn_errorStale. A player id never handed out still counts: the null id, an index past the highest slot used, generation 0 and a generation its slot has not reached; mtwnDestroyCliprefused withmtwn_errorBusywhile a player holds the clip.
Stale target and clip ids still count. A host now reads no misuse for stopping or reading a player after it ended, naming a kept player after destroying it, or destroying a clip a player still holds: the count is smaller for the same calls, never larger. The transitions guide no longer asks for
mtwnPlayer_IsValidbefore stopping a transition that may have ended. - a stale id of a player the context handed out, kept or not, given to
Fixed
mtwnStartTransitionrefuses an unknown color space, hue method, angle method or rotation path withmtwn_errorInvalidand one misuse whatever the target's kind, andmtwnContext_MeasureTransitionan unknown angle method or rotation path.mtwnMakeTrackandmtwnClip_AddTrackrefuse an unknown color space, hue method or angle method whatever the track's kind, as they already did the rotation path, the add counting one misuse. A kind that does not read the choice took any value, thoughmtwnStartTransition's header said every unknown choice is refused (#210).- The headers and guides say what the code does. None of these changes the code.
mtwn_errorStaleis an id that names no live object: one destroyed since it was handed out, one never handed out, or the null id.base.hand the getting started guide said only the first (#189).context.h's opening comment names the const readers refused while the host's code runs:mtwnTarget_GetStatecalled back in, and four readers inside an update's groups (#190).mtwnClipPlayer_GetState's header lists only the codes it returns. It said "As mtwnClipPlayer_Play", whose list hasmtwn_errorState(#191).- A player whose clip has more than 128 nodes plays on the calling thread only when other players play the same clip. The threads guide and
mtwnContext_GetSerialAdvanceCount's header said it did whenever other players ran (#192). MTWN_SEEK_LIMIT's comment derived a margin of 16 from a bound that gives 8. It now derives it from the tighter bound the same lines give (#195).
v0.14.0
0.14.0 (2026-10-30)
This release finishes the API review for 1.0. The readers take the context const and read it when host code calls back in (#170). The calls on a clip's player are named for it (#178) and the two counters that were not are named Get...Count (#176). The reduced motion switch returns nothing (#174), the host easing's callback is a function type (#172), and mtwnMakeTrack takes the keyframes it rewrites first (#184). Three getters no longer crash on a null context, and a refused rough easing leaves its points. The getting started guide says what 1.0 will keep. No value, velocity or event record changes.
Changed
-
Breaking: ten readers take a
const mtwnContext*(#170):mtwnClip_GetTrack,mtwnClip_GetNode,mtwnClip_FindLabel,mtwnClip_GetDuration,mtwnTarget_GetValue,mtwnTarget_GetState,mtwnPlayer_GetState(nowmtwnClipPlayer_GetState),mtwnContext_ReadTargets,mtwnContext_GetClipBytesandmtwnContext_MeasureTransition. C callers compile as before; a C++ pointer to one of them has a new type.- Called back in from the host's code the context runs (a host easing, the allocator, the task system's functions), all but
mtwnTarget_GetStatenow read the context as the call in progress has left it, where they returnedmtwn_errorBusy, and count no misuse. mtwnTarget_GetStateis still refused when called back in: a target's velocity may be computed when read, by sampling a host easing, which could otherwise call it again without end.- While an update's clip players run in groups on the host's task system,
mtwnTarget_GetValue,mtwnContext_ReadTargets,mtwnClipPlayer_GetStateandmtwnContext_MeasureTransitioncalled from the host's code are refused withmtwn_errorBusy, one misuse counted, their outputs left as they were, since a group may be writing what they read. mtwnDestroyClipandmtwnDestroyContextrelease a clip's id before the host's free gets its block, so a free that reads back the clip finds the id stale.
- Called back in from the host's code the context runs (a host easing, the allocator, the task system's functions), all but
-
Breaking: the calls that take only a clip's player are named for it (#178):
Before Now mtwnPlayer_Play,_Pause,_Reverse,_Finish,_CancelmtwnClipPlayer_Play,_Pause,_Reverse,_Finish,_CancelmtwnPlayer_Seek,_Sweep,_SeekProgress,_SeekLabelmtwnClipPlayer_Seek,_Sweep,_SeekProgress,_SeekLabelmtwnPlayer_SetRate,_SetWeight,_GetStatemtwnClipPlayer_SetRate,_SetWeight,_GetStatemtwnCreatePlayermtwnCreateClipPlayermtwnPlayerDefmtwnClipPlayerDefmtwnDefaultPlayerDefmtwnDefaultClipPlayerDefMTWN_PLAYER_DEF_COOKIEMTWN_CLIP_PLAYER_DEF_COOKIEmtwnPlayerStatemtwnClipPlayerStatemtwnPlayerId,mtwnDestroyPlayerandmtwnPlayer_IsValid, which take a transition's player too, andmtwnPlayStatekeep their names. Parameters, values and the cookie's value are unchanged. -
Breaking:
mtwnContext_GetDroppedEventsismtwnContext_GetDroppedEventCountandmtwnContext_GetSerialAdvancesismtwnContext_GetSerialAdvanceCount, as the family names its counters. Their parameters and behavior are unchanged (#176). -
Breaking:
mtwnContext_EnableReducedMotionreturnsvoid. A null context changes nothing, and a call back in changes nothing and counts one misuse, as their refusals did; read the state withmtwnContext_IsReducedMotionEnabled(#174). -
Breaking:
mtwnEaseFunctionis a function type,double (void* context, double progress), as the task hooks' are:mtwnMakeHostEasingtakes, andmtwnEasing.functionholds, amtwnEaseFunction*. Passing a function's name compiles as before; a variable of the old type is declaredmtwnEaseFunction*(#172). -
Breaking:
mtwnMakeTracktakes the keyframes and their count first and the def third:mtwnMakeTrack(keyframes, count, def, lengthsOut, mixesOut, trackOut). It rewrites the keyframes in place for angles and for rotations on the shortest path, so they are the call's object, as the family's conventions order it. A call in the old order no longer compiles (#184). -
docs/user/getting-started.mdhas a Compatibility section, linked from the README: what 1.0.0 will keep across 1.y (every function, type, enum value, macro and struct field in the headers, things only added), what it leaves out (the fields of the made value structs a host never reads), the binary interface within a minor version, and results the same bits on every platform, any change of a bit stated here. The transitions guide says to askmtwnPlayer_IsValidbefore stopping a transition that may have ended (#180).
Fixed
mtwnContext_GetMisuse,mtwnContext_GetSerialAdvanceCountandmtwnContext_GetMemoryBytesread 0 from a null context, where they crashed, as the other getters do (#165).- A refused
mtwnMakeRoughEasingleaves the caller's points as they were. A host shape giving a value that is not finite past the first inner points left part of a new set there; the shape is now sampled at every inner point before one is written (#168). mtwnFitLinearEasingsays what a refused fit leaves in its points, which its header called unspecified: the fit's first points, at most the capacity, exactly as a larger buffer would start, and the rest as they were (#182).